update dart code style

This commit is contained in:
Christien Rioux 2025-08-30 11:50:21 -04:00
parent d9d145814f
commit 81f3263f59
167 changed files with 15223 additions and 11716 deletions

View file

@ -19,7 +19,7 @@ pluginManagement {
plugins {
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
id "com.android.application" version "8.8.0" apply false
id "org.jetbrains.kotlin.android" version "1.9.25" apply false
id "org.jetbrains.kotlin.android" version "2.1.0" apply false
}
include ":app"

View file

@ -7,19 +7,25 @@ import '../models/models.dart';
import '../repository/account_repository.dart';
class AccountInfoCubit extends Cubit<AccountInfo> {
AccountInfoCubit(
{required AccountRepository accountRepository,
required RecordKey superIdentityRecordKey})
: _accountRepository = accountRepository,
super(accountRepository.getAccountInfo(superIdentityRecordKey)!) {
final AccountRepository _accountRepository;
late final StreamSubscription<AccountRepositoryChange>
_accountRepositorySubscription;
AccountInfoCubit({
required AccountRepository accountRepository,
required RecordKey superIdentityRecordKey,
}) : _accountRepository = accountRepository,
super(accountRepository.getAccountInfo(superIdentityRecordKey)!) {
// Subscribe to streams
_accountRepositorySubscription = _accountRepository.stream.listen((change) {
switch (change) {
case AccountRepositoryChange.activeLocalAccount:
case AccountRepositoryChange.localAccounts:
case AccountRepositoryChange.userLogins:
final acctInfo =
accountRepository.getAccountInfo(superIdentityRecordKey);
final acctInfo = accountRepository.getAccountInfo(
superIdentityRecordKey,
);
if (acctInfo != null) {
emit(acctInfo);
}
@ -32,8 +38,4 @@ class AccountInfoCubit extends Cubit<AccountInfo> {
await super.close();
await _accountRepositorySubscription.cancel();
}
final AccountRepository _accountRepository;
late final StreamSubscription<AccountRepositoryChange>
_accountRepositorySubscription;
}

View file

@ -9,7 +9,7 @@ import '../account_manager.dart';
typedef AccountRecordState = proto.Account;
typedef _SspUpdateState = (
AccountSpec accountSpec,
Future<void> Function() onSuccess
Future<void> Function() onSuccess,
);
/// The saved state of a VeilidChat Account on the DHT
@ -18,20 +18,27 @@ typedef _SspUpdateState = (
/// account and is privately encrypted with an owned record from the 'userLogin'
/// tabledb-local storage, encrypted by the unlock code for the account.
class AccountRecordCubit extends DefaultDHTRecordCubit<AccountRecordState> {
AccountRecordCubit(
{required LocalAccount localAccount, required UserLogin userLogin})
: super(
decodeState: proto.Account.fromBuffer,
open: () => _open(localAccount, userLogin));
final _sspUpdate = SingleStateProcessor<_SspUpdateState>();
AccountRecordCubit({
required LocalAccount localAccount,
required UserLogin userLogin,
}) : super(
decodeState: proto.Account.fromBuffer,
open: () => _open(localAccount, userLogin),
);
static Future<DHTRecord> _open(
LocalAccount localAccount, UserLogin userLogin) async {
LocalAccount localAccount,
UserLogin userLogin,
) async {
// Record not yet open, do it
final pool = DHTRecordPool.instance;
final record = await pool.openRecordOwned(
userLogin.accountRecordInfo.accountRecord,
debugName: 'AccountRecordCubit::_open::AccountRecord',
parent: localAccount.superIdentity.currentInstance.recordKey);
userLogin.accountRecordInfo.accountRecord,
debugName: 'AccountRecordCubit::_open::AccountRecord',
parent: localAccount.superIdentity.currentInstance.recordKey,
);
return record;
}
@ -46,14 +53,18 @@ class AccountRecordCubit extends DefaultDHTRecordCubit<AccountRecordState> {
// Public Interface
void updateAccount(
AccountSpec accountSpec, Future<void> Function() onChanged) {
AccountSpec accountSpec,
Future<void> Function() onChanged,
) {
_sspUpdate.updateState((accountSpec, onChanged), (state) async {
await _updateAccountAsync(state.$1, state.$2);
});
}
Future<void> _updateAccountAsync(
AccountSpec accountSpec, Future<void> Function() onChanged) async {
AccountSpec accountSpec,
Future<void> Function() onChanged,
) async {
var changed = true;
await record?.eventualUpdateProtobuf(proto.Account.fromBuffer, (old) async {
if (old == null) {
@ -72,6 +83,4 @@ class AccountRecordCubit extends DefaultDHTRecordCubit<AccountRecordState> {
await onChanged();
}
}
final _sspUpdate = SingleStateProcessor<_SspUpdateState>();
}

View file

@ -6,9 +6,14 @@ import 'package:veilid_support/veilid_support.dart';
import '../repository/account_repository.dart';
class ActiveLocalAccountCubit extends Cubit<RecordKey?> {
final AccountRepository _accountRepository;
late final StreamSubscription<AccountRepositoryChange>
_accountRepositorySubscription;
ActiveLocalAccountCubit(AccountRepository accountRepository)
: _accountRepository = accountRepository,
super(accountRepository.getActiveLocalAccount()) {
: _accountRepository = accountRepository,
super(accountRepository.getActiveLocalAccount()) {
// Subscribe to streams
_accountRepositorySubscription = _accountRepository.stream.listen((change) {
switch (change) {
@ -27,8 +32,4 @@ class ActiveLocalAccountCubit extends Cubit<RecordKey?> {
await super.close();
await _accountRepositorySubscription.cancel();
}
final AccountRepository _accountRepository;
late final StreamSubscription<AccountRepositoryChange>
_accountRepositorySubscription;
}

View file

@ -12,9 +12,14 @@ typedef LocalAccountsState = IList<LocalAccount>;
class LocalAccountsCubit extends Cubit<LocalAccountsState>
with StateMapFollowable<LocalAccountsState, RecordKey, LocalAccount> {
final AccountRepository _accountRepository;
late final StreamSubscription<AccountRepositoryChange>
_accountRepositorySubscription;
LocalAccountsCubit(AccountRepository accountRepository)
: _accountRepository = accountRepository,
super(accountRepository.getLocalAccounts()) {
: _accountRepository = accountRepository,
super(accountRepository.getLocalAccounts()) {
// Subscribe to streams
_accountRepositorySubscription = _accountRepository.stream.listen((change) {
switch (change) {
@ -38,11 +43,10 @@ class LocalAccountsCubit extends Cubit<LocalAccountsState>
@override
IMap<RecordKey, LocalAccount> getStateMap(LocalAccountsState state) {
final stateValue = state;
return IMap.fromIterable(stateValue,
keyMapper: (e) => e.superIdentity.recordKey, valueMapper: (e) => e);
return IMap.fromIterable(
stateValue,
keyMapper: (e) => e.superIdentity.recordKey,
valueMapper: (e) => e,
);
}
final AccountRepository _accountRepository;
late final StreamSubscription<AccountRepositoryChange>
_accountRepositorySubscription;
}

View file

@ -4,33 +4,46 @@ import 'package:veilid_support/veilid_support.dart';
import '../../account_manager/account_manager.dart';
typedef PerAccountCollectionBlocMapState
= BlocMapState<RecordKey, PerAccountCollectionState>;
typedef PerAccountCollectionBlocMapState =
BlocMapState<RecordKey, PerAccountCollectionState>;
/// Map of the logged in user accounts to their PerAccountCollectionCubit
/// Ensures there is an single account record cubit for each logged in account
class PerAccountCollectionBlocMapCubit extends BlocMapCubit<RecordKey,
PerAccountCollectionState, PerAccountCollectionCubit>
class PerAccountCollectionBlocMapCubit
extends
BlocMapCubit<
RecordKey,
PerAccountCollectionState,
PerAccountCollectionCubit
>
with StateMapFollower<LocalAccountsState, RecordKey, LocalAccount> {
////////////////////////////////////////////////////////////////////////////
final AccountRepository _accountRepository;
final Locator _locator;
PerAccountCollectionBlocMapCubit({
required Locator locator,
required AccountRepository accountRepository,
}) : _locator = locator,
_accountRepository = accountRepository {
}) : _locator = locator,
_accountRepository = accountRepository {
// Follow the local accounts cubit
follow(locator<LocalAccountsCubit>());
}
// Add account record cubit
void _addPerAccountCollectionCubit(
{required RecordKey superIdentityRecordKey}) =>
add(
superIdentityRecordKey,
() => PerAccountCollectionCubit(
locator: _locator,
accountInfoCubit: AccountInfoCubit(
accountRepository: _accountRepository,
superIdentityRecordKey: superIdentityRecordKey)));
void _addPerAccountCollectionCubit({
required RecordKey superIdentityRecordKey,
}) => add(
superIdentityRecordKey,
() => PerAccountCollectionCubit(
locator: _locator,
accountInfoCubit: AccountInfoCubit(
accountRepository: _accountRepository,
superIdentityRecordKey: superIdentityRecordKey,
),
),
);
/// StateFollower /////////////////////////
@ -39,25 +52,26 @@ class PerAccountCollectionBlocMapCubit extends BlocMapCubit<RecordKey,
@override
void updateState(
RecordKey key, LocalAccount? oldValue, LocalAccount newValue) {
RecordKey key,
LocalAccount? oldValue,
LocalAccount newValue,
) {
// Don't replace unless this is a totally different account
// The sub-cubit's subscription will update our state later
if (oldValue != null) {
if (oldValue.superIdentity.recordKey !=
newValue.superIdentity.recordKey) {
throw StateError(
'should remove LocalAccount and make a new one, not change it, if '
'the superidentity record key has changed');
'should remove LocalAccount and make a new one, not change it, if '
'the superidentity record key has changed',
);
}
// This never changes anything that should result in rebuildin the
// sub-cubit
return;
}
_addPerAccountCollectionCubit(
superIdentityRecordKey: newValue.superIdentity.recordKey);
superIdentityRecordKey: newValue.superIdentity.recordKey,
);
}
////////////////////////////////////////////////////////////////////////////
final AccountRepository _accountRepository;
final Locator _locator;
}

View file

@ -19,11 +19,105 @@ const _kAccountRecordSubscriptionListenKey =
'accountRecordSubscriptionListenKey';
class PerAccountCollectionCubit extends Cubit<PerAccountCollectionState> {
final Locator _locator;
final _processor = SingleStateProcessor<AccountInfo>();
final _initWait = WaitSet<void, void>();
// Per-account cubits regardless of login state
final AccountInfoCubit accountInfoCubit;
// Per logged-in account cubits
AccountRecordCubit? accountRecordCubit;
StreamSubscription<AsyncValue<AccountRecordState>>?
_accountRecordSubscription;
final contactInvitationListCubitUpdater =
BlocUpdater<
ContactInvitationListCubit,
(AccountInfo, OwnedDHTRecordPointer)
>(
create: (params) => ContactInvitationListCubit(
accountInfo: params.$1,
contactInvitationListRecordPointer: params.$2,
),
);
final contactListCubitUpdater =
BlocUpdater<ContactListCubit, (AccountInfo, OwnedDHTRecordPointer)>(
create: (params) => ContactListCubit(
accountInfo: params.$1,
contactListRecordPointer: params.$2,
),
);
final waitingInvitationsBlocMapCubitUpdater =
BlocUpdater<
WaitingInvitationsBlocMapCubit,
(
AccountInfo,
AccountRecordCubit,
ContactInvitationListCubit,
ContactListCubit,
NotificationsCubit,
)
>(
create: (params) => WaitingInvitationsBlocMapCubit(
accountInfo: params.$1,
accountRecordCubit: params.$2,
contactInvitationListCubit: params.$3,
contactListCubit: params.$4,
notificationsCubit: params.$5,
),
);
final activeChatCubitUpdater = BlocUpdater<ActiveChatCubit, bool>(
create: (_) => ActiveChatCubit(null),
);
final chatListCubitUpdater =
BlocUpdater<
ChatListCubit,
(AccountInfo, OwnedDHTRecordPointer, ActiveChatCubit)
>(
create: (params) => ChatListCubit(
accountInfo: params.$1,
chatListRecordPointer: params.$2,
activeChatCubit: params.$3,
),
);
final activeConversationsBlocMapCubitUpdater =
BlocUpdater<
ActiveConversationsBlocMapCubit,
(AccountInfo, AccountRecordCubit, ChatListCubit, ContactListCubit)
>(
create: (params) => ActiveConversationsBlocMapCubit(
accountInfo: params.$1,
accountRecordCubit: params.$2,
chatListCubit: params.$3,
contactListCubit: params.$4,
),
);
final activeSingleContactChatBlocMapCubitUpdater =
BlocUpdater<
ActiveSingleContactChatBlocMapCubit,
(AccountInfo, ActiveConversationsBlocMapCubit)
>(
create: (params) => ActiveSingleContactChatBlocMapCubit(
accountInfo: params.$1,
activeConversationsBlocMapCubit: params.$2,
),
);
PerAccountCollectionCubit({
required Locator locator,
required this.accountInfoCubit,
}) : _locator = locator,
super(_initialState(accountInfoCubit)) {
}) : _locator = locator,
super(_initialState(accountInfoCubit)) {
// Async Init
_initWait.add(_init);
}
@ -49,26 +143,30 @@ class PerAccountCollectionCubit extends Cubit<PerAccountCollectionState> {
await super.close();
}
Future<void> _init(Completer<void> _cancel) async {
Future<void> _init(Completer<void> cancel) async {
// subscribe to accountInfo changes
_processor.follow(accountInfoCubit.stream, accountInfoCubit.state,
_followAccountInfoState);
_processor.follow(
accountInfoCubit.stream,
accountInfoCubit.state,
_followAccountInfoState,
);
}
static PerAccountCollectionState _initialState(
AccountInfoCubit accountInfoCubit) =>
PerAccountCollectionState(
accountInfo: accountInfoCubit.state,
avAccountRecordState: const AsyncValue.loading(),
contactInvitationListCubit: null,
accountInfoCubit: null,
accountRecordCubit: null,
contactListCubit: null,
waitingInvitationsBlocMapCubit: null,
activeChatCubit: null,
chatListCubit: null,
activeConversationsBlocMapCubit: null,
activeSingleContactChatBlocMapCubit: null);
AccountInfoCubit accountInfoCubit,
) => PerAccountCollectionState(
accountInfo: accountInfoCubit.state,
avAccountRecordState: const AsyncValue.loading(),
contactInvitationListCubit: null,
accountInfoCubit: null,
accountRecordCubit: null,
contactListCubit: null,
waitingInvitationsBlocMapCubit: null,
activeChatCubit: null,
chatListCubit: null,
activeConversationsBlocMapCubit: null,
activeSingleContactChatBlocMapCubit: null,
);
Future<void> _followAccountInfoState(AccountInfo accountInfo) async {
// Get the next state
@ -94,17 +192,21 @@ class PerAccountCollectionCubit extends Cubit<PerAccountCollectionState> {
// Create AccountRecordCubit
accountRecordCubit ??= AccountRecordCubit(
localAccount: accountInfo.localAccount,
userLogin: accountInfo.userLogin!);
localAccount: accountInfo.localAccount,
userLogin: accountInfo.userLogin!,
);
// Update state to value
nextState =
await _updateAccountRecordState(nextState, accountRecordCubit!.state);
nextState = await _updateAccountRecordState(
nextState,
accountRecordCubit!.state,
);
emit(nextState);
// Subscribe AccountRecordCubit
_accountRecordSubscription ??=
accountRecordCubit!.stream.listen((avAccountRecordState) {
_accountRecordSubscription ??= accountRecordCubit!.stream.listen((
avAccountRecordState,
) {
serialFuture((this, _kAccountRecordSubscriptionListenKey), () async {
emit(await _updateAccountRecordState(state, avAccountRecordState));
});
@ -113,102 +215,120 @@ class PerAccountCollectionCubit extends Cubit<PerAccountCollectionState> {
}
Future<PerAccountCollectionState> _updateAccountRecordState(
PerAccountCollectionState prevState,
AsyncValue<AccountRecordState>? avAccountRecordState) async {
PerAccountCollectionState prevState,
AsyncValue<AccountRecordState>? avAccountRecordState,
) async {
// Get next state
final nextState =
prevState.copyWith(avAccountRecordState: avAccountRecordState);
final nextState = prevState.copyWith(
avAccountRecordState: avAccountRecordState,
);
// Get bloc parameters
final accountInfo = nextState.accountInfo;
// ContactInvitationListCubit
final contactInvitationListRecordPointer = nextState
.avAccountRecordState?.asData?.value.contactInvitationRecords
.avAccountRecordState
?.asData
?.value
.contactInvitationRecords
.toDart();
final contactInvitationListCubit = await contactInvitationListCubitUpdater
.update(accountInfo.userLogin == null ||
contactInvitationListRecordPointer == null
? null
: (accountInfo, contactInvitationListRecordPointer));
.update(
accountInfo.userLogin == null ||
contactInvitationListRecordPointer == null
? null
: (accountInfo, contactInvitationListRecordPointer),
);
// ContactListCubit
final contactListRecordPointer =
nextState.avAccountRecordState?.asData?.value.contactList.toDart();
final contactListRecordPointer = nextState
.avAccountRecordState
?.asData
?.value
.contactList
.toDart();
final contactListCubit = await contactListCubitUpdater.update(
accountInfo.userLogin == null || contactListRecordPointer == null
? null
: (accountInfo, contactListRecordPointer));
accountInfo.userLogin == null || contactListRecordPointer == null
? null
: (accountInfo, contactListRecordPointer),
);
// WaitingInvitationsBlocMapCubit
final waitingInvitationsBlocMapCubit =
await waitingInvitationsBlocMapCubitUpdater.update(
accountInfo.userLogin == null ||
contactInvitationListCubit == null ||
contactListCubit == null
? null
: (
accountInfo,
accountRecordCubit!,
contactInvitationListCubit,
contactListCubit,
_locator<NotificationsCubit>(),
));
accountInfo.userLogin == null ||
contactInvitationListCubit == null ||
contactListCubit == null
? null
: (
accountInfo,
accountRecordCubit!,
contactInvitationListCubit,
contactListCubit,
_locator<NotificationsCubit>(),
),
);
// ActiveChatCubit
final activeChatCubit = await activeChatCubitUpdater
.update((accountInfo.userLogin == null) ? null : true);
final activeChatCubit = await activeChatCubitUpdater.update(
(accountInfo.userLogin == null) ? null : true,
);
// ChatListCubit
final chatListRecordPointer =
nextState.avAccountRecordState?.asData?.value.chatList.toDart();
final chatListRecordPointer = nextState
.avAccountRecordState
?.asData
?.value
.chatList
.toDart();
final chatListCubit = await chatListCubitUpdater.update(
accountInfo.userLogin == null ||
chatListRecordPointer == null ||
activeChatCubit == null
? null
: (accountInfo, chatListRecordPointer, activeChatCubit));
accountInfo.userLogin == null ||
chatListRecordPointer == null ||
activeChatCubit == null
? null
: (accountInfo, chatListRecordPointer, activeChatCubit),
);
// ActiveConversationsBlocMapCubit
final activeConversationsBlocMapCubit =
await activeConversationsBlocMapCubitUpdater.update(
accountRecordCubit == null ||
chatListCubit == null ||
contactListCubit == null
? null
: (
accountInfo,
accountRecordCubit!,
chatListCubit,
contactListCubit
));
accountRecordCubit == null ||
chatListCubit == null ||
contactListCubit == null
? null
: (
accountInfo,
accountRecordCubit!,
chatListCubit,
contactListCubit,
),
);
// ActiveSingleContactChatBlocMapCubit
final activeSingleContactChatBlocMapCubit =
await activeSingleContactChatBlocMapCubitUpdater.update(
accountInfo.userLogin == null ||
activeConversationsBlocMapCubit == null
? null
: (
accountInfo,
activeConversationsBlocMapCubit,
));
accountInfo.userLogin == null ||
activeConversationsBlocMapCubit == null
? null
: (accountInfo, activeConversationsBlocMapCubit),
);
// Update available blocs in our state
return nextState.copyWith(
contactInvitationListCubit: contactInvitationListCubit,
accountInfoCubit: accountInfoCubit,
accountRecordCubit: accountRecordCubit,
contactListCubit: contactListCubit,
waitingInvitationsBlocMapCubit: waitingInvitationsBlocMapCubit,
activeChatCubit: activeChatCubit,
chatListCubit: chatListCubit,
activeConversationsBlocMapCubit: activeConversationsBlocMapCubit,
activeSingleContactChatBlocMapCubit:
activeSingleContactChatBlocMapCubit);
contactInvitationListCubit: contactInvitationListCubit,
accountInfoCubit: accountInfoCubit,
accountRecordCubit: accountRecordCubit,
contactListCubit: contactListCubit,
waitingInvitationsBlocMapCubit: waitingInvitationsBlocMapCubit,
activeChatCubit: activeChatCubit,
chatListCubit: chatListCubit,
activeConversationsBlocMapCubit: activeConversationsBlocMapCubit,
activeSingleContactChatBlocMapCubit: activeSingleContactChatBlocMapCubit,
);
}
T collectionLocator<T>() {
@ -241,70 +361,4 @@ class PerAccountCollectionCubit extends Cubit<PerAccountCollectionState> {
}
return _locator<T>();
}
final Locator _locator;
final _processor = SingleStateProcessor<AccountInfo>();
final _initWait = WaitSet<void, void>();
// Per-account cubits regardless of login state
final AccountInfoCubit accountInfoCubit;
// Per logged-in account cubits
AccountRecordCubit? accountRecordCubit;
StreamSubscription<AsyncValue<AccountRecordState>>?
_accountRecordSubscription;
final contactInvitationListCubitUpdater = BlocUpdater<
ContactInvitationListCubit, (AccountInfo, OwnedDHTRecordPointer)>(
create: (params) => ContactInvitationListCubit(
accountInfo: params.$1,
contactInvitationListRecordPointer: params.$2,
));
final contactListCubitUpdater =
BlocUpdater<ContactListCubit, (AccountInfo, OwnedDHTRecordPointer)>(
create: (params) => ContactListCubit(
accountInfo: params.$1,
contactListRecordPointer: params.$2,
));
final waitingInvitationsBlocMapCubitUpdater = BlocUpdater<
WaitingInvitationsBlocMapCubit,
(
AccountInfo,
AccountRecordCubit,
ContactInvitationListCubit,
ContactListCubit,
NotificationsCubit,
)>(
create: (params) => WaitingInvitationsBlocMapCubit(
accountInfo: params.$1,
accountRecordCubit: params.$2,
contactInvitationListCubit: params.$3,
contactListCubit: params.$4,
notificationsCubit: params.$5,
));
final activeChatCubitUpdater =
BlocUpdater<ActiveChatCubit, bool>(create: (_) => ActiveChatCubit(null));
final chatListCubitUpdater = BlocUpdater<ChatListCubit,
(AccountInfo, OwnedDHTRecordPointer, ActiveChatCubit)>(
create: (params) => ChatListCubit(
accountInfo: params.$1,
chatListRecordPointer: params.$2,
activeChatCubit: params.$3));
final activeConversationsBlocMapCubitUpdater = BlocUpdater<
ActiveConversationsBlocMapCubit,
(AccountInfo, AccountRecordCubit, ChatListCubit, ContactListCubit)>(
create: (params) => ActiveConversationsBlocMapCubit(
accountInfo: params.$1,
accountRecordCubit: params.$2,
chatListCubit: params.$3,
contactListCubit: params.$4));
final activeSingleContactChatBlocMapCubitUpdater = BlocUpdater<
ActiveSingleContactChatBlocMapCubit,
(
AccountInfo,
ActiveConversationsBlocMapCubit,
)>(
create: (params) => ActiveSingleContactChatBlocMapCubit(
accountInfo: params.$1,
activeConversationsBlocMapCubit: params.$2,
));
}

View file

@ -9,9 +9,16 @@ import '../repository/account_repository.dart';
typedef UserLoginsState = IList<UserLogin>;
class UserLoginsCubit extends Cubit<UserLoginsState> {
////////////////////////////////////////////////////////////////////////////
final AccountRepository _accountRepository;
late final StreamSubscription<AccountRepositoryChange>
_accountRepositorySubscription;
UserLoginsCubit(AccountRepository accountRepository)
: _accountRepository = accountRepository,
super(accountRepository.getUserLogins()) {
: _accountRepository = accountRepository,
super(accountRepository.getUserLogins()) {
// Subscribe to streams
_accountRepositorySubscription = _accountRepository.stream.listen((change) {
switch (change) {
@ -30,9 +37,4 @@ class UserLoginsCubit extends Cubit<UserLoginsState> {
await super.close();
await _accountRepositorySubscription.cancel();
}
////////////////////////////////////////////////////////////////////////////
final AccountRepository _accountRepository;
late final StreamSubscription<AccountRepositoryChange>
_accountRepositorySubscription;
}

View file

@ -7,37 +7,31 @@ import 'package:veilid_support/veilid_support.dart';
import '../../conversation/conversation.dart';
import '../account_manager.dart';
enum AccountInfoStatus {
accountInvalid,
accountLocked,
accountUnlocked,
}
enum AccountInfoStatus { accountInvalid, accountLocked, accountUnlocked }
@immutable
class AccountInfo extends Equatable implements ToDebugMap {
final AccountInfoStatus status;
final LocalAccount localAccount;
final UserLogin? userLogin;
const AccountInfo({
required this.status,
required this.localAccount,
required this.userLogin,
});
final AccountInfoStatus status;
final LocalAccount localAccount;
final UserLogin? userLogin;
@override
List<Object?> get props => [
status,
localAccount,
userLogin,
];
List<Object?> get props => [status, localAccount, userLogin];
@override
Map<String, dynamic> toDebugMap() => {
'status': status,
'localAccount': localAccount,
'userLogin': userLogin,
};
'status': status,
'localAccount': localAccount,
'userLogin': userLogin,
};
}
extension AccountInfoExt on AccountInfo {
@ -58,17 +52,23 @@ extension AccountInfoExt on AccountInfo {
// xxx: this needs to go away, because its bullshit
Future<VeilidCrypto> makeConversationCrypto(Author remoteAuthor) async {
// vvv bullshit
final bullshitRemotePublicKey =
PublicKey.fromString(remoteAuthor.id.toString());
final bullshitRemotePublicKey = PublicKey.fromString(
remoteAuthor.id.toString(),
);
// ^^^ bullshit
final identitySecret = userLogin!.identitySecret;
final cs = await Veilid.instance.getCryptoSystem(identitySecret.kind);
final sharedSecret = await cs.generateSharedSecret(bullshitRemotePublicKey,
identitySecret, utf8.encode('VeilidChat Conversation'));
final sharedSecret = await cs.generateSharedSecret(
bullshitRemotePublicKey,
identitySecret,
utf8.encode('VeilidChat Conversation'),
);
final messagesCrypto = await VeilidCryptoPrivate.fromSharedSecret(
identitySecret.kind, sharedSecret);
identitySecret.kind,
sharedSecret,
);
return messagesCrypto;
}
}

View file

@ -10,44 +10,69 @@ import '../../proto/proto.dart' as proto;
/// Some are privately held as proto.Account configurations
@immutable
class AccountSpec extends Equatable {
const AccountSpec(
{required this.name,
required this.pronouns,
required this.about,
required this.availability,
required this.invisible,
required this.freeMessage,
required this.awayMessage,
required this.busyMessage,
required this.avatar,
required this.autoAway,
required this.autoAwayTimeout});
////////////////////////////////////////////////////////////////////////////
final String name;
final String pronouns;
final String about;
final proto.Availability availability;
final bool invisible;
final String freeMessage;
final String awayMessage;
final String busyMessage;
final proto.DataReference? avatar;
final bool autoAway;
final int autoAwayTimeout;
const AccountSpec({
required this.name,
required this.pronouns,
required this.about,
required this.availability,
required this.invisible,
required this.freeMessage,
required this.awayMessage,
required this.busyMessage,
required this.avatar,
required this.autoAway,
required this.autoAwayTimeout,
});
const AccountSpec.empty()
: name = '',
pronouns = '',
about = '',
availability = proto.Availability.AVAILABILITY_FREE,
invisible = false,
freeMessage = '',
awayMessage = '',
busyMessage = '',
avatar = null,
autoAway = false,
autoAwayTimeout = 15;
: name = '',
pronouns = '',
about = '',
availability = proto.Availability.AVAILABILITY_FREE,
invisible = false,
freeMessage = '',
awayMessage = '',
busyMessage = '',
avatar = null,
autoAway = false,
autoAwayTimeout = 15;
AccountSpec.fromProto(proto.Account p)
: name = p.profile.name,
pronouns = p.profile.pronouns,
about = p.profile.about,
availability = p.profile.availability,
invisible = p.invisible,
freeMessage = p.freeMessage,
awayMessage = p.awayMessage,
busyMessage = p.busyMessage,
avatar = p.profile.hasAvatar() ? p.profile.avatar : null,
autoAway = p.autodetectAway,
autoAwayTimeout = p.autoAwayTimeoutMin;
: name = p.profile.name,
pronouns = p.profile.pronouns,
about = p.profile.about,
availability = p.profile.availability,
invisible = p.invisible,
freeMessage = p.freeMessage,
awayMessage = p.awayMessage,
busyMessage = p.busyMessage,
avatar = p.profile.hasAvatar() ? p.profile.avatar : null,
autoAway = p.autodetectAway,
autoAwayTimeout = p.autoAwayTimeoutMin;
String get status {
late final String status;
@ -90,32 +115,18 @@ class AccountSpec extends Equatable {
return newProto;
}
////////////////////////////////////////////////////////////////////////////
final String name;
final String pronouns;
final String about;
final proto.Availability availability;
final bool invisible;
final String freeMessage;
final String awayMessage;
final String busyMessage;
final proto.DataReference? avatar;
final bool autoAway;
final int autoAwayTimeout;
@override
List<Object?> get props => [
name,
pronouns,
about,
availability,
invisible,
freeMessage,
awayMessage,
busyMessage,
avatar,
autoAway,
autoAwayTimeout
];
name,
pronouns,
about,
availability,
invisible,
freeMessage,
awayMessage,
busyMessage,
avatar,
autoAway,
autoAwayTimeout,
];
}

View file

@ -1,6 +1,5 @@
// dart format width=80
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// coverage:ignore-file
// 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
@ -12,251 +11,285 @@ part of 'local_account.dart';
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$LocalAccount {
// The super identity key record for the account,
// containing the publicKey in the currentIdentity
SuperIdentity
get superIdentity; // The encrypted currentIdentity secret that goes with
SuperIdentity get superIdentity;// The encrypted currentIdentity secret that goes with
// the identityPublicKey with appended salt
@Uint8ListJsonConverter()
Uint8List
get identitySecretBytes; // The kind of encryption input used on the account
EncryptionKeyType
get encryptionKeyType; // If account is not hidden, password can be retrieved via
bool
get biometricsEnabled; // Keep account hidden unless account password is entered
@Uint8ListJsonConverter() Uint8List get identitySecretBytes;// The kind of encryption input used on the account
EncryptionKeyType get encryptionKeyType;// If account is not hidden, password can be retrieved via
bool get biometricsEnabled;// Keep account hidden unless account password is entered
// (tries all hidden accounts with auth method (no biometrics))
bool get hiddenAccount; // Display name for account until it is unlocked
String get name;
/// Create a copy of LocalAccount
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$LocalAccountCopyWith<LocalAccount> get copyWith =>
_$LocalAccountCopyWithImpl<LocalAccount>(
this as LocalAccount, _$identity);
bool get hiddenAccount;// Display name for account until it is unlocked
String get name;
/// Create a copy of LocalAccount
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$LocalAccountCopyWith<LocalAccount> get copyWith => _$LocalAccountCopyWithImpl<LocalAccount>(this as LocalAccount, _$identity);
/// Serializes this LocalAccount to a JSON map.
Map<String, dynamic> toJson();
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is LocalAccount &&
(identical(other.superIdentity, superIdentity) ||
other.superIdentity == superIdentity) &&
const DeepCollectionEquality()
.equals(other.identitySecretBytes, identitySecretBytes) &&
(identical(other.encryptionKeyType, encryptionKeyType) ||
other.encryptionKeyType == encryptionKeyType) &&
(identical(other.biometricsEnabled, biometricsEnabled) ||
other.biometricsEnabled == biometricsEnabled) &&
(identical(other.hiddenAccount, hiddenAccount) ||
other.hiddenAccount == hiddenAccount) &&
(identical(other.name, name) || other.name == name));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(
runtimeType,
superIdentity,
const DeepCollectionEquality().hash(identitySecretBytes),
encryptionKeyType,
biometricsEnabled,
hiddenAccount,
name);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is LocalAccount&&(identical(other.superIdentity, superIdentity) || other.superIdentity == superIdentity)&&const DeepCollectionEquality().equals(other.identitySecretBytes, identitySecretBytes)&&(identical(other.encryptionKeyType, encryptionKeyType) || other.encryptionKeyType == encryptionKeyType)&&(identical(other.biometricsEnabled, biometricsEnabled) || other.biometricsEnabled == biometricsEnabled)&&(identical(other.hiddenAccount, hiddenAccount) || other.hiddenAccount == hiddenAccount)&&(identical(other.name, name) || other.name == name));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,superIdentity,const DeepCollectionEquality().hash(identitySecretBytes),encryptionKeyType,biometricsEnabled,hiddenAccount,name);
@override
String toString() {
return 'LocalAccount(superIdentity: $superIdentity, identitySecretBytes: $identitySecretBytes, encryptionKeyType: $encryptionKeyType, biometricsEnabled: $biometricsEnabled, hiddenAccount: $hiddenAccount, name: $name)';
}
@override
String toString() {
return 'LocalAccount(superIdentity: $superIdentity, identitySecretBytes: $identitySecretBytes, encryptionKeyType: $encryptionKeyType, biometricsEnabled: $biometricsEnabled, hiddenAccount: $hiddenAccount, name: $name)';
}
}
/// @nodoc
abstract mixin class $LocalAccountCopyWith<$Res> {
factory $LocalAccountCopyWith(
LocalAccount value, $Res Function(LocalAccount) _then) =
_$LocalAccountCopyWithImpl;
@useResult
$Res call(
{SuperIdentity superIdentity,
@Uint8ListJsonConverter() Uint8List identitySecretBytes,
EncryptionKeyType encryptionKeyType,
bool biometricsEnabled,
bool hiddenAccount,
String name});
abstract mixin class $LocalAccountCopyWith<$Res> {
factory $LocalAccountCopyWith(LocalAccount value, $Res Function(LocalAccount) _then) = _$LocalAccountCopyWithImpl;
@useResult
$Res call({
SuperIdentity superIdentity,@Uint8ListJsonConverter() Uint8List identitySecretBytes, EncryptionKeyType encryptionKeyType, bool biometricsEnabled, bool hiddenAccount, String name
});
$SuperIdentityCopyWith<$Res> get superIdentity;
$SuperIdentityCopyWith<$Res> get superIdentity;
}
/// @nodoc
class _$LocalAccountCopyWithImpl<$Res> implements $LocalAccountCopyWith<$Res> {
class _$LocalAccountCopyWithImpl<$Res>
implements $LocalAccountCopyWith<$Res> {
_$LocalAccountCopyWithImpl(this._self, this._then);
final LocalAccount _self;
final $Res Function(LocalAccount) _then;
/// Create a copy of LocalAccount
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? superIdentity = null,
Object? identitySecretBytes = null,
Object? encryptionKeyType = null,
Object? biometricsEnabled = null,
Object? hiddenAccount = null,
Object? name = null,
}) {
return _then(_self.copyWith(
superIdentity: null == superIdentity
? _self.superIdentity
: superIdentity // ignore: cast_nullable_to_non_nullable
as SuperIdentity,
identitySecretBytes: null == identitySecretBytes
? _self.identitySecretBytes
: identitySecretBytes // ignore: cast_nullable_to_non_nullable
as Uint8List,
encryptionKeyType: null == encryptionKeyType
? _self.encryptionKeyType
: encryptionKeyType // ignore: cast_nullable_to_non_nullable
as EncryptionKeyType,
biometricsEnabled: null == biometricsEnabled
? _self.biometricsEnabled
: biometricsEnabled // ignore: cast_nullable_to_non_nullable
as bool,
hiddenAccount: null == hiddenAccount
? _self.hiddenAccount
: hiddenAccount // ignore: cast_nullable_to_non_nullable
as bool,
name: null == name
? _self.name
: name // ignore: cast_nullable_to_non_nullable
as String,
));
}
/// Create a copy of LocalAccount
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? superIdentity = null,Object? identitySecretBytes = null,Object? encryptionKeyType = null,Object? biometricsEnabled = null,Object? hiddenAccount = null,Object? name = null,}) {
return _then(_self.copyWith(
superIdentity: null == superIdentity ? _self.superIdentity : superIdentity // ignore: cast_nullable_to_non_nullable
as SuperIdentity,identitySecretBytes: null == identitySecretBytes ? _self.identitySecretBytes : identitySecretBytes // ignore: cast_nullable_to_non_nullable
as Uint8List,encryptionKeyType: null == encryptionKeyType ? _self.encryptionKeyType : encryptionKeyType // ignore: cast_nullable_to_non_nullable
as EncryptionKeyType,biometricsEnabled: null == biometricsEnabled ? _self.biometricsEnabled : biometricsEnabled // ignore: cast_nullable_to_non_nullable
as bool,hiddenAccount: null == hiddenAccount ? _self.hiddenAccount : hiddenAccount // ignore: cast_nullable_to_non_nullable
as bool,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
as String,
));
}
/// Create a copy of LocalAccount
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$SuperIdentityCopyWith<$Res> get superIdentity {
return $SuperIdentityCopyWith<$Res>(_self.superIdentity, (value) {
return _then(_self.copyWith(superIdentity: value));
});
}
}
/// Adds pattern-matching-related methods to [LocalAccount].
extension LocalAccountPatterns on LocalAccount {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _LocalAccount value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _LocalAccount() when $default != null:
return $default(_that);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _LocalAccount value) $default,){
final _that = this;
switch (_that) {
case _LocalAccount():
return $default(_that);case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _LocalAccount value)? $default,){
final _that = this;
switch (_that) {
case _LocalAccount() when $default != null:
return $default(_that);case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( SuperIdentity superIdentity, @Uint8ListJsonConverter() Uint8List identitySecretBytes, EncryptionKeyType encryptionKeyType, bool biometricsEnabled, bool hiddenAccount, String name)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _LocalAccount() when $default != null:
return $default(_that.superIdentity,_that.identitySecretBytes,_that.encryptionKeyType,_that.biometricsEnabled,_that.hiddenAccount,_that.name);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( SuperIdentity superIdentity, @Uint8ListJsonConverter() Uint8List identitySecretBytes, EncryptionKeyType encryptionKeyType, bool biometricsEnabled, bool hiddenAccount, String name) $default,) {final _that = this;
switch (_that) {
case _LocalAccount():
return $default(_that.superIdentity,_that.identitySecretBytes,_that.encryptionKeyType,_that.biometricsEnabled,_that.hiddenAccount,_that.name);case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( SuperIdentity superIdentity, @Uint8ListJsonConverter() Uint8List identitySecretBytes, EncryptionKeyType encryptionKeyType, bool biometricsEnabled, bool hiddenAccount, String name)? $default,) {final _that = this;
switch (_that) {
case _LocalAccount() when $default != null:
return $default(_that.superIdentity,_that.identitySecretBytes,_that.encryptionKeyType,_that.biometricsEnabled,_that.hiddenAccount,_that.name);case _:
return null;
}
}
/// Create a copy of LocalAccount
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$SuperIdentityCopyWith<$Res> get superIdentity {
return $SuperIdentityCopyWith<$Res>(_self.superIdentity, (value) {
return _then(_self.copyWith(superIdentity: value));
});
}
}
/// @nodoc
@JsonSerializable()
class _LocalAccount implements LocalAccount {
const _LocalAccount(
{required this.superIdentity,
@Uint8ListJsonConverter() required this.identitySecretBytes,
required this.encryptionKeyType,
required this.biometricsEnabled,
required this.hiddenAccount,
required this.name});
const _LocalAccount({required this.superIdentity, @Uint8ListJsonConverter() required this.identitySecretBytes, required this.encryptionKeyType, required this.biometricsEnabled, required this.hiddenAccount, required this.name});
// The super identity key record for the account,
// containing the publicKey in the currentIdentity
@override
final SuperIdentity superIdentity;
@override final SuperIdentity superIdentity;
// The encrypted currentIdentity secret that goes with
// the identityPublicKey with appended salt
@override
@Uint8ListJsonConverter()
final Uint8List identitySecretBytes;
@override@Uint8ListJsonConverter() final Uint8List identitySecretBytes;
// The kind of encryption input used on the account
@override
final EncryptionKeyType encryptionKeyType;
@override final EncryptionKeyType encryptionKeyType;
// If account is not hidden, password can be retrieved via
@override
final bool biometricsEnabled;
@override final bool biometricsEnabled;
// Keep account hidden unless account password is entered
// (tries all hidden accounts with auth method (no biometrics))
@override
final bool hiddenAccount;
@override final bool hiddenAccount;
// Display name for account until it is unlocked
@override
final String name;
@override final String name;
/// Create a copy of LocalAccount
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$LocalAccountCopyWith<_LocalAccount> get copyWith =>
__$LocalAccountCopyWithImpl<_LocalAccount>(this, _$identity);
/// Create a copy of LocalAccount
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$LocalAccountCopyWith<_LocalAccount> get copyWith => __$LocalAccountCopyWithImpl<_LocalAccount>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$LocalAccountToJson(
this,
);
}
@override
Map<String, dynamic> toJson() {
return _$LocalAccountToJson(this, );
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _LocalAccount &&
(identical(other.superIdentity, superIdentity) ||
other.superIdentity == superIdentity) &&
const DeepCollectionEquality()
.equals(other.identitySecretBytes, identitySecretBytes) &&
(identical(other.encryptionKeyType, encryptionKeyType) ||
other.encryptionKeyType == encryptionKeyType) &&
(identical(other.biometricsEnabled, biometricsEnabled) ||
other.biometricsEnabled == biometricsEnabled) &&
(identical(other.hiddenAccount, hiddenAccount) ||
other.hiddenAccount == hiddenAccount) &&
(identical(other.name, name) || other.name == name));
}
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _LocalAccount&&(identical(other.superIdentity, superIdentity) || other.superIdentity == superIdentity)&&const DeepCollectionEquality().equals(other.identitySecretBytes, identitySecretBytes)&&(identical(other.encryptionKeyType, encryptionKeyType) || other.encryptionKeyType == encryptionKeyType)&&(identical(other.biometricsEnabled, biometricsEnabled) || other.biometricsEnabled == biometricsEnabled)&&(identical(other.hiddenAccount, hiddenAccount) || other.hiddenAccount == hiddenAccount)&&(identical(other.name, name) || other.name == name));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,superIdentity,const DeepCollectionEquality().hash(identitySecretBytes),encryptionKeyType,biometricsEnabled,hiddenAccount,name);
@override
String toString() {
return 'LocalAccount(superIdentity: $superIdentity, identitySecretBytes: $identitySecretBytes, encryptionKeyType: $encryptionKeyType, biometricsEnabled: $biometricsEnabled, hiddenAccount: $hiddenAccount, name: $name)';
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(
runtimeType,
superIdentity,
const DeepCollectionEquality().hash(identitySecretBytes),
encryptionKeyType,
biometricsEnabled,
hiddenAccount,
name);
@override
String toString() {
return 'LocalAccount(superIdentity: $superIdentity, identitySecretBytes: $identitySecretBytes, encryptionKeyType: $encryptionKeyType, biometricsEnabled: $biometricsEnabled, hiddenAccount: $hiddenAccount, name: $name)';
}
}
/// @nodoc
abstract mixin class _$LocalAccountCopyWith<$Res>
implements $LocalAccountCopyWith<$Res> {
factory _$LocalAccountCopyWith(
_LocalAccount value, $Res Function(_LocalAccount) _then) =
__$LocalAccountCopyWithImpl;
@override
@useResult
$Res call(
{SuperIdentity superIdentity,
@Uint8ListJsonConverter() Uint8List identitySecretBytes,
EncryptionKeyType encryptionKeyType,
bool biometricsEnabled,
bool hiddenAccount,
String name});
abstract mixin class _$LocalAccountCopyWith<$Res> implements $LocalAccountCopyWith<$Res> {
factory _$LocalAccountCopyWith(_LocalAccount value, $Res Function(_LocalAccount) _then) = __$LocalAccountCopyWithImpl;
@override @useResult
$Res call({
SuperIdentity superIdentity,@Uint8ListJsonConverter() Uint8List identitySecretBytes, EncryptionKeyType encryptionKeyType, bool biometricsEnabled, bool hiddenAccount, String name
});
@override $SuperIdentityCopyWith<$Res> get superIdentity;
@override
$SuperIdentityCopyWith<$Res> get superIdentity;
}
/// @nodoc
class __$LocalAccountCopyWithImpl<$Res>
implements _$LocalAccountCopyWith<$Res> {
@ -265,55 +298,30 @@ class __$LocalAccountCopyWithImpl<$Res>
final _LocalAccount _self;
final $Res Function(_LocalAccount) _then;
/// Create a copy of LocalAccount
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$Res call({
Object? superIdentity = null,
Object? identitySecretBytes = null,
Object? encryptionKeyType = null,
Object? biometricsEnabled = null,
Object? hiddenAccount = null,
Object? name = null,
}) {
return _then(_LocalAccount(
superIdentity: null == superIdentity
? _self.superIdentity
: superIdentity // ignore: cast_nullable_to_non_nullable
as SuperIdentity,
identitySecretBytes: null == identitySecretBytes
? _self.identitySecretBytes
: identitySecretBytes // ignore: cast_nullable_to_non_nullable
as Uint8List,
encryptionKeyType: null == encryptionKeyType
? _self.encryptionKeyType
: encryptionKeyType // ignore: cast_nullable_to_non_nullable
as EncryptionKeyType,
biometricsEnabled: null == biometricsEnabled
? _self.biometricsEnabled
: biometricsEnabled // ignore: cast_nullable_to_non_nullable
as bool,
hiddenAccount: null == hiddenAccount
? _self.hiddenAccount
: hiddenAccount // ignore: cast_nullable_to_non_nullable
as bool,
name: null == name
? _self.name
: name // ignore: cast_nullable_to_non_nullable
as String,
));
}
/// Create a copy of LocalAccount
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? superIdentity = null,Object? identitySecretBytes = null,Object? encryptionKeyType = null,Object? biometricsEnabled = null,Object? hiddenAccount = null,Object? name = null,}) {
return _then(_LocalAccount(
superIdentity: null == superIdentity ? _self.superIdentity : superIdentity // ignore: cast_nullable_to_non_nullable
as SuperIdentity,identitySecretBytes: null == identitySecretBytes ? _self.identitySecretBytes : identitySecretBytes // ignore: cast_nullable_to_non_nullable
as Uint8List,encryptionKeyType: null == encryptionKeyType ? _self.encryptionKeyType : encryptionKeyType // ignore: cast_nullable_to_non_nullable
as EncryptionKeyType,biometricsEnabled: null == biometricsEnabled ? _self.biometricsEnabled : biometricsEnabled // ignore: cast_nullable_to_non_nullable
as bool,hiddenAccount: null == hiddenAccount ? _self.hiddenAccount : hiddenAccount // ignore: cast_nullable_to_non_nullable
as bool,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
as String,
));
}
/// Create a copy of LocalAccount
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$SuperIdentityCopyWith<$Res> get superIdentity {
return $SuperIdentityCopyWith<$Res>(_self.superIdentity, (value) {
return _then(_self.copyWith(superIdentity: value));
});
}
/// Create a copy of LocalAccount
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$SuperIdentityCopyWith<$Res> get superIdentity {
return $SuperIdentityCopyWith<$Res>(_self.superIdentity, (value) {
return _then(_self.copyWith(superIdentity: value));
});
}
}
// dart format on

View file

@ -9,10 +9,12 @@ part of 'local_account.dart';
_LocalAccount _$LocalAccountFromJson(Map<String, dynamic> json) =>
_LocalAccount(
superIdentity: SuperIdentity.fromJson(json['super_identity']),
identitySecretBytes: const Uint8ListJsonConverter()
.fromJson(json['identity_secret_bytes']),
encryptionKeyType:
EncryptionKeyType.fromJson(json['encryption_key_type']),
identitySecretBytes: const Uint8ListJsonConverter().fromJson(
json['identity_secret_bytes'],
),
encryptionKeyType: EncryptionKeyType.fromJson(
json['encryption_key_type'],
),
biometricsEnabled: json['biometrics_enabled'] as bool,
hiddenAccount: json['hidden_account'] as bool,
name: json['name'] as String,
@ -21,8 +23,9 @@ _LocalAccount _$LocalAccountFromJson(Map<String, dynamic> json) =>
Map<String, dynamic> _$LocalAccountToJson(_LocalAccount instance) =>
<String, dynamic>{
'super_identity': instance.superIdentity.toJson(),
'identity_secret_bytes':
const Uint8ListJsonConverter().toJson(instance.identitySecretBytes),
'identity_secret_bytes': const Uint8ListJsonConverter().toJson(
instance.identitySecretBytes,
),
'encryption_key_type': instance.encryptionKeyType.toJson(),
'biometrics_enabled': instance.biometricsEnabled,
'hidden_account': instance.hiddenAccount,

View file

@ -1,6 +1,5 @@
// dart format width=80
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// coverage:ignore-file
// 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
@ -12,110 +11,47 @@ part of 'per_account_collection_state.dart';
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$PerAccountCollectionState {
AccountInfo get accountInfo;
AsyncValue<AccountRecordState>? get avAccountRecordState;
AccountInfoCubit? get accountInfoCubit;
AccountRecordCubit? get accountRecordCubit;
ContactInvitationListCubit? get contactInvitationListCubit;
ContactListCubit? get contactListCubit;
WaitingInvitationsBlocMapCubit? get waitingInvitationsBlocMapCubit;
ActiveChatCubit? get activeChatCubit;
ChatListCubit? get chatListCubit;
ActiveConversationsBlocMapCubit? get activeConversationsBlocMapCubit;
ActiveSingleContactChatBlocMapCubit? get activeSingleContactChatBlocMapCubit;
/// Create a copy of PerAccountCollectionState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$PerAccountCollectionStateCopyWith<PerAccountCollectionState> get copyWith =>
_$PerAccountCollectionStateCopyWithImpl<PerAccountCollectionState>(
this as PerAccountCollectionState, _$identity);
AccountInfo get accountInfo; AsyncValue<AccountRecordState>? get avAccountRecordState; AccountInfoCubit? get accountInfoCubit; AccountRecordCubit? get accountRecordCubit; ContactInvitationListCubit? get contactInvitationListCubit; ContactListCubit? get contactListCubit; WaitingInvitationsBlocMapCubit? get waitingInvitationsBlocMapCubit; ActiveChatCubit? get activeChatCubit; ChatListCubit? get chatListCubit; ActiveConversationsBlocMapCubit? get activeConversationsBlocMapCubit; ActiveSingleContactChatBlocMapCubit? get activeSingleContactChatBlocMapCubit;
/// Create a copy of PerAccountCollectionState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$PerAccountCollectionStateCopyWith<PerAccountCollectionState> get copyWith => _$PerAccountCollectionStateCopyWithImpl<PerAccountCollectionState>(this as PerAccountCollectionState, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is PerAccountCollectionState &&
(identical(other.accountInfo, accountInfo) ||
other.accountInfo == accountInfo) &&
(identical(other.avAccountRecordState, avAccountRecordState) ||
other.avAccountRecordState == avAccountRecordState) &&
(identical(other.accountInfoCubit, accountInfoCubit) ||
other.accountInfoCubit == accountInfoCubit) &&
(identical(other.accountRecordCubit, accountRecordCubit) ||
other.accountRecordCubit == accountRecordCubit) &&
(identical(other.contactInvitationListCubit,
contactInvitationListCubit) ||
other.contactInvitationListCubit ==
contactInvitationListCubit) &&
(identical(other.contactListCubit, contactListCubit) ||
other.contactListCubit == contactListCubit) &&
(identical(other.waitingInvitationsBlocMapCubit,
waitingInvitationsBlocMapCubit) ||
other.waitingInvitationsBlocMapCubit ==
waitingInvitationsBlocMapCubit) &&
(identical(other.activeChatCubit, activeChatCubit) ||
other.activeChatCubit == activeChatCubit) &&
(identical(other.chatListCubit, chatListCubit) ||
other.chatListCubit == chatListCubit) &&
(identical(other.activeConversationsBlocMapCubit,
activeConversationsBlocMapCubit) ||
other.activeConversationsBlocMapCubit ==
activeConversationsBlocMapCubit) &&
(identical(other.activeSingleContactChatBlocMapCubit,
activeSingleContactChatBlocMapCubit) ||
other.activeSingleContactChatBlocMapCubit ==
activeSingleContactChatBlocMapCubit));
}
@override
int get hashCode => Object.hash(
runtimeType,
accountInfo,
avAccountRecordState,
accountInfoCubit,
accountRecordCubit,
contactInvitationListCubit,
contactListCubit,
waitingInvitationsBlocMapCubit,
activeChatCubit,
chatListCubit,
activeConversationsBlocMapCubit,
activeSingleContactChatBlocMapCubit);
@override
String toString() {
return 'PerAccountCollectionState(accountInfo: $accountInfo, avAccountRecordState: $avAccountRecordState, accountInfoCubit: $accountInfoCubit, accountRecordCubit: $accountRecordCubit, contactInvitationListCubit: $contactInvitationListCubit, contactListCubit: $contactListCubit, waitingInvitationsBlocMapCubit: $waitingInvitationsBlocMapCubit, activeChatCubit: $activeChatCubit, chatListCubit: $chatListCubit, activeConversationsBlocMapCubit: $activeConversationsBlocMapCubit, activeSingleContactChatBlocMapCubit: $activeSingleContactChatBlocMapCubit)';
}
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is PerAccountCollectionState&&(identical(other.accountInfo, accountInfo) || other.accountInfo == accountInfo)&&(identical(other.avAccountRecordState, avAccountRecordState) || other.avAccountRecordState == avAccountRecordState)&&(identical(other.accountInfoCubit, accountInfoCubit) || other.accountInfoCubit == accountInfoCubit)&&(identical(other.accountRecordCubit, accountRecordCubit) || other.accountRecordCubit == accountRecordCubit)&&(identical(other.contactInvitationListCubit, contactInvitationListCubit) || other.contactInvitationListCubit == contactInvitationListCubit)&&(identical(other.contactListCubit, contactListCubit) || other.contactListCubit == contactListCubit)&&(identical(other.waitingInvitationsBlocMapCubit, waitingInvitationsBlocMapCubit) || other.waitingInvitationsBlocMapCubit == waitingInvitationsBlocMapCubit)&&(identical(other.activeChatCubit, activeChatCubit) || other.activeChatCubit == activeChatCubit)&&(identical(other.chatListCubit, chatListCubit) || other.chatListCubit == chatListCubit)&&(identical(other.activeConversationsBlocMapCubit, activeConversationsBlocMapCubit) || other.activeConversationsBlocMapCubit == activeConversationsBlocMapCubit)&&(identical(other.activeSingleContactChatBlocMapCubit, activeSingleContactChatBlocMapCubit) || other.activeSingleContactChatBlocMapCubit == activeSingleContactChatBlocMapCubit));
}
@override
int get hashCode => Object.hash(runtimeType,accountInfo,avAccountRecordState,accountInfoCubit,accountRecordCubit,contactInvitationListCubit,contactListCubit,waitingInvitationsBlocMapCubit,activeChatCubit,chatListCubit,activeConversationsBlocMapCubit,activeSingleContactChatBlocMapCubit);
@override
String toString() {
return 'PerAccountCollectionState(accountInfo: $accountInfo, avAccountRecordState: $avAccountRecordState, accountInfoCubit: $accountInfoCubit, accountRecordCubit: $accountRecordCubit, contactInvitationListCubit: $contactInvitationListCubit, contactListCubit: $contactListCubit, waitingInvitationsBlocMapCubit: $waitingInvitationsBlocMapCubit, activeChatCubit: $activeChatCubit, chatListCubit: $chatListCubit, activeConversationsBlocMapCubit: $activeConversationsBlocMapCubit, activeSingleContactChatBlocMapCubit: $activeSingleContactChatBlocMapCubit)';
}
}
/// @nodoc
abstract mixin class $PerAccountCollectionStateCopyWith<$Res> {
factory $PerAccountCollectionStateCopyWith(PerAccountCollectionState value,
$Res Function(PerAccountCollectionState) _then) =
_$PerAccountCollectionStateCopyWithImpl;
@useResult
$Res call(
{AccountInfo accountInfo,
AsyncValue<AccountRecordState>? avAccountRecordState,
AccountInfoCubit? accountInfoCubit,
AccountRecordCubit? accountRecordCubit,
ContactInvitationListCubit? contactInvitationListCubit,
ContactListCubit? contactListCubit,
WaitingInvitationsBlocMapCubit? waitingInvitationsBlocMapCubit,
ActiveChatCubit? activeChatCubit,
ChatListCubit? chatListCubit,
ActiveConversationsBlocMapCubit? activeConversationsBlocMapCubit,
ActiveSingleContactChatBlocMapCubit?
activeSingleContactChatBlocMapCubit});
abstract mixin class $PerAccountCollectionStateCopyWith<$Res> {
factory $PerAccountCollectionStateCopyWith(PerAccountCollectionState value, $Res Function(PerAccountCollectionState) _then) = _$PerAccountCollectionStateCopyWithImpl;
@useResult
$Res call({
AccountInfo accountInfo, AsyncValue<AccountRecordState>? avAccountRecordState, AccountInfoCubit? accountInfoCubit, AccountRecordCubit? accountRecordCubit, ContactInvitationListCubit? contactInvitationListCubit, ContactListCubit? contactListCubit, WaitingInvitationsBlocMapCubit? waitingInvitationsBlocMapCubit, ActiveChatCubit? activeChatCubit, ChatListCubit? chatListCubit, ActiveConversationsBlocMapCubit? activeConversationsBlocMapCubit, ActiveSingleContactChatBlocMapCubit? activeSingleContactChatBlocMapCubit
});
$AsyncValueCopyWith<Account, $Res>? get avAccountRecordState;
$AsyncValueCopyWith<Account, $Res>? get avAccountRecordState;
}
/// @nodoc
class _$PerAccountCollectionStateCopyWithImpl<$Res>
implements $PerAccountCollectionStateCopyWith<$Res> {
@ -124,224 +60,220 @@ class _$PerAccountCollectionStateCopyWithImpl<$Res>
final PerAccountCollectionState _self;
final $Res Function(PerAccountCollectionState) _then;
/// Create a copy of PerAccountCollectionState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? accountInfo = null,
Object? avAccountRecordState = freezed,
Object? accountInfoCubit = freezed,
Object? accountRecordCubit = freezed,
Object? contactInvitationListCubit = freezed,
Object? contactListCubit = freezed,
Object? waitingInvitationsBlocMapCubit = freezed,
Object? activeChatCubit = freezed,
Object? chatListCubit = freezed,
Object? activeConversationsBlocMapCubit = freezed,
Object? activeSingleContactChatBlocMapCubit = freezed,
}) {
return _then(_self.copyWith(
accountInfo: null == accountInfo
? _self.accountInfo
: accountInfo // ignore: cast_nullable_to_non_nullable
as AccountInfo,
avAccountRecordState: freezed == avAccountRecordState
? _self.avAccountRecordState
: avAccountRecordState // ignore: cast_nullable_to_non_nullable
as AsyncValue<AccountRecordState>?,
accountInfoCubit: freezed == accountInfoCubit
? _self.accountInfoCubit
: accountInfoCubit // ignore: cast_nullable_to_non_nullable
as AccountInfoCubit?,
accountRecordCubit: freezed == accountRecordCubit
? _self.accountRecordCubit
: accountRecordCubit // ignore: cast_nullable_to_non_nullable
as AccountRecordCubit?,
contactInvitationListCubit: freezed == contactInvitationListCubit
? _self.contactInvitationListCubit
: contactInvitationListCubit // ignore: cast_nullable_to_non_nullable
as ContactInvitationListCubit?,
contactListCubit: freezed == contactListCubit
? _self.contactListCubit
: contactListCubit // ignore: cast_nullable_to_non_nullable
as ContactListCubit?,
waitingInvitationsBlocMapCubit: freezed == waitingInvitationsBlocMapCubit
? _self.waitingInvitationsBlocMapCubit
: waitingInvitationsBlocMapCubit // ignore: cast_nullable_to_non_nullable
as WaitingInvitationsBlocMapCubit?,
activeChatCubit: freezed == activeChatCubit
? _self.activeChatCubit
: activeChatCubit // ignore: cast_nullable_to_non_nullable
as ActiveChatCubit?,
chatListCubit: freezed == chatListCubit
? _self.chatListCubit
: chatListCubit // ignore: cast_nullable_to_non_nullable
as ChatListCubit?,
activeConversationsBlocMapCubit: freezed ==
activeConversationsBlocMapCubit
? _self.activeConversationsBlocMapCubit
: activeConversationsBlocMapCubit // ignore: cast_nullable_to_non_nullable
as ActiveConversationsBlocMapCubit?,
activeSingleContactChatBlocMapCubit: freezed ==
activeSingleContactChatBlocMapCubit
? _self.activeSingleContactChatBlocMapCubit
: activeSingleContactChatBlocMapCubit // ignore: cast_nullable_to_non_nullable
as ActiveSingleContactChatBlocMapCubit?,
));
}
/// Create a copy of PerAccountCollectionState
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$AsyncValueCopyWith<Account, $Res>? get avAccountRecordState {
/// Create a copy of PerAccountCollectionState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? accountInfo = null,Object? avAccountRecordState = freezed,Object? accountInfoCubit = freezed,Object? accountRecordCubit = freezed,Object? contactInvitationListCubit = freezed,Object? contactListCubit = freezed,Object? waitingInvitationsBlocMapCubit = freezed,Object? activeChatCubit = freezed,Object? chatListCubit = freezed,Object? activeConversationsBlocMapCubit = freezed,Object? activeSingleContactChatBlocMapCubit = freezed,}) {
return _then(_self.copyWith(
accountInfo: null == accountInfo ? _self.accountInfo : accountInfo // ignore: cast_nullable_to_non_nullable
as AccountInfo,avAccountRecordState: freezed == avAccountRecordState ? _self.avAccountRecordState : avAccountRecordState // ignore: cast_nullable_to_non_nullable
as AsyncValue<AccountRecordState>?,accountInfoCubit: freezed == accountInfoCubit ? _self.accountInfoCubit : accountInfoCubit // ignore: cast_nullable_to_non_nullable
as AccountInfoCubit?,accountRecordCubit: freezed == accountRecordCubit ? _self.accountRecordCubit : accountRecordCubit // ignore: cast_nullable_to_non_nullable
as AccountRecordCubit?,contactInvitationListCubit: freezed == contactInvitationListCubit ? _self.contactInvitationListCubit : contactInvitationListCubit // ignore: cast_nullable_to_non_nullable
as ContactInvitationListCubit?,contactListCubit: freezed == contactListCubit ? _self.contactListCubit : contactListCubit // ignore: cast_nullable_to_non_nullable
as ContactListCubit?,waitingInvitationsBlocMapCubit: freezed == waitingInvitationsBlocMapCubit ? _self.waitingInvitationsBlocMapCubit : waitingInvitationsBlocMapCubit // ignore: cast_nullable_to_non_nullable
as WaitingInvitationsBlocMapCubit?,activeChatCubit: freezed == activeChatCubit ? _self.activeChatCubit : activeChatCubit // ignore: cast_nullable_to_non_nullable
as ActiveChatCubit?,chatListCubit: freezed == chatListCubit ? _self.chatListCubit : chatListCubit // ignore: cast_nullable_to_non_nullable
as ChatListCubit?,activeConversationsBlocMapCubit: freezed == activeConversationsBlocMapCubit ? _self.activeConversationsBlocMapCubit : activeConversationsBlocMapCubit // ignore: cast_nullable_to_non_nullable
as ActiveConversationsBlocMapCubit?,activeSingleContactChatBlocMapCubit: freezed == activeSingleContactChatBlocMapCubit ? _self.activeSingleContactChatBlocMapCubit : activeSingleContactChatBlocMapCubit // ignore: cast_nullable_to_non_nullable
as ActiveSingleContactChatBlocMapCubit?,
));
}
/// Create a copy of PerAccountCollectionState
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$AsyncValueCopyWith<Account, $Res>? get avAccountRecordState {
if (_self.avAccountRecordState == null) {
return null;
}
return $AsyncValueCopyWith<Account, $Res>(_self.avAccountRecordState!,
(value) {
return _then(_self.copyWith(avAccountRecordState: value));
});
return null;
}
return $AsyncValueCopyWith<Account, $Res>(_self.avAccountRecordState!, (value) {
return _then(_self.copyWith(avAccountRecordState: value));
});
}
}
/// Adds pattern-matching-related methods to [PerAccountCollectionState].
extension PerAccountCollectionStatePatterns on PerAccountCollectionState {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _PerAccountCollectionState value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _PerAccountCollectionState() when $default != null:
return $default(_that);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _PerAccountCollectionState value) $default,){
final _that = this;
switch (_that) {
case _PerAccountCollectionState():
return $default(_that);}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _PerAccountCollectionState value)? $default,){
final _that = this;
switch (_that) {
case _PerAccountCollectionState() when $default != null:
return $default(_that);case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( AccountInfo accountInfo, AsyncValue<AccountRecordState>? avAccountRecordState, AccountInfoCubit? accountInfoCubit, AccountRecordCubit? accountRecordCubit, ContactInvitationListCubit? contactInvitationListCubit, ContactListCubit? contactListCubit, WaitingInvitationsBlocMapCubit? waitingInvitationsBlocMapCubit, ActiveChatCubit? activeChatCubit, ChatListCubit? chatListCubit, ActiveConversationsBlocMapCubit? activeConversationsBlocMapCubit, ActiveSingleContactChatBlocMapCubit? activeSingleContactChatBlocMapCubit)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _PerAccountCollectionState() when $default != null:
return $default(_that.accountInfo,_that.avAccountRecordState,_that.accountInfoCubit,_that.accountRecordCubit,_that.contactInvitationListCubit,_that.contactListCubit,_that.waitingInvitationsBlocMapCubit,_that.activeChatCubit,_that.chatListCubit,_that.activeConversationsBlocMapCubit,_that.activeSingleContactChatBlocMapCubit);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( AccountInfo accountInfo, AsyncValue<AccountRecordState>? avAccountRecordState, AccountInfoCubit? accountInfoCubit, AccountRecordCubit? accountRecordCubit, ContactInvitationListCubit? contactInvitationListCubit, ContactListCubit? contactListCubit, WaitingInvitationsBlocMapCubit? waitingInvitationsBlocMapCubit, ActiveChatCubit? activeChatCubit, ChatListCubit? chatListCubit, ActiveConversationsBlocMapCubit? activeConversationsBlocMapCubit, ActiveSingleContactChatBlocMapCubit? activeSingleContactChatBlocMapCubit) $default,) {final _that = this;
switch (_that) {
case _PerAccountCollectionState():
return $default(_that.accountInfo,_that.avAccountRecordState,_that.accountInfoCubit,_that.accountRecordCubit,_that.contactInvitationListCubit,_that.contactListCubit,_that.waitingInvitationsBlocMapCubit,_that.activeChatCubit,_that.chatListCubit,_that.activeConversationsBlocMapCubit,_that.activeSingleContactChatBlocMapCubit);}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( AccountInfo accountInfo, AsyncValue<AccountRecordState>? avAccountRecordState, AccountInfoCubit? accountInfoCubit, AccountRecordCubit? accountRecordCubit, ContactInvitationListCubit? contactInvitationListCubit, ContactListCubit? contactListCubit, WaitingInvitationsBlocMapCubit? waitingInvitationsBlocMapCubit, ActiveChatCubit? activeChatCubit, ChatListCubit? chatListCubit, ActiveConversationsBlocMapCubit? activeConversationsBlocMapCubit, ActiveSingleContactChatBlocMapCubit? activeSingleContactChatBlocMapCubit)? $default,) {final _that = this;
switch (_that) {
case _PerAccountCollectionState() when $default != null:
return $default(_that.accountInfo,_that.avAccountRecordState,_that.accountInfoCubit,_that.accountRecordCubit,_that.contactInvitationListCubit,_that.contactListCubit,_that.waitingInvitationsBlocMapCubit,_that.activeChatCubit,_that.chatListCubit,_that.activeConversationsBlocMapCubit,_that.activeSingleContactChatBlocMapCubit);case _:
return null;
}
}
}
/// @nodoc
class _PerAccountCollectionState extends PerAccountCollectionState {
const _PerAccountCollectionState(
{required this.accountInfo,
required this.avAccountRecordState,
required this.accountInfoCubit,
required this.accountRecordCubit,
required this.contactInvitationListCubit,
required this.contactListCubit,
required this.waitingInvitationsBlocMapCubit,
required this.activeChatCubit,
required this.chatListCubit,
required this.activeConversationsBlocMapCubit,
required this.activeSingleContactChatBlocMapCubit})
: super._();
const _PerAccountCollectionState({required this.accountInfo, required this.avAccountRecordState, required this.accountInfoCubit, required this.accountRecordCubit, required this.contactInvitationListCubit, required this.contactListCubit, required this.waitingInvitationsBlocMapCubit, required this.activeChatCubit, required this.chatListCubit, required this.activeConversationsBlocMapCubit, required this.activeSingleContactChatBlocMapCubit}): super._();
@override
final AccountInfo accountInfo;
@override
final AsyncValue<AccountRecordState>? avAccountRecordState;
@override
final AccountInfoCubit? accountInfoCubit;
@override
final AccountRecordCubit? accountRecordCubit;
@override
final ContactInvitationListCubit? contactInvitationListCubit;
@override
final ContactListCubit? contactListCubit;
@override
final WaitingInvitationsBlocMapCubit? waitingInvitationsBlocMapCubit;
@override
final ActiveChatCubit? activeChatCubit;
@override
final ChatListCubit? chatListCubit;
@override
final ActiveConversationsBlocMapCubit? activeConversationsBlocMapCubit;
@override
final ActiveSingleContactChatBlocMapCubit?
activeSingleContactChatBlocMapCubit;
@override final AccountInfo accountInfo;
@override final AsyncValue<AccountRecordState>? avAccountRecordState;
@override final AccountInfoCubit? accountInfoCubit;
@override final AccountRecordCubit? accountRecordCubit;
@override final ContactInvitationListCubit? contactInvitationListCubit;
@override final ContactListCubit? contactListCubit;
@override final WaitingInvitationsBlocMapCubit? waitingInvitationsBlocMapCubit;
@override final ActiveChatCubit? activeChatCubit;
@override final ChatListCubit? chatListCubit;
@override final ActiveConversationsBlocMapCubit? activeConversationsBlocMapCubit;
@override final ActiveSingleContactChatBlocMapCubit? activeSingleContactChatBlocMapCubit;
/// Create a copy of PerAccountCollectionState
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$PerAccountCollectionStateCopyWith<_PerAccountCollectionState>
get copyWith =>
__$PerAccountCollectionStateCopyWithImpl<_PerAccountCollectionState>(
this, _$identity);
/// Create a copy of PerAccountCollectionState
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$PerAccountCollectionStateCopyWith<_PerAccountCollectionState> get copyWith => __$PerAccountCollectionStateCopyWithImpl<_PerAccountCollectionState>(this, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _PerAccountCollectionState &&
(identical(other.accountInfo, accountInfo) ||
other.accountInfo == accountInfo) &&
(identical(other.avAccountRecordState, avAccountRecordState) ||
other.avAccountRecordState == avAccountRecordState) &&
(identical(other.accountInfoCubit, accountInfoCubit) ||
other.accountInfoCubit == accountInfoCubit) &&
(identical(other.accountRecordCubit, accountRecordCubit) ||
other.accountRecordCubit == accountRecordCubit) &&
(identical(other.contactInvitationListCubit,
contactInvitationListCubit) ||
other.contactInvitationListCubit ==
contactInvitationListCubit) &&
(identical(other.contactListCubit, contactListCubit) ||
other.contactListCubit == contactListCubit) &&
(identical(other.waitingInvitationsBlocMapCubit,
waitingInvitationsBlocMapCubit) ||
other.waitingInvitationsBlocMapCubit ==
waitingInvitationsBlocMapCubit) &&
(identical(other.activeChatCubit, activeChatCubit) ||
other.activeChatCubit == activeChatCubit) &&
(identical(other.chatListCubit, chatListCubit) ||
other.chatListCubit == chatListCubit) &&
(identical(other.activeConversationsBlocMapCubit,
activeConversationsBlocMapCubit) ||
other.activeConversationsBlocMapCubit ==
activeConversationsBlocMapCubit) &&
(identical(other.activeSingleContactChatBlocMapCubit,
activeSingleContactChatBlocMapCubit) ||
other.activeSingleContactChatBlocMapCubit ==
activeSingleContactChatBlocMapCubit));
}
@override
int get hashCode => Object.hash(
runtimeType,
accountInfo,
avAccountRecordState,
accountInfoCubit,
accountRecordCubit,
contactInvitationListCubit,
contactListCubit,
waitingInvitationsBlocMapCubit,
activeChatCubit,
chatListCubit,
activeConversationsBlocMapCubit,
activeSingleContactChatBlocMapCubit);
@override
String toString() {
return 'PerAccountCollectionState(accountInfo: $accountInfo, avAccountRecordState: $avAccountRecordState, accountInfoCubit: $accountInfoCubit, accountRecordCubit: $accountRecordCubit, contactInvitationListCubit: $contactInvitationListCubit, contactListCubit: $contactListCubit, waitingInvitationsBlocMapCubit: $waitingInvitationsBlocMapCubit, activeChatCubit: $activeChatCubit, chatListCubit: $chatListCubit, activeConversationsBlocMapCubit: $activeConversationsBlocMapCubit, activeSingleContactChatBlocMapCubit: $activeSingleContactChatBlocMapCubit)';
}
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _PerAccountCollectionState&&(identical(other.accountInfo, accountInfo) || other.accountInfo == accountInfo)&&(identical(other.avAccountRecordState, avAccountRecordState) || other.avAccountRecordState == avAccountRecordState)&&(identical(other.accountInfoCubit, accountInfoCubit) || other.accountInfoCubit == accountInfoCubit)&&(identical(other.accountRecordCubit, accountRecordCubit) || other.accountRecordCubit == accountRecordCubit)&&(identical(other.contactInvitationListCubit, contactInvitationListCubit) || other.contactInvitationListCubit == contactInvitationListCubit)&&(identical(other.contactListCubit, contactListCubit) || other.contactListCubit == contactListCubit)&&(identical(other.waitingInvitationsBlocMapCubit, waitingInvitationsBlocMapCubit) || other.waitingInvitationsBlocMapCubit == waitingInvitationsBlocMapCubit)&&(identical(other.activeChatCubit, activeChatCubit) || other.activeChatCubit == activeChatCubit)&&(identical(other.chatListCubit, chatListCubit) || other.chatListCubit == chatListCubit)&&(identical(other.activeConversationsBlocMapCubit, activeConversationsBlocMapCubit) || other.activeConversationsBlocMapCubit == activeConversationsBlocMapCubit)&&(identical(other.activeSingleContactChatBlocMapCubit, activeSingleContactChatBlocMapCubit) || other.activeSingleContactChatBlocMapCubit == activeSingleContactChatBlocMapCubit));
}
@override
int get hashCode => Object.hash(runtimeType,accountInfo,avAccountRecordState,accountInfoCubit,accountRecordCubit,contactInvitationListCubit,contactListCubit,waitingInvitationsBlocMapCubit,activeChatCubit,chatListCubit,activeConversationsBlocMapCubit,activeSingleContactChatBlocMapCubit);
@override
String toString() {
return 'PerAccountCollectionState(accountInfo: $accountInfo, avAccountRecordState: $avAccountRecordState, accountInfoCubit: $accountInfoCubit, accountRecordCubit: $accountRecordCubit, contactInvitationListCubit: $contactInvitationListCubit, contactListCubit: $contactListCubit, waitingInvitationsBlocMapCubit: $waitingInvitationsBlocMapCubit, activeChatCubit: $activeChatCubit, chatListCubit: $chatListCubit, activeConversationsBlocMapCubit: $activeConversationsBlocMapCubit, activeSingleContactChatBlocMapCubit: $activeSingleContactChatBlocMapCubit)';
}
}
/// @nodoc
abstract mixin class _$PerAccountCollectionStateCopyWith<$Res>
implements $PerAccountCollectionStateCopyWith<$Res> {
factory _$PerAccountCollectionStateCopyWith(_PerAccountCollectionState value,
$Res Function(_PerAccountCollectionState) _then) =
__$PerAccountCollectionStateCopyWithImpl;
@override
@useResult
$Res call(
{AccountInfo accountInfo,
AsyncValue<AccountRecordState>? avAccountRecordState,
AccountInfoCubit? accountInfoCubit,
AccountRecordCubit? accountRecordCubit,
ContactInvitationListCubit? contactInvitationListCubit,
ContactListCubit? contactListCubit,
WaitingInvitationsBlocMapCubit? waitingInvitationsBlocMapCubit,
ActiveChatCubit? activeChatCubit,
ChatListCubit? chatListCubit,
ActiveConversationsBlocMapCubit? activeConversationsBlocMapCubit,
ActiveSingleContactChatBlocMapCubit?
activeSingleContactChatBlocMapCubit});
abstract mixin class _$PerAccountCollectionStateCopyWith<$Res> implements $PerAccountCollectionStateCopyWith<$Res> {
factory _$PerAccountCollectionStateCopyWith(_PerAccountCollectionState value, $Res Function(_PerAccountCollectionState) _then) = __$PerAccountCollectionStateCopyWithImpl;
@override @useResult
$Res call({
AccountInfo accountInfo, AsyncValue<AccountRecordState>? avAccountRecordState, AccountInfoCubit? accountInfoCubit, AccountRecordCubit? accountRecordCubit, ContactInvitationListCubit? contactInvitationListCubit, ContactListCubit? contactListCubit, WaitingInvitationsBlocMapCubit? waitingInvitationsBlocMapCubit, ActiveChatCubit? activeChatCubit, ChatListCubit? chatListCubit, ActiveConversationsBlocMapCubit? activeConversationsBlocMapCubit, ActiveSingleContactChatBlocMapCubit? activeSingleContactChatBlocMapCubit
});
@override $AsyncValueCopyWith<Account, $Res>? get avAccountRecordState;
@override
$AsyncValueCopyWith<Account, $Res>? get avAccountRecordState;
}
/// @nodoc
class __$PerAccountCollectionStateCopyWithImpl<$Res>
implements _$PerAccountCollectionStateCopyWith<$Res> {
@ -350,87 +282,38 @@ class __$PerAccountCollectionStateCopyWithImpl<$Res>
final _PerAccountCollectionState _self;
final $Res Function(_PerAccountCollectionState) _then;
/// Create a copy of PerAccountCollectionState
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$Res call({
Object? accountInfo = null,
Object? avAccountRecordState = freezed,
Object? accountInfoCubit = freezed,
Object? accountRecordCubit = freezed,
Object? contactInvitationListCubit = freezed,
Object? contactListCubit = freezed,
Object? waitingInvitationsBlocMapCubit = freezed,
Object? activeChatCubit = freezed,
Object? chatListCubit = freezed,
Object? activeConversationsBlocMapCubit = freezed,
Object? activeSingleContactChatBlocMapCubit = freezed,
}) {
return _then(_PerAccountCollectionState(
accountInfo: null == accountInfo
? _self.accountInfo
: accountInfo // ignore: cast_nullable_to_non_nullable
as AccountInfo,
avAccountRecordState: freezed == avAccountRecordState
? _self.avAccountRecordState
: avAccountRecordState // ignore: cast_nullable_to_non_nullable
as AsyncValue<AccountRecordState>?,
accountInfoCubit: freezed == accountInfoCubit
? _self.accountInfoCubit
: accountInfoCubit // ignore: cast_nullable_to_non_nullable
as AccountInfoCubit?,
accountRecordCubit: freezed == accountRecordCubit
? _self.accountRecordCubit
: accountRecordCubit // ignore: cast_nullable_to_non_nullable
as AccountRecordCubit?,
contactInvitationListCubit: freezed == contactInvitationListCubit
? _self.contactInvitationListCubit
: contactInvitationListCubit // ignore: cast_nullable_to_non_nullable
as ContactInvitationListCubit?,
contactListCubit: freezed == contactListCubit
? _self.contactListCubit
: contactListCubit // ignore: cast_nullable_to_non_nullable
as ContactListCubit?,
waitingInvitationsBlocMapCubit: freezed == waitingInvitationsBlocMapCubit
? _self.waitingInvitationsBlocMapCubit
: waitingInvitationsBlocMapCubit // ignore: cast_nullable_to_non_nullable
as WaitingInvitationsBlocMapCubit?,
activeChatCubit: freezed == activeChatCubit
? _self.activeChatCubit
: activeChatCubit // ignore: cast_nullable_to_non_nullable
as ActiveChatCubit?,
chatListCubit: freezed == chatListCubit
? _self.chatListCubit
: chatListCubit // ignore: cast_nullable_to_non_nullable
as ChatListCubit?,
activeConversationsBlocMapCubit: freezed ==
activeConversationsBlocMapCubit
? _self.activeConversationsBlocMapCubit
: activeConversationsBlocMapCubit // ignore: cast_nullable_to_non_nullable
as ActiveConversationsBlocMapCubit?,
activeSingleContactChatBlocMapCubit: freezed ==
activeSingleContactChatBlocMapCubit
? _self.activeSingleContactChatBlocMapCubit
: activeSingleContactChatBlocMapCubit // ignore: cast_nullable_to_non_nullable
as ActiveSingleContactChatBlocMapCubit?,
));
}
/// Create a copy of PerAccountCollectionState
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? accountInfo = null,Object? avAccountRecordState = freezed,Object? accountInfoCubit = freezed,Object? accountRecordCubit = freezed,Object? contactInvitationListCubit = freezed,Object? contactListCubit = freezed,Object? waitingInvitationsBlocMapCubit = freezed,Object? activeChatCubit = freezed,Object? chatListCubit = freezed,Object? activeConversationsBlocMapCubit = freezed,Object? activeSingleContactChatBlocMapCubit = freezed,}) {
return _then(_PerAccountCollectionState(
accountInfo: null == accountInfo ? _self.accountInfo : accountInfo // ignore: cast_nullable_to_non_nullable
as AccountInfo,avAccountRecordState: freezed == avAccountRecordState ? _self.avAccountRecordState : avAccountRecordState // ignore: cast_nullable_to_non_nullable
as AsyncValue<AccountRecordState>?,accountInfoCubit: freezed == accountInfoCubit ? _self.accountInfoCubit : accountInfoCubit // ignore: cast_nullable_to_non_nullable
as AccountInfoCubit?,accountRecordCubit: freezed == accountRecordCubit ? _self.accountRecordCubit : accountRecordCubit // ignore: cast_nullable_to_non_nullable
as AccountRecordCubit?,contactInvitationListCubit: freezed == contactInvitationListCubit ? _self.contactInvitationListCubit : contactInvitationListCubit // ignore: cast_nullable_to_non_nullable
as ContactInvitationListCubit?,contactListCubit: freezed == contactListCubit ? _self.contactListCubit : contactListCubit // ignore: cast_nullable_to_non_nullable
as ContactListCubit?,waitingInvitationsBlocMapCubit: freezed == waitingInvitationsBlocMapCubit ? _self.waitingInvitationsBlocMapCubit : waitingInvitationsBlocMapCubit // ignore: cast_nullable_to_non_nullable
as WaitingInvitationsBlocMapCubit?,activeChatCubit: freezed == activeChatCubit ? _self.activeChatCubit : activeChatCubit // ignore: cast_nullable_to_non_nullable
as ActiveChatCubit?,chatListCubit: freezed == chatListCubit ? _self.chatListCubit : chatListCubit // ignore: cast_nullable_to_non_nullable
as ChatListCubit?,activeConversationsBlocMapCubit: freezed == activeConversationsBlocMapCubit ? _self.activeConversationsBlocMapCubit : activeConversationsBlocMapCubit // ignore: cast_nullable_to_non_nullable
as ActiveConversationsBlocMapCubit?,activeSingleContactChatBlocMapCubit: freezed == activeSingleContactChatBlocMapCubit ? _self.activeSingleContactChatBlocMapCubit : activeSingleContactChatBlocMapCubit // ignore: cast_nullable_to_non_nullable
as ActiveSingleContactChatBlocMapCubit?,
));
}
/// Create a copy of PerAccountCollectionState
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$AsyncValueCopyWith<Account, $Res>? get avAccountRecordState {
/// Create a copy of PerAccountCollectionState
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$AsyncValueCopyWith<Account, $Res>? get avAccountRecordState {
if (_self.avAccountRecordState == null) {
return null;
}
return $AsyncValueCopyWith<Account, $Res>(_self.avAccountRecordState!,
(value) {
return _then(_self.copyWith(avAccountRecordState: value));
});
return null;
}
return $AsyncValueCopyWith<Account, $Res>(_self.avAccountRecordState!, (value) {
return _then(_self.copyWith(avAccountRecordState: value));
});
}
}
// dart format on

View file

@ -1,6 +1,5 @@
// dart format width=80
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// coverage:ignore-file
// 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
@ -12,246 +11,297 @@ part of 'user_login.dart';
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$UserLogin {
// SuperIdentity record key for the user
// used to index the local accounts table
RecordKey
get superIdentityRecordKey; // The identity secret as unlocked from the local accounts table
SecretKey
get identitySecret; // The account record key, owner key and secret pulled from the identity
AccountRecordInfo
get accountRecordInfo; // The time this login was most recently used
Timestamp get lastActive;
/// Create a copy of UserLogin
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$UserLoginCopyWith<UserLogin> get copyWith =>
_$UserLoginCopyWithImpl<UserLogin>(this as UserLogin, _$identity);
RecordKey get superIdentityRecordKey;// The identity secret as unlocked from the local accounts table
SecretKey get identitySecret;// The account record key, owner key and secret pulled from the identity
AccountRecordInfo get accountRecordInfo;// The time this login was most recently used
Timestamp get lastActive;
/// Create a copy of UserLogin
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$UserLoginCopyWith<UserLogin> get copyWith => _$UserLoginCopyWithImpl<UserLogin>(this as UserLogin, _$identity);
/// Serializes this UserLogin to a JSON map.
Map<String, dynamic> toJson();
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is UserLogin &&
(identical(other.superIdentityRecordKey, superIdentityRecordKey) ||
other.superIdentityRecordKey == superIdentityRecordKey) &&
(identical(other.identitySecret, identitySecret) ||
other.identitySecret == identitySecret) &&
(identical(other.accountRecordInfo, accountRecordInfo) ||
other.accountRecordInfo == accountRecordInfo) &&
(identical(other.lastActive, lastActive) ||
other.lastActive == lastActive));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType, superIdentityRecordKey,
identitySecret, accountRecordInfo, lastActive);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is UserLogin&&(identical(other.superIdentityRecordKey, superIdentityRecordKey) || other.superIdentityRecordKey == superIdentityRecordKey)&&(identical(other.identitySecret, identitySecret) || other.identitySecret == identitySecret)&&(identical(other.accountRecordInfo, accountRecordInfo) || other.accountRecordInfo == accountRecordInfo)&&(identical(other.lastActive, lastActive) || other.lastActive == lastActive));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,superIdentityRecordKey,identitySecret,accountRecordInfo,lastActive);
@override
String toString() {
return 'UserLogin(superIdentityRecordKey: $superIdentityRecordKey, identitySecret: $identitySecret, accountRecordInfo: $accountRecordInfo, lastActive: $lastActive)';
}
@override
String toString() {
return 'UserLogin(superIdentityRecordKey: $superIdentityRecordKey, identitySecret: $identitySecret, accountRecordInfo: $accountRecordInfo, lastActive: $lastActive)';
}
}
/// @nodoc
abstract mixin class $UserLoginCopyWith<$Res> {
factory $UserLoginCopyWith(UserLogin value, $Res Function(UserLogin) _then) =
_$UserLoginCopyWithImpl;
@useResult
$Res call(
{RecordKey superIdentityRecordKey,
SecretKey identitySecret,
AccountRecordInfo accountRecordInfo,
Timestamp lastActive});
abstract mixin class $UserLoginCopyWith<$Res> {
factory $UserLoginCopyWith(UserLogin value, $Res Function(UserLogin) _then) = _$UserLoginCopyWithImpl;
@useResult
$Res call({
RecordKey superIdentityRecordKey, SecretKey identitySecret, AccountRecordInfo accountRecordInfo, Timestamp lastActive
});
$AccountRecordInfoCopyWith<$Res> get accountRecordInfo;
$AccountRecordInfoCopyWith<$Res> get accountRecordInfo;
}
/// @nodoc
class _$UserLoginCopyWithImpl<$Res> implements $UserLoginCopyWith<$Res> {
class _$UserLoginCopyWithImpl<$Res>
implements $UserLoginCopyWith<$Res> {
_$UserLoginCopyWithImpl(this._self, this._then);
final UserLogin _self;
final $Res Function(UserLogin) _then;
/// Create a copy of UserLogin
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? superIdentityRecordKey = null,
Object? identitySecret = null,
Object? accountRecordInfo = null,
Object? lastActive = null,
}) {
return _then(_self.copyWith(
superIdentityRecordKey: null == superIdentityRecordKey
? _self.superIdentityRecordKey
: superIdentityRecordKey // ignore: cast_nullable_to_non_nullable
as RecordKey,
identitySecret: null == identitySecret
? _self.identitySecret
: identitySecret // ignore: cast_nullable_to_non_nullable
as SecretKey,
accountRecordInfo: null == accountRecordInfo
? _self.accountRecordInfo
: accountRecordInfo // ignore: cast_nullable_to_non_nullable
as AccountRecordInfo,
lastActive: null == lastActive
? _self.lastActive
: lastActive // ignore: cast_nullable_to_non_nullable
as Timestamp,
));
}
/// Create a copy of UserLogin
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? superIdentityRecordKey = null,Object? identitySecret = null,Object? accountRecordInfo = null,Object? lastActive = null,}) {
return _then(_self.copyWith(
superIdentityRecordKey: null == superIdentityRecordKey ? _self.superIdentityRecordKey : superIdentityRecordKey // ignore: cast_nullable_to_non_nullable
as RecordKey,identitySecret: null == identitySecret ? _self.identitySecret : identitySecret // ignore: cast_nullable_to_non_nullable
as SecretKey,accountRecordInfo: null == accountRecordInfo ? _self.accountRecordInfo : accountRecordInfo // ignore: cast_nullable_to_non_nullable
as AccountRecordInfo,lastActive: null == lastActive ? _self.lastActive : lastActive // ignore: cast_nullable_to_non_nullable
as Timestamp,
));
}
/// Create a copy of UserLogin
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$AccountRecordInfoCopyWith<$Res> get accountRecordInfo {
return $AccountRecordInfoCopyWith<$Res>(_self.accountRecordInfo, (value) {
return _then(_self.copyWith(accountRecordInfo: value));
});
}
}
/// Adds pattern-matching-related methods to [UserLogin].
extension UserLoginPatterns on UserLogin {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _UserLogin value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _UserLogin() when $default != null:
return $default(_that);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _UserLogin value) $default,){
final _that = this;
switch (_that) {
case _UserLogin():
return $default(_that);}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _UserLogin value)? $default,){
final _that = this;
switch (_that) {
case _UserLogin() when $default != null:
return $default(_that);case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( RecordKey superIdentityRecordKey, SecretKey identitySecret, AccountRecordInfo accountRecordInfo, Timestamp lastActive)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _UserLogin() when $default != null:
return $default(_that.superIdentityRecordKey,_that.identitySecret,_that.accountRecordInfo,_that.lastActive);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( RecordKey superIdentityRecordKey, SecretKey identitySecret, AccountRecordInfo accountRecordInfo, Timestamp lastActive) $default,) {final _that = this;
switch (_that) {
case _UserLogin():
return $default(_that.superIdentityRecordKey,_that.identitySecret,_that.accountRecordInfo,_that.lastActive);}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( RecordKey superIdentityRecordKey, SecretKey identitySecret, AccountRecordInfo accountRecordInfo, Timestamp lastActive)? $default,) {final _that = this;
switch (_that) {
case _UserLogin() when $default != null:
return $default(_that.superIdentityRecordKey,_that.identitySecret,_that.accountRecordInfo,_that.lastActive);case _:
return null;
}
}
/// Create a copy of UserLogin
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$AccountRecordInfoCopyWith<$Res> get accountRecordInfo {
return $AccountRecordInfoCopyWith<$Res>(_self.accountRecordInfo, (value) {
return _then(_self.copyWith(accountRecordInfo: value));
});
}
}
/// @nodoc
@JsonSerializable()
class _UserLogin implements UserLogin {
const _UserLogin(
{required this.superIdentityRecordKey,
required this.identitySecret,
required this.accountRecordInfo,
required this.lastActive});
const _UserLogin({required this.superIdentityRecordKey, required this.identitySecret, required this.accountRecordInfo, required this.lastActive});
// SuperIdentity record key for the user
// used to index the local accounts table
@override
final RecordKey superIdentityRecordKey;
@override final RecordKey superIdentityRecordKey;
// The identity secret as unlocked from the local accounts table
@override
final SecretKey identitySecret;
@override final SecretKey identitySecret;
// The account record key, owner key and secret pulled from the identity
@override
final AccountRecordInfo accountRecordInfo;
@override final AccountRecordInfo accountRecordInfo;
// The time this login was most recently used
@override
final Timestamp lastActive;
@override final Timestamp lastActive;
/// Create a copy of UserLogin
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$UserLoginCopyWith<_UserLogin> get copyWith =>
__$UserLoginCopyWithImpl<_UserLogin>(this, _$identity);
/// Create a copy of UserLogin
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$UserLoginCopyWith<_UserLogin> get copyWith => __$UserLoginCopyWithImpl<_UserLogin>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$UserLoginToJson(
this,
);
}
@override
Map<String, dynamic> toJson() {
return _$UserLoginToJson(this, );
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _UserLogin &&
(identical(other.superIdentityRecordKey, superIdentityRecordKey) ||
other.superIdentityRecordKey == superIdentityRecordKey) &&
(identical(other.identitySecret, identitySecret) ||
other.identitySecret == identitySecret) &&
(identical(other.accountRecordInfo, accountRecordInfo) ||
other.accountRecordInfo == accountRecordInfo) &&
(identical(other.lastActive, lastActive) ||
other.lastActive == lastActive));
}
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _UserLogin&&(identical(other.superIdentityRecordKey, superIdentityRecordKey) || other.superIdentityRecordKey == superIdentityRecordKey)&&(identical(other.identitySecret, identitySecret) || other.identitySecret == identitySecret)&&(identical(other.accountRecordInfo, accountRecordInfo) || other.accountRecordInfo == accountRecordInfo)&&(identical(other.lastActive, lastActive) || other.lastActive == lastActive));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,superIdentityRecordKey,identitySecret,accountRecordInfo,lastActive);
@override
String toString() {
return 'UserLogin(superIdentityRecordKey: $superIdentityRecordKey, identitySecret: $identitySecret, accountRecordInfo: $accountRecordInfo, lastActive: $lastActive)';
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType, superIdentityRecordKey,
identitySecret, accountRecordInfo, lastActive);
@override
String toString() {
return 'UserLogin(superIdentityRecordKey: $superIdentityRecordKey, identitySecret: $identitySecret, accountRecordInfo: $accountRecordInfo, lastActive: $lastActive)';
}
}
/// @nodoc
abstract mixin class _$UserLoginCopyWith<$Res>
implements $UserLoginCopyWith<$Res> {
factory _$UserLoginCopyWith(
_UserLogin value, $Res Function(_UserLogin) _then) =
__$UserLoginCopyWithImpl;
@override
@useResult
$Res call(
{RecordKey superIdentityRecordKey,
SecretKey identitySecret,
AccountRecordInfo accountRecordInfo,
Timestamp lastActive});
abstract mixin class _$UserLoginCopyWith<$Res> implements $UserLoginCopyWith<$Res> {
factory _$UserLoginCopyWith(_UserLogin value, $Res Function(_UserLogin) _then) = __$UserLoginCopyWithImpl;
@override @useResult
$Res call({
RecordKey superIdentityRecordKey, SecretKey identitySecret, AccountRecordInfo accountRecordInfo, Timestamp lastActive
});
@override $AccountRecordInfoCopyWith<$Res> get accountRecordInfo;
@override
$AccountRecordInfoCopyWith<$Res> get accountRecordInfo;
}
/// @nodoc
class __$UserLoginCopyWithImpl<$Res> implements _$UserLoginCopyWith<$Res> {
class __$UserLoginCopyWithImpl<$Res>
implements _$UserLoginCopyWith<$Res> {
__$UserLoginCopyWithImpl(this._self, this._then);
final _UserLogin _self;
final $Res Function(_UserLogin) _then;
/// Create a copy of UserLogin
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$Res call({
Object? superIdentityRecordKey = null,
Object? identitySecret = null,
Object? accountRecordInfo = null,
Object? lastActive = null,
}) {
return _then(_UserLogin(
superIdentityRecordKey: null == superIdentityRecordKey
? _self.superIdentityRecordKey
: superIdentityRecordKey // ignore: cast_nullable_to_non_nullable
as RecordKey,
identitySecret: null == identitySecret
? _self.identitySecret
: identitySecret // ignore: cast_nullable_to_non_nullable
as SecretKey,
accountRecordInfo: null == accountRecordInfo
? _self.accountRecordInfo
: accountRecordInfo // ignore: cast_nullable_to_non_nullable
as AccountRecordInfo,
lastActive: null == lastActive
? _self.lastActive
: lastActive // ignore: cast_nullable_to_non_nullable
as Timestamp,
));
}
/// Create a copy of UserLogin
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? superIdentityRecordKey = null,Object? identitySecret = null,Object? accountRecordInfo = null,Object? lastActive = null,}) {
return _then(_UserLogin(
superIdentityRecordKey: null == superIdentityRecordKey ? _self.superIdentityRecordKey : superIdentityRecordKey // ignore: cast_nullable_to_non_nullable
as RecordKey,identitySecret: null == identitySecret ? _self.identitySecret : identitySecret // ignore: cast_nullable_to_non_nullable
as SecretKey,accountRecordInfo: null == accountRecordInfo ? _self.accountRecordInfo : accountRecordInfo // ignore: cast_nullable_to_non_nullable
as AccountRecordInfo,lastActive: null == lastActive ? _self.lastActive : lastActive // ignore: cast_nullable_to_non_nullable
as Timestamp,
));
}
/// Create a copy of UserLogin
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$AccountRecordInfoCopyWith<$Res> get accountRecordInfo {
return $AccountRecordInfoCopyWith<$Res>(_self.accountRecordInfo, (value) {
return _then(_self.copyWith(accountRecordInfo: value));
});
}
/// Create a copy of UserLogin
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$AccountRecordInfoCopyWith<$Res> get accountRecordInfo {
return $AccountRecordInfoCopyWith<$Res>(_self.accountRecordInfo, (value) {
return _then(_self.copyWith(accountRecordInfo: value));
});
}
}
// dart format on

View file

@ -7,13 +7,11 @@ part of 'user_login.dart';
// **************************************************************************
_UserLogin _$UserLoginFromJson(Map<String, dynamic> json) => _UserLogin(
superIdentityRecordKey:
RecordKey.fromJson(json['super_identity_record_key']),
identitySecret: Typed<BareSecretKey>.fromJson(json['identity_secret']),
accountRecordInfo:
AccountRecordInfo.fromJson(json['account_record_info']),
lastActive: Timestamp.fromJson(json['last_active']),
);
superIdentityRecordKey: RecordKey.fromJson(json['super_identity_record_key']),
identitySecret: Typed<BareSecretKey>.fromJson(json['identity_secret']),
accountRecordInfo: AccountRecordInfo.fromJson(json['account_record_info']),
lastActive: Timestamp.fromJson(json['last_active']),
);
Map<String, dynamic> _$UserLoginToJson(_UserLogin instance) =>
<String, dynamic>{

View file

@ -12,38 +12,55 @@ const veilidChatApplicationId = 'com.veilid.veilidchat';
enum AccountRepositoryChange { localAccounts, userLogins, activeLocalAccount }
class AccountRepository {
//////////////////////////////////////////////////////////////
/// Fields
static var instance = AccountRepository._();
final TableDBValue<IList<LocalAccount>> _localAccounts;
final TableDBValue<IList<UserLogin>> _userLogins;
final TableDBValue<RecordKey?> _activeLocalAccount;
final StreamController<AccountRepositoryChange> _streamController;
AccountRepository._()
: _localAccounts = _initLocalAccounts(),
_userLogins = _initUserLogins(),
_activeLocalAccount = _initActiveAccount(),
_streamController =
StreamController<AccountRepositoryChange>.broadcast();
: _localAccounts = _initLocalAccounts(),
_userLogins = _initUserLogins(),
_activeLocalAccount = _initActiveAccount(),
_streamController = StreamController<AccountRepositoryChange>.broadcast();
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()),
makeInitialValue: IList<LocalAccount>.empty);
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()),
makeInitialValue: IList<LocalAccount>.empty,
);
static TableDBValue<IList<UserLogin>> _initUserLogins() => TableDBValue(
tableName: 'local_account_manager',
tableKeyName: 'user_logins',
valueFromJson: (obj) => obj != null
? IList<UserLogin>.fromJson(obj, genericFromJson(UserLogin.fromJson))
: IList<UserLogin>(),
valueToJson: (val) => val?.toJson((la) => la.toJson()),
makeInitialValue: IList<UserLogin>.empty);
tableName: 'local_account_manager',
tableKeyName: 'user_logins',
valueFromJson: (obj) => obj != null
? IList<UserLogin>.fromJson(obj, genericFromJson(UserLogin.fromJson))
: IList<UserLogin>(),
valueToJson: (val) => val?.toJson((la) => la.toJson()),
makeInitialValue: IList<UserLogin>.empty,
);
static TableDBValue<RecordKey?> _initActiveAccount() => TableDBValue(
tableName: 'local_account_manager',
tableKeyName: 'active_local_account',
valueFromJson: (obj) => obj == null ? null : RecordKey.fromJson(obj),
valueToJson: (val) => val?.toJson(),
makeInitialValue: () => null);
tableName: 'local_account_manager',
tableKeyName: 'active_local_account',
valueFromJson: (obj) => obj == null ? null : RecordKey.fromJson(obj),
valueToJson: (val) => val?.toJson(),
makeInitialValue: () => null,
);
Future<void> init() async {
await _localAccounts.get();
@ -63,8 +80,11 @@ class AccountRepository {
Stream<AccountRepositoryChange> get stream => _streamController.stream;
IList<LocalAccount> getLocalAccounts() => _localAccounts.value;
RecordKey? getActiveLocalAccount() => _activeLocalAccount.value;
IList<UserLogin> getUserLogins() => _userLogins.value;
UserLogin? getActiveUserLogin() {
final activeLocalAccount = _activeLocalAccount.value;
return activeLocalAccount == null
@ -75,7 +95,8 @@ class AccountRepository {
LocalAccount? fetchLocalAccount(RecordKey accountSuperIdentityRecordKey) {
final localAccounts = _localAccounts.value;
final idx = localAccounts.indexWhere(
(e) => e.superIdentity.recordKey == accountSuperIdentityRecordKey);
(e) => e.superIdentity.recordKey == accountSuperIdentityRecordKey,
);
if (idx == -1) {
return null;
}
@ -84,8 +105,9 @@ class AccountRepository {
UserLogin? fetchUserLogin(RecordKey superIdentityRecordKey) {
final userLogins = _userLogins.value;
final idx = userLogins
.indexWhere((e) => e.superIdentityRecordKey == superIdentityRecordKey);
final idx = userLogins.indexWhere(
(e) => e.superIdentityRecordKey == superIdentityRecordKey,
);
if (idx == -1) {
return null;
}
@ -133,18 +155,23 @@ class AccountRepository {
/// with the identity instance, stores the account in the identity key and
/// then logs into that account with no password set at this time
Future<WritableSuperIdentity> createWithNewSuperIdentity(
AccountSpec accountSpec) async {
AccountSpec accountSpec,
) async {
log.debug('Creating super identity');
final wsi = await WritableSuperIdentity.create();
try {
final localAccount = await _newLocalAccount(
superIdentity: wsi.superIdentity,
identitySecret: wsi.identitySecret,
accountSpec: accountSpec);
superIdentity: wsi.superIdentity,
identitySecret: wsi.identitySecret,
accountSpec: accountSpec,
);
// Log in the new account by default with no pin
final ok = await login(
localAccount.superIdentity.recordKey, EncryptionKeyType.none, '');
localAccount.superIdentity.recordKey,
EncryptionKeyType.none,
'',
);
assert(ok, 'login with none should never fail');
return wsi;
@ -155,20 +182,25 @@ class AccountRepository {
}
Future<void> updateLocalAccount(
RecordKey superIdentityRecordKey, AccountSpec accountSpec) async {
RecordKey superIdentityRecordKey,
AccountSpec accountSpec,
) async {
final localAccounts = await _localAccounts.get();
final newLocalAccounts = localAccounts.replaceFirstWhere(
(x) => x.superIdentity.recordKey == superIdentityRecordKey,
(localAccount) => localAccount!.copyWith(name: accountSpec.name));
(x) => x.superIdentity.recordKey == superIdentityRecordKey,
(localAccount) => localAccount!.copyWith(name: accountSpec.name),
);
await _localAccounts.set(newLocalAccounts);
_streamController.add(AccountRepositoryChange.localAccounts);
}
/// Remove an account and wipe the messages for this account from this device
Future<bool> deleteLocalAccount(RecordKey superIdentityRecordKey,
OwnedDHTRecordPointer? accountRecord) async {
Future<bool> deleteLocalAccount(
RecordKey superIdentityRecordKey,
OwnedDHTRecordPointer? accountRecord,
) async {
// Delete the account record locally which causes a deep delete
// of all the contacts, invites, chats, and messages in the dht record
// pool
@ -180,7 +212,8 @@ class AccountRepository {
final localAccounts = await _localAccounts.get();
final newLocalAccounts = localAccounts.removeWhere(
(la) => la.superIdentity.recordKey == superIdentityRecordKey);
(la) => la.superIdentity.recordKey == superIdentityRecordKey,
);
await _localAccounts.set(newLocalAccounts);
_streamController.add(AccountRepositoryChange.localAccounts);
@ -193,8 +226,10 @@ class AccountRepository {
/// Recover an account with the master identity secret
/// Delete an account from all devices
Future<bool> destroyAccount(RecordKey superIdentityRecordKey,
OwnedDHTRecordPointer accountRecord) async {
Future<bool> destroyAccount(
RecordKey superIdentityRecordKey,
OwnedDHTRecordPointer accountRecord,
) async {
// Get which local account we want to fetch the profile for
final localAccount = fetchLocalAccount(superIdentityRecordKey);
if (localAccount == null) {
@ -209,11 +244,12 @@ class AccountRepository {
final success = await localAccount.superIdentity.currentInstance
.removeAccount(
superRecordKey: localAccount.superIdentity.recordKey,
secretKey: userLogin.identitySecret,
applicationId: veilidChatApplicationId,
removeAccountCallback: (accountRecordInfos) async =>
accountRecordInfos.singleOrNull);
superRecordKey: localAccount.superIdentity.recordKey,
secretKey: userLogin.identitySecret,
applicationId: veilidChatApplicationId,
removeAccountCallback: (accountRecordInfos) async =>
accountRecordInfos.singleOrNull,
);
if (!success) {
return false;
}
@ -232,7 +268,8 @@ class AccountRepository {
if (superIdentityRecordKey != null) {
// Assert the specified record key can be found, will throw if not
final _ = _userLogins.value.firstWhere(
(ul) => ul.superIdentityRecordKey == superIdentityRecordKey);
(ul) => ul.superIdentityRecordKey == superIdentityRecordKey,
);
}
await _activeLocalAccount.set(superIdentityRecordKey);
_streamController.add(AccountRepositoryChange.activeLocalAccount);
@ -243,73 +280,74 @@ class AccountRepository {
/// Creates a new Account associated with the current instance of the identity
/// Adds a logged-out LocalAccount to track its existence on this device
Future<LocalAccount> _newLocalAccount(
{required SuperIdentity superIdentity,
required SecretKey identitySecret,
required AccountSpec accountSpec,
EncryptionKeyType encryptionKeyType = EncryptionKeyType.none,
String encryptionKey = ''}) async {
Future<LocalAccount> _newLocalAccount({
required SuperIdentity superIdentity,
required SecretKey identitySecret,
required AccountSpec accountSpec,
EncryptionKeyType encryptionKeyType = EncryptionKeyType.none,
String encryptionKey = '',
}) async {
log.debug('Creating new local account');
final localAccounts = await _localAccounts.get();
// Add account with profile to DHT
await superIdentity.currentInstance.addAccount(
superRecordKey: superIdentity.recordKey,
secretKey: identitySecret,
applicationId: veilidChatApplicationId,
createAccountCallback: (parent) async {
// Make empty contact list
log.debug('Creating contacts list');
final contactList = await (await DHTShortArray.create(
debugName: 'AccountRepository::_newLocalAccount::Contacts',
parent: parent))
.scope((r) async => r.recordPointer!);
superRecordKey: superIdentity.recordKey,
secretKey: identitySecret,
applicationId: veilidChatApplicationId,
createAccountCallback: (parent) async {
// Make empty contact list
log.debug('Creating contacts list');
final contactList = await (await DHTShortArray.create(
debugName: 'AccountRepository::_newLocalAccount::Contacts',
parent: parent,
)).scope((r) async => r.recordPointer!);
// Make empty contact invitation record list
log.debug('Creating contact invitation records list');
final contactInvitationRecords = await (await DHTShortArray.create(
debugName:
'AccountRepository::_newLocalAccount::ContactInvitations',
parent: parent))
.scope((r) async => r.recordPointer!);
// Make empty contact invitation record list
log.debug('Creating contact invitation records list');
final contactInvitationRecords = await (await DHTShortArray.create(
debugName: 'AccountRepository::_newLocalAccount::ContactInvitations',
parent: parent,
)).scope((r) async => r.recordPointer!);
// Make empty chat record list
log.debug('Creating chat records list');
final chatRecords = await (await DHTShortArray.create(
debugName: 'AccountRepository::_newLocalAccount::Chats',
parent: parent))
.scope((r) async => r.recordPointer!);
// Make empty chat record list
log.debug('Creating chat records list');
final chatRecords = await (await DHTShortArray.create(
debugName: 'AccountRepository::_newLocalAccount::Chats',
parent: parent,
)).scope((r) async => r.recordPointer!);
final groupChatRecords = await (await DHTShortArray.create(
debugName: 'AccountRepository::_newLocalAccount::GroupChats',
parent: parent))
.scope((r) async => r.recordPointer!);
final groupChatRecords = await (await DHTShortArray.create(
debugName: 'AccountRepository::_newLocalAccount::GroupChats',
parent: parent,
)).scope((r) async => r.recordPointer!);
// Make account object
final profile = proto.Profile()
..name = accountSpec.name
..pronouns = accountSpec.pronouns
..about = accountSpec.about
..status = accountSpec.status
..availability = accountSpec.availability
..timestamp = Veilid.instance.now().toInt64();
// Make account object
final profile = proto.Profile()
..name = accountSpec.name
..pronouns = accountSpec.pronouns
..about = accountSpec.about
..status = accountSpec.status
..availability = accountSpec.availability
..timestamp = Veilid.instance.now().toInt64();
final account = proto.Account()
..profile = profile
..invisible = accountSpec.invisible
..autoAwayTimeoutMin = accountSpec.autoAwayTimeout
..contactList = contactList.toProto()
..contactInvitationRecords = contactInvitationRecords.toProto()
..chatList = chatRecords.toProto()
..groupChatList = groupChatRecords.toProto()
..freeMessage = accountSpec.freeMessage
..awayMessage = accountSpec.awayMessage
..busyMessage = accountSpec.busyMessage
..autodetectAway = accountSpec.autoAway;
final account = proto.Account()
..profile = profile
..invisible = accountSpec.invisible
..autoAwayTimeoutMin = accountSpec.autoAwayTimeout
..contactList = contactList.toProto()
..contactInvitationRecords = contactInvitationRecords.toProto()
..chatList = chatRecords.toProto()
..groupChatList = groupChatRecords.toProto()
..freeMessage = accountSpec.freeMessage
..awayMessage = accountSpec.awayMessage
..busyMessage = accountSpec.busyMessage
..autodetectAway = accountSpec.autoAway;
return account.writeToBuffer();
});
return account.writeToBuffer();
},
);
// Encrypt identitySecret with key
final identitySecretBytes = await encryptionKeyType.encryptSecretToBytes(
@ -342,16 +380,19 @@ class AccountRepository {
}
Future<bool> _decryptedLogin(
SuperIdentity superIdentity, SecretKey identitySecret) async {
SuperIdentity superIdentity,
SecretKey identitySecret,
) async {
// Verify identity secret works and return the valid cryptosystem
await superIdentity.currentInstance.validateIdentitySecret(identitySecret);
// Read the identity key to get the account keys
final accountRecordInfoList = await superIdentity.currentInstance
.readAccount(
superRecordKey: superIdentity.recordKey,
secretKey: identitySecret,
applicationId: veilidChatApplicationId);
superRecordKey: superIdentity.recordKey,
secretKey: identitySecret,
applicationId: veilidChatApplicationId,
);
if (accountRecordInfoList.length > 1) {
throw IdentityException.limitExceeded;
} else if (accountRecordInfoList.isEmpty) {
@ -363,15 +404,17 @@ class AccountRepository {
final userLogins = await _userLogins.get();
final now = Veilid.instance.now();
final newUserLogins = userLogins.replaceFirstWhere(
(ul) => ul.superIdentityRecordKey == superIdentity.recordKey,
(ul) => ul != null
? ul.copyWith(lastActive: now)
: UserLogin(
superIdentityRecordKey: superIdentity.recordKey,
identitySecret: identitySecret,
accountRecordInfo: accountRecordInfo,
lastActive: now),
addIfNotFound: true);
(ul) => ul.superIdentityRecordKey == superIdentity.recordKey,
(ul) => ul != null
? ul.copyWith(lastActive: now)
: UserLogin(
superIdentityRecordKey: superIdentity.recordKey,
identitySecret: identitySecret,
accountRecordInfo: accountRecordInfo,
lastActive: now,
),
addIfNotFound: true,
);
await _userLogins.set(newUserLogins);
await _activeLocalAccount.set(superIdentity.recordKey);
@ -383,13 +426,17 @@ class AccountRepository {
return true;
}
Future<bool> login(RecordKey accountSuperRecordKey,
EncryptionKeyType encryptionKeyType, String encryptionKey) async {
Future<bool> login(
RecordKey accountSuperRecordKey,
EncryptionKeyType encryptionKeyType,
String encryptionKey,
) async {
final localAccounts = await _localAccounts.get();
// Get account, throws if not found
final localAccount = localAccounts.firstWhere(
(la) => la.superIdentity.recordKey == accountSuperRecordKey);
(la) => la.superIdentity.recordKey == accountSuperRecordKey,
);
// Log in with this local account
@ -398,12 +445,12 @@ class AccountRepository {
throw Exception('Wrong authentication type');
}
final identitySecret =
await localAccount.encryptionKeyType.decryptSecretFromBytes(
secretBytes: localAccount.identitySecretBytes,
cryptoKind: localAccount.superIdentity.currentInstance.recordKey.kind,
encryptionKey: encryptionKey,
);
final identitySecret = await localAccount.encryptionKeyType
.decryptSecretFromBytes(
secretBytes: localAccount.identitySecretBytes,
cryptoKind: localAccount.superIdentity.currentInstance.recordKey.kind,
encryptionKey: encryptionKey,
);
// Validate this secret with the identity public key and log in
return _decryptedLogin(localAccount.superIdentity, identitySecret);
@ -420,7 +467,8 @@ class AccountRepository {
if (logoutUser == activeLocalAccount) {
await switchToAccount(
_localAccounts.value.firstOrNull?.superIdentity.recordKey);
_localAccounts.value.firstOrNull?.superIdentity.recordKey,
);
}
final logoutUserLogin = fetchUserLogin(logoutUser);
@ -430,19 +478,10 @@ class AccountRepository {
}
// Remove user from active logins list
final newUserLogins = (await _userLogins.get())
.removeWhere((ul) => ul.superIdentityRecordKey == logoutUser);
final newUserLogins = (await _userLogins.get()).removeWhere(
(ul) => ul.superIdentityRecordKey == logoutUser,
);
await _userLogins.set(newUserLogins);
_streamController.add(AccountRepositoryChange.userLogins);
}
//////////////////////////////////////////////////////////////
/// Fields
static AccountRepository instance = AccountRepository._();
final TableDBValue<IList<LocalAccount>> _localAccounts;
final TableDBValue<IList<UserLogin>> _userLogins;
final TableDBValue<RecordKey?> _activeLocalAccount;
final StreamController<AccountRepositoryChange> _streamController;
}

View file

@ -20,45 +20,64 @@ import 'edit_profile_form.dart';
const _kDoBackArrow = 'doBackArrow';
class EditAccountPage extends StatefulWidget {
const EditAccountPage(
{required this.superIdentityRecordKey,
required this.initialValue,
required this.accountRecord,
super.key});
final RecordKey superIdentityRecordKey;
final AccountSpec initialValue;
final OwnedDHTRecordPointer accountRecord;
const EditAccountPage({
required this.superIdentityRecordKey,
required this.initialValue,
required this.accountRecord,
super.key,
});
@override
State createState() => _EditAccountPageState();
final RecordKey superIdentityRecordKey;
final AccountSpec initialValue;
final OwnedDHTRecordPointer accountRecord;
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties
..add(DiagnosticsProperty<RecordKey>(
'superIdentityRecordKey', superIdentityRecordKey))
..add(
DiagnosticsProperty<RecordKey>(
'superIdentityRecordKey',
superIdentityRecordKey,
),
)
..add(DiagnosticsProperty<AccountSpec>('initialValue', initialValue))
..add(DiagnosticsProperty<OwnedDHTRecordPointer>(
'accountRecord', accountRecord));
..add(
DiagnosticsProperty<OwnedDHTRecordPointer>(
'accountRecord',
accountRecord,
),
);
}
}
class _EditAccountPageState extends WindowSetupState<EditAccountPage> {
////////////////////////////////////////////////////////////////////////////
var _isInAsyncCall = false;
var _isModified = false;
_EditAccountPageState()
: super(
titleBarStyle: TitleBarStyle.normal,
orientationCapability: OrientationCapability.portraitOnly);
: super(
titleBarStyle: TitleBarStyle.normal,
orientationCapability: OrientationCapability.portraitOnly,
);
EditProfileForm _editAccountForm(BuildContext context) => EditProfileForm(
header: translate('edit_account_page.header'),
instructions: translate('edit_account_page.instructions'),
submitText: translate('button.update'),
submitDisabledText: translate('button.waiting_for_network'),
onSubmit: _onSubmit,
onModifiedState: _onModifiedState,
initialValue: widget.initialValue,
);
header: translate('edit_account_page.header'),
instructions: translate('edit_account_page.instructions'),
submitText: translate('button.update'),
submitDisabledText: translate('button.waiting_for_network'),
onSubmit: _onSubmit,
onModifiedState: _onModifiedState,
initialValue: widget.initialValue,
);
Future<void> _onRemoveAccount() async {
// dismiss the keyboard by unfocusing the textfield
@ -69,36 +88,62 @@ class _EditAccountPageState extends WindowSetupState<EditAccountPage> {
}
final confirmed = await StyledDialog.show<bool>(
context: context,
title: translate('edit_account_page.remove_account_confirm'),
child: Column(mainAxisSize: MainAxisSize.min, children: [
Text(translate('edit_account_page.remove_account_confirm_message'))
.paddingLTRB(24.scaled(context), 24.scaled(context),
24.scaled(context), 0),
Text(translate('confirmation.are_you_sure'))
.paddingAll(8.scaled(context)),
Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [
ElevatedButton(
context: context,
title: translate('edit_account_page.remove_account_confirm'),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
translate('edit_account_page.remove_account_confirm_message'),
).paddingLTRB(
24.scaled(context),
24.scaled(context),
24.scaled(context),
0,
),
Text(
translate('confirmation.are_you_sure'),
).paddingAll(8.scaled(context)),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton(
onPressed: () {
Navigator.of(context).pop(false);
},
child: Row(mainAxisSize: MainAxisSize.min, children: [
Icon(Icons.cancel, size: 16.scaled(context))
.paddingLTRB(0, 0, 4.scaled(context), 0),
Text(translate('button.no')).paddingLTRB(0, 0, 4, 0)
])),
ElevatedButton(
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.cancel,
size: 16.scaled(context),
).paddingLTRB(0, 0, 4.scaled(context), 0),
Text(translate('button.no')).paddingLTRB(0, 0, 4, 0),
],
),
),
ElevatedButton(
onPressed: () {
Navigator.of(context).pop(true);
},
child: Row(mainAxisSize: MainAxisSize.min, children: [
Icon(Icons.check, size: 16.scaled(context))
.paddingLTRB(0, 0, 4.scaled(context), 0),
Text(translate('button.yes'))
.paddingLTRB(0, 0, 4.scaled(context), 0)
]))
]).paddingAll(24.scaled(context))
]));
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.check,
size: 16.scaled(context),
).paddingLTRB(0, 0, 4.scaled(context), 0),
Text(
translate('button.yes'),
).paddingLTRB(0, 0, 4.scaled(context), 0),
],
),
),
],
).paddingAll(24.scaled(context)),
],
),
);
if (confirmed != null && confirmed) {
try {
setState(() {
@ -106,17 +151,20 @@ class _EditAccountPageState extends WindowSetupState<EditAccountPage> {
});
try {
final success = await AccountRepository.instance.deleteLocalAccount(
widget.superIdentityRecordKey, widget.accountRecord);
widget.superIdentityRecordKey,
widget.accountRecord,
);
if (mounted) {
if (success) {
context
.read<NotificationsCubit>()
.info(text: translate('edit_account_page.account_removed'));
context.read<NotificationsCubit>().info(
text: translate('edit_account_page.account_removed'),
);
GoRouterHelper(context).pop();
} else {
context.read<NotificationsCubit>().error(
title: translate('edit_account_page.failed_to_remove_title'),
text: translate('edit_account_page.try_again_network'));
title: translate('edit_account_page.failed_to_remove_title'),
text: translate('edit_account_page.try_again_network'),
);
}
}
} finally {
@ -127,7 +175,10 @@ class _EditAccountPageState extends WindowSetupState<EditAccountPage> {
} on Exception catch (e, st) {
if (mounted) {
await showErrorStacktraceModal(
context: context, error: e, stackTrace: st);
context: context,
error: e,
stackTrace: st,
);
}
}
}
@ -142,41 +193,74 @@ class _EditAccountPageState extends WindowSetupState<EditAccountPage> {
}
final confirmed = await StyledDialog.show<bool>(
context: context,
title: translate('edit_account_page.destroy_account_confirm'),
child: Column(mainAxisSize: MainAxisSize.min, children: [
Text(translate('edit_account_page.destroy_account_confirm_message'))
.paddingLTRB(24.scaled(context), 24.scaled(context),
24.scaled(context), 0),
Text(translate(
'edit_account_page.destroy_account_confirm_message_details'))
.paddingLTRB(24.scaled(context), 24.scaled(context),
24.scaled(context), 0),
Text(translate('confirmation.are_you_sure'))
.paddingAll(24.scaled(context)),
Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [
ElevatedButton(
context: context,
title: translate('edit_account_page.destroy_account_confirm'),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
translate('edit_account_page.destroy_account_confirm_message'),
).paddingLTRB(
24.scaled(context),
24.scaled(context),
24.scaled(context),
0,
),
Text(
translate(
'edit_account_page.destroy_account_confirm_message_details',
),
).paddingLTRB(
24.scaled(context),
24.scaled(context),
24.scaled(context),
0,
),
Text(
translate('confirmation.are_you_sure'),
).paddingAll(24.scaled(context)),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton(
onPressed: () {
Navigator.of(context).pop(false);
},
child: Row(mainAxisSize: MainAxisSize.min, children: [
Icon(Icons.cancel, size: 16.scaled(context))
.paddingLTRB(0, 0, 4.scaled(context), 0),
Text(translate('button.no'))
.paddingLTRB(0, 0, 4.scaled(context), 0)
])),
ElevatedButton(
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.cancel,
size: 16.scaled(context),
).paddingLTRB(0, 0, 4.scaled(context), 0),
Text(
translate('button.no'),
).paddingLTRB(0, 0, 4.scaled(context), 0),
],
),
),
ElevatedButton(
onPressed: () {
Navigator.of(context).pop(true);
},
child: Row(mainAxisSize: MainAxisSize.min, children: [
Icon(Icons.check, size: 16.scaled(context))
.paddingLTRB(0, 0, 4.scaled(context), 0),
Text(translate('button.yes'))
.paddingLTRB(0, 0, 4.scaled(context), 0)
]))
]).paddingAll(24.scaled(context))
]));
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.check,
size: 16.scaled(context),
).paddingLTRB(0, 0, 4.scaled(context), 0),
Text(
translate('button.yes'),
).paddingLTRB(0, 0, 4.scaled(context), 0),
],
),
),
],
).paddingAll(24.scaled(context)),
],
),
);
if (confirmed != null && confirmed) {
try {
setState(() {
@ -184,17 +268,20 @@ class _EditAccountPageState extends WindowSetupState<EditAccountPage> {
});
try {
final success = await AccountRepository.instance.destroyAccount(
widget.superIdentityRecordKey, widget.accountRecord);
widget.superIdentityRecordKey,
widget.accountRecord,
);
if (mounted) {
if (success) {
context
.read<NotificationsCubit>()
.info(text: translate('edit_account_page.account_destroyed'));
context.read<NotificationsCubit>().info(
text: translate('edit_account_page.account_destroyed'),
);
GoRouterHelper(context).pop();
} else {
context.read<NotificationsCubit>().error(
title: translate('edit_account_page.failed_to_destroy_title'),
text: translate('edit_account_page.try_again_network'));
title: translate('edit_account_page.failed_to_destroy_title'),
text: translate('edit_account_page.try_again_network'),
);
}
}
} finally {
@ -205,7 +292,10 @@ class _EditAccountPageState extends WindowSetupState<EditAccountPage> {
} on Exception catch (e, st) {
if (mounted) {
await showErrorStacktraceModal(
context: context, error: e, stackTrace: st);
context: context,
error: e,
stackTrace: st,
);
}
}
}
@ -224,8 +314,8 @@ class _EditAccountPageState extends WindowSetupState<EditAccountPage> {
});
try {
// Look up account cubit for this specific account
final perAccountCollectionBlocMapCubit =
context.read<PerAccountCollectionBlocMapCubit>();
final perAccountCollectionBlocMapCubit = context
.read<PerAccountCollectionBlocMapCubit>();
final accountRecordCubit = perAccountCollectionBlocMapCubit
.entry(widget.superIdentityRecordKey)
?.accountRecordCubit;
@ -237,8 +327,10 @@ class _EditAccountPageState extends WindowSetupState<EditAccountPage> {
// This triggers ConversationCubits to update
accountRecordCubit.updateAccount(accountSpec, () async {
// Update local account profile
await AccountRepository.instance
.updateLocalAccount(widget.superIdentityRecordKey, accountSpec);
await AccountRepository.instance.updateLocalAccount(
widget.superIdentityRecordKey,
accountSpec,
);
});
return true;
@ -250,7 +342,10 @@ class _EditAccountPageState extends WindowSetupState<EditAccountPage> {
} on Exception catch (e, st) {
if (mounted) {
await showErrorStacktraceModal(
context: context, error: e, stackTrace: st);
context: context,
error: e,
stackTrace: st,
);
}
}
return false;
@ -261,65 +356,67 @@ class _EditAccountPageState extends WindowSetupState<EditAccountPage> {
final displayModalHUD = _isInAsyncCall;
return StyledScaffold(
appBar: DefaultAppBar(
context: context,
title: Text(translate('edit_account_page.titlebar')),
leading: Navigator.canPop(context)
? IconButton(
icon: const Icon(Icons.arrow_back),
iconSize: 24.scaled(context),
onPressed: () {
singleFuture((this, _kDoBackArrow), () async {
if (_isModified) {
final ok = await showConfirmModal(
context: context,
title:
translate('confirmation.discard_changes'),
text: translate(
'confirmation.are_you_sure_discard'));
if (!ok) {
return;
}
}
if (context.mounted) {
Navigator.pop(context);
}
});
})
: null,
actions: [
const SignalStrengthMeterWidget(),
IconButton(
icon: const Icon(Icons.settings),
iconSize: 24.scaled(context),
tooltip: translate('menu.settings_tooltip'),
onPressed: () async {
await GoRouterHelper(context).push('/settings');
})
]),
body: SingleChildScrollView(
child: Column(children: [
_editAccountForm(context).paddingLTRB(0, 0, 0, 32),
StyledButtonBox(
instructions:
translate('edit_account_page.remove_account_description'),
buttonIcon: Icons.person_remove_alt_1,
buttonText: translate('edit_account_page.remove_account'),
onClick: _onRemoveAccount,
),
StyledButtonBox(
instructions:
translate('edit_account_page.destroy_account_description'),
buttonIcon: Icons.person_off,
buttonText: translate('edit_account_page.destroy_account'),
onClick: _onDestroyAccount,
appBar: DefaultAppBar(
context: context,
title: Text(translate('edit_account_page.titlebar')),
leading: Navigator.canPop(context)
? IconButton(
icon: const Icon(Icons.arrow_back),
iconSize: 24.scaled(context),
onPressed: () {
singleFuture((this, _kDoBackArrow), () async {
if (_isModified) {
final ok = await showConfirmModal(
context: context,
title: translate('confirmation.discard_changes'),
text: translate('confirmation.are_you_sure_discard'),
);
if (!ok) {
return;
}
}
if (context.mounted) {
Navigator.pop(context);
}
});
},
)
]).paddingSymmetric(horizontal: 24, vertical: 8)))
.withModalHUD(context, displayModalHUD);
: null,
actions: [
const SignalStrengthMeterWidget(),
IconButton(
icon: const Icon(Icons.settings),
iconSize: 24.scaled(context),
tooltip: translate('menu.settings_tooltip'),
onPressed: () async {
await GoRouterHelper(context).push('/settings');
},
),
],
),
body: SingleChildScrollView(
child: Column(
children: [
_editAccountForm(context).paddingLTRB(0, 0, 0, 32),
StyledButtonBox(
instructions: translate(
'edit_account_page.remove_account_description',
),
buttonIcon: Icons.person_remove_alt_1,
buttonText: translate('edit_account_page.remove_account'),
onClick: _onRemoveAccount,
),
StyledButtonBox(
instructions: translate(
'edit_account_page.destroy_account_description',
),
buttonIcon: Icons.person_off,
buttonText: translate('edit_account_page.destroy_account'),
onClick: _onDestroyAccount,
),
],
).paddingSymmetric(horizontal: 24, vertical: 8),
),
).withModalHUD(context, displayModalHUD);
}
////////////////////////////////////////////////////////////////////////////
var _isInAsyncCall = false;
var _isModified = false;
}

View file

@ -16,6 +16,40 @@ import '../models/models.dart';
const _kDoSubmitEditProfile = 'doSubmitEditProfile';
class EditProfileForm extends StatefulWidget {
final String header;
final String instructions;
final Future<bool> Function(AccountSpec) onSubmit;
final void Function(bool)? onModifiedState;
final String submitText;
final String submitDisabledText;
final AccountSpec initialValue;
static const formFieldName = 'name';
static const formFieldPronouns = 'pronouns';
static const formFieldAbout = 'about';
static const formFieldAvailability = 'availability';
static const formFieldFreeMessage = 'free_message';
static const formFieldAwayMessage = 'away_message';
static const formFieldBusyMessage = 'busy_message';
static const formFieldAvatar = 'avatar';
static const formFieldAutoAway = 'auto_away';
static const formFieldAutoAwayTimeout = 'auto_away_timeout';
const EditProfileForm({
required this.header,
required this.instructions,
@ -30,14 +64,6 @@ class EditProfileForm extends StatefulWidget {
@override
State createState() => _EditProfileFormState();
final String header;
final String instructions;
final Future<bool> Function(AccountSpec) onSubmit;
final void Function(bool)? onModifiedState;
final String submitText;
final String submitDisabledText;
final AccountSpec initialValue;
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
@ -46,23 +72,20 @@ class EditProfileForm extends StatefulWidget {
..add(StringProperty('instructions', instructions))
..add(StringProperty('submitText', submitText))
..add(StringProperty('submitDisabledText', submitDisabledText))
..add(ObjectFlagProperty<Future<bool> Function(AccountSpec)>.has(
'onSubmit', onSubmit))
..add(ObjectFlagProperty<void Function(bool p1)?>.has(
'onModifiedState', onModifiedState))
..add(
ObjectFlagProperty<Future<bool> Function(AccountSpec)>.has(
'onSubmit',
onSubmit,
),
)
..add(
ObjectFlagProperty<void Function(bool p1)?>.has(
'onModifiedState',
onModifiedState,
),
)
..add(DiagnosticsProperty<AccountSpec>('initialValue', initialValue));
}
static const formFieldName = 'name';
static const formFieldPronouns = 'pronouns';
static const formFieldAbout = 'about';
static const formFieldAvailability = 'availability';
static const formFieldFreeMessage = 'free_message';
static const formFieldAwayMessage = 'away_message';
static const formFieldBusyMessage = 'busy_message';
static const formFieldAvatar = 'avatar';
static const formFieldAutoAway = 'auto_away';
static const formFieldAutoAwayTimeout = 'auto_away_timeout';
}
class _EditProfileFormState extends State<EditProfileForm> {
@ -78,14 +101,15 @@ class _EditProfileFormState extends State<EditProfileForm> {
}
FormBuilderDropdown<proto.Availability> _availabilityDropDown(
BuildContext context) {
BuildContext context,
) {
final theme = Theme.of(context);
final scale = theme.extension<ScaleScheme>()!;
final initialValue =
_savedValue.availability == proto.Availability.AVAILABILITY_UNSPECIFIED
? proto.Availability.AVAILABILITY_FREE
: _savedValue.availability;
? proto.Availability.AVAILABILITY_FREE
: _savedValue.availability;
final availabilities = [
proto.Availability.AVAILABILITY_FREE,
@ -98,68 +122,102 @@ class _EditProfileFormState extends State<EditProfileForm> {
name: EditProfileForm.formFieldAvailability,
initialValue: initialValue,
decoration: InputDecoration(
contentPadding: const EdgeInsets.all(8).scaled(context),
floatingLabelBehavior: FloatingLabelBehavior.always,
labelText: translate('account.form_availability'),
hintText: translate('account.empty_busy_message')),
contentPadding: const EdgeInsets.all(8).scaled(context),
floatingLabelBehavior: FloatingLabelBehavior.always,
labelText: translate('account.form_availability'),
hintText: translate('account.empty_busy_message'),
),
items: availabilities
.map((availability) => DropdownMenuItem<proto.Availability>(
.map(
(availability) => DropdownMenuItem<proto.Availability>(
value: availability,
child: Row(mainAxisSize: MainAxisSize.min, children: [
AvailabilityWidget.availabilityIcon(
context, availability, scale.primaryScale.appText),
Text(availability == proto.Availability.AVAILABILITY_OFFLINE
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
AvailabilityWidget.availabilityIcon(
context,
availability,
scale.primaryScale.appText,
),
Text(
availability == proto.Availability.AVAILABILITY_OFFLINE
? translate('availability.always_show_offline')
: AvailabilityWidget.availabilityName(availability))
.paddingLTRB(8.scaled(context), 0, 0, 0),
])))
: AvailabilityWidget.availabilityName(availability),
).paddingLTRB(8.scaled(context), 0, 0, 0),
],
),
),
)
.toList(),
);
}
AccountSpec _makeAccountSpec() {
final name = _formKey
.currentState!.fields[EditProfileForm.formFieldName]!.value as String;
final pronouns = _formKey.currentState!
.fields[EditProfileForm.formFieldPronouns]!.value as String;
final about = _formKey
.currentState!.fields[EditProfileForm.formFieldAbout]!.value as String;
final availability = _formKey
.currentState!
.fields[EditProfileForm.formFieldAvailability]!
.value as proto.Availability;
final name =
_formKey.currentState!.fields[EditProfileForm.formFieldName]!.value
as String;
final pronouns =
_formKey.currentState!.fields[EditProfileForm.formFieldPronouns]!.value
as String;
final about =
_formKey.currentState!.fields[EditProfileForm.formFieldAbout]!.value
as String;
final availability =
_formKey
.currentState!
.fields[EditProfileForm.formFieldAvailability]!
.value
as proto.Availability;
final invisible = availability == proto.Availability.AVAILABILITY_OFFLINE;
final freeMessage = _formKey.currentState!
.fields[EditProfileForm.formFieldFreeMessage]!.value as String;
final awayMessage = _formKey.currentState!
.fields[EditProfileForm.formFieldAwayMessage]!.value as String;
final busyMessage = _formKey.currentState!
.fields[EditProfileForm.formFieldBusyMessage]!.value as String;
final freeMessage =
_formKey
.currentState!
.fields[EditProfileForm.formFieldFreeMessage]!
.value
as String;
final awayMessage =
_formKey
.currentState!
.fields[EditProfileForm.formFieldAwayMessage]!
.value
as String;
final busyMessage =
_formKey
.currentState!
.fields[EditProfileForm.formFieldBusyMessage]!
.value
as String;
const proto.DataReference? avatar = null;
// final avatar = _formKey.currentState!
// .fields[EditProfileForm.formFieldAvatar]!.value
//as proto.DataReference?;
final autoAway = _formKey
.currentState!.fields[EditProfileForm.formFieldAutoAway]!.value as bool;
final autoAwayTimeoutString = _formKey.currentState!
.fields[EditProfileForm.formFieldAutoAwayTimeout]!.value as String;
final autoAway =
_formKey.currentState!.fields[EditProfileForm.formFieldAutoAway]!.value
as bool;
final autoAwayTimeoutString =
_formKey
.currentState!
.fields[EditProfileForm.formFieldAutoAwayTimeout]!
.value
as String;
final autoAwayTimeout = int.parse(autoAwayTimeoutString);
return AccountSpec(
name: name,
pronouns: pronouns,
about: about,
availability: availability,
invisible: invisible,
freeMessage: freeMessage,
awayMessage: awayMessage,
busyMessage: busyMessage,
avatar: avatar,
autoAway: autoAway,
autoAwayTimeout: autoAwayTimeout);
name: name,
pronouns: pronouns,
about: about,
availability: availability,
invisible: invisible,
freeMessage: freeMessage,
awayMessage: awayMessage,
busyMessage: busyMessage,
avatar: avatar,
autoAway: autoAway,
autoAwayTimeout: autoAwayTimeout,
);
}
// Check if everything is the same and update state
@ -172,9 +230,7 @@ class _EditProfileFormState extends State<EditProfileForm> {
}
}
Widget _editProfileForm(
BuildContext context,
) {
Widget _editProfileForm(BuildContext context) {
final theme = Theme.of(context);
final textTheme = theme.textTheme;
@ -187,14 +243,16 @@ class _EditProfileFormState extends State<EditProfileForm> {
mainAxisAlignment: MainAxisAlignment.spaceBetween,
spacing: 8,
children: [
Row(children: [
const Spacer(),
StyledAvatar(
name: _currentValueName,
size: 128.scaled(context),
).paddingLTRB(0, 0, 0, 16),
const Spacer()
]),
Row(
children: [
const Spacer(),
StyledAvatar(
name: _currentValueName,
size: 128.scaled(context),
).paddingLTRB(0, 0, 0, 16),
const Spacer(),
],
),
FormBuilderTextField(
autofocus: true,
name: EditProfileForm.formFieldName,
@ -205,10 +263,11 @@ class _EditProfileFormState extends State<EditProfileForm> {
});
},
decoration: InputDecoration(
contentPadding: const EdgeInsets.all(8).scaled(context),
floatingLabelBehavior: FloatingLabelBehavior.always,
labelText: translate('account.form_name'),
hintText: translate('account.empty_name')),
contentPadding: const EdgeInsets.all(8).scaled(context),
floatingLabelBehavior: FloatingLabelBehavior.always,
labelText: translate('account.form_name'),
hintText: translate('account.empty_name'),
),
maxLength: 64,
// The validator receives the text that the user has entered.
validator: FormBuilderValidators.compose([
@ -221,10 +280,11 @@ class _EditProfileFormState extends State<EditProfileForm> {
initialValue: _savedValue.pronouns,
maxLength: 64,
decoration: InputDecoration(
contentPadding: const EdgeInsets.all(8).scaled(context),
floatingLabelBehavior: FloatingLabelBehavior.always,
labelText: translate('account.form_pronouns'),
hintText: translate('account.empty_pronouns')),
contentPadding: const EdgeInsets.all(8).scaled(context),
floatingLabelBehavior: FloatingLabelBehavior.always,
labelText: translate('account.form_pronouns'),
hintText: translate('account.empty_pronouns'),
),
textInputAction: TextInputAction.next,
),
FormBuilderTextField(
@ -234,10 +294,11 @@ class _EditProfileFormState extends State<EditProfileForm> {
maxLines: 8,
minLines: 1,
decoration: InputDecoration(
contentPadding: const EdgeInsets.all(8).scaled(context),
floatingLabelBehavior: FloatingLabelBehavior.always,
labelText: translate('account.form_about'),
hintText: translate('account.empty_about')),
contentPadding: const EdgeInsets.all(8).scaled(context),
floatingLabelBehavior: FloatingLabelBehavior.always,
labelText: translate('account.form_about'),
hintText: translate('account.empty_about'),
),
textInputAction: TextInputAction.newline,
),
_availabilityDropDown(context).paddingLTRB(0, 0, 0, 16),
@ -246,10 +307,11 @@ class _EditProfileFormState extends State<EditProfileForm> {
initialValue: _savedValue.freeMessage,
maxLength: 128,
decoration: InputDecoration(
contentPadding: const EdgeInsets.all(8).scaled(context),
floatingLabelBehavior: FloatingLabelBehavior.always,
labelText: translate('account.form_free_message'),
hintText: translate('account.empty_free_message')),
contentPadding: const EdgeInsets.all(8).scaled(context),
floatingLabelBehavior: FloatingLabelBehavior.always,
labelText: translate('account.form_free_message'),
hintText: translate('account.empty_free_message'),
),
textInputAction: TextInputAction.next,
),
FormBuilderTextField(
@ -257,10 +319,11 @@ class _EditProfileFormState extends State<EditProfileForm> {
initialValue: _savedValue.awayMessage,
maxLength: 128,
decoration: InputDecoration(
contentPadding: const EdgeInsets.all(8).scaled(context),
floatingLabelBehavior: FloatingLabelBehavior.always,
labelText: translate('account.form_away_message'),
hintText: translate('account.empty_away_message')),
contentPadding: const EdgeInsets.all(8).scaled(context),
floatingLabelBehavior: FloatingLabelBehavior.always,
labelText: translate('account.form_away_message'),
hintText: translate('account.empty_away_message'),
),
textInputAction: TextInputAction.next,
),
FormBuilderTextField(
@ -268,17 +331,20 @@ class _EditProfileFormState extends State<EditProfileForm> {
initialValue: _savedValue.busyMessage,
maxLength: 128,
decoration: InputDecoration(
contentPadding: const EdgeInsets.all(8).scaled(context),
floatingLabelBehavior: FloatingLabelBehavior.always,
labelText: translate('account.form_busy_message'),
hintText: translate('account.empty_busy_message')),
contentPadding: const EdgeInsets.all(8).scaled(context),
floatingLabelBehavior: FloatingLabelBehavior.always,
labelText: translate('account.form_busy_message'),
hintText: translate('account.empty_busy_message'),
),
textInputAction: TextInputAction.next,
),
FormBuilderCheckbox(
name: EditProfileForm.formFieldAutoAway,
initialValue: _savedValue.autoAway,
title: Text(translate('account.form_auto_away'),
style: textTheme.labelMedium),
title: Text(
translate('account.form_auto_away'),
style: textTheme.labelMedium,
),
onChanged: (v) {
setState(() {
_currentValueAutoAway = v ?? false;
@ -296,39 +362,52 @@ class _EditProfileFormState extends State<EditProfileForm> {
validator: FormBuilderValidators.positiveNumber(),
textInputAction: TextInputAction.next,
),
Row(children: [
const Spacer(),
Text(widget.instructions).toCenter().flexible(flex: 6),
const Spacer(),
]).paddingSymmetric(vertical: 16.scaled(context)),
Row(children: [
const Spacer(),
Builder(builder: (context) {
final networkReady = context
.watch<ConnectionStateCubit>()
.state
.asData
?.value
.isPublicInternetReady ??
false;
Row(
children: [
const Spacer(),
Text(widget.instructions).toCenter().flexible(flex: 6),
const Spacer(),
],
).paddingSymmetric(vertical: 16.scaled(context)),
Row(
children: [
const Spacer(),
Builder(
builder: (context) {
final networkReady =
context
.watch<ConnectionStateCubit>()
.state
.asData
?.value
.isPublicInternetReady ??
false;
return ElevatedButton(
onPressed: (networkReady && _isModified) ? _doSubmit : null,
child: Padding(
padding: EdgeInsetsGeometry.all(4.scaled(context)),
child: Row(mainAxisSize: MainAxisSize.min, children: [
Icon(networkReady ? Icons.check : Icons.hourglass_empty,
size: 16.scaled(context))
.paddingLTRB(0, 0, 4.scaled(context), 0),
Text(networkReady
? widget.submitText
: widget.submitDisabledText)
.paddingLTRB(0, 0, 4.scaled(context), 0)
]),
));
}),
const Spacer()
])
return ElevatedButton(
onPressed: (networkReady && _isModified) ? _doSubmit : null,
child: Padding(
padding: EdgeInsetsGeometry.all(4.scaled(context)),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
networkReady ? Icons.check : Icons.hourglass_empty,
size: 16.scaled(context),
).paddingLTRB(0, 0, 4.scaled(context), 0),
Text(
networkReady
? widget.submitText
: widget.submitDisabledText,
).paddingLTRB(0, 0, 4.scaled(context), 0),
],
),
),
);
},
),
const Spacer(),
],
),
],
),
);
@ -351,9 +430,7 @@ class _EditProfileFormState extends State<EditProfileForm> {
}
@override
Widget build(BuildContext context) => _editProfileForm(
context,
);
Widget build(BuildContext context) => _editProfileForm(context);
///////////////////////////////////////////////////////////////////////////
late AccountSpec _savedValue;

View file

@ -22,21 +22,24 @@ class NewAccountPage extends StatefulWidget {
}
class _NewAccountPageState extends WindowSetupState<NewAccountPage> {
_NewAccountPageState()
: super(
titleBarStyle: TitleBarStyle.normal,
orientationCapability: OrientationCapability.portraitOnly);
////////////////////////////////////////////////////////////////////////////
Widget _newAccountForm(
BuildContext context,
) =>
EditProfileForm(
header: translate('new_account_page.header'),
instructions: translate('new_account_page.instructions'),
submitText: translate('new_account_page.create'),
submitDisabledText: translate('button.waiting_for_network'),
initialValue: const AccountSpec.empty(),
onSubmit: _onSubmit);
var _isInAsyncCall = false;
_NewAccountPageState()
: super(
titleBarStyle: TitleBarStyle.normal,
orientationCapability: OrientationCapability.portraitOnly,
);
Widget _newAccountForm(BuildContext context) => EditProfileForm(
header: translate('new_account_page.header'),
instructions: translate('new_account_page.instructions'),
submitText: translate('new_account_page.create'),
submitDisabledText: translate('button.waiting_for_network'),
initialValue: const AccountSpec.empty(),
onSubmit: _onSubmit,
);
Future<bool> _onSubmit(AccountSpec accountSpec) async {
// dismiss the keyboard by unfocusing the textfield
@ -47,7 +50,8 @@ class _NewAccountPageState extends WindowSetupState<NewAccountPage> {
_isInAsyncCall = true;
});
try {
final networkReady = context
final networkReady =
context
.read<ConnectionStateCubit>()
.state
.asData
@ -58,18 +62,22 @@ class _NewAccountPageState extends WindowSetupState<NewAccountPage> {
final canSubmit = networkReady;
if (!canSubmit) {
context.read<NotificationsCubit>().error(
text: translate('new_account_page.network_is_offline'),
title: translate('new_account_page.error'));
text: translate('new_account_page.network_is_offline'),
title: translate('new_account_page.error'),
);
return false;
}
final isFirstAccount =
AccountRepository.instance.getLocalAccounts().isEmpty;
final isFirstAccount = AccountRepository.instance
.getLocalAccounts()
.isEmpty;
final writableSuperIdentity = await AccountRepository.instance
.createWithNewSuperIdentity(accountSpec);
GoRouterHelper(context).pushReplacement('/new_account/recovery_key',
extra: [writableSuperIdentity, accountSpec.name, isFirstAccount]);
GoRouterHelper(context).pushReplacement(
'/new_account/recovery_key',
extra: [writableSuperIdentity, accountSpec.name, isFirstAccount],
);
return true;
} finally {
@ -82,7 +90,10 @@ class _NewAccountPageState extends WindowSetupState<NewAccountPage> {
} on Exception catch (e, st) {
if (mounted) {
await showErrorStacktraceModal(
context: context, error: e, stackTrace: st);
context: context,
error: e,
stackTrace: st,
);
}
}
return false;
@ -94,39 +105,36 @@ class _NewAccountPageState extends WindowSetupState<NewAccountPage> {
return StyledScaffold(
appBar: DefaultAppBar(
context: context,
title: Text(translate('new_account_page.titlebar')),
leading: GoRouterHelper(context).canPop()
? IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () {
if (GoRouterHelper(context).canPop()) {
GoRouterHelper(context).pop();
} else {
GoRouterHelper(context).go('/');
}
},
)
: null,
actions: [
const SignalStrengthMeterWidget(),
IconButton(
icon: const Icon(Icons.settings),
iconSize: 24.scaled(context),
tooltip: translate('menu.settings_tooltip'),
onPressed: () async {
await GoRouterHelper(context).push('/settings');
})
]),
context: context,
title: Text(translate('new_account_page.titlebar')),
leading: GoRouterHelper(context).canPop()
? IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () {
if (GoRouterHelper(context).canPop()) {
GoRouterHelper(context).pop();
} else {
GoRouterHelper(context).go('/');
}
},
)
: null,
actions: [
const SignalStrengthMeterWidget(),
IconButton(
icon: const Icon(Icons.settings),
iconSize: 24.scaled(context),
tooltip: translate('menu.settings_tooltip'),
onPressed: () async {
await GoRouterHelper(context).push('/settings');
},
),
],
),
body: SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
child: _newAccountForm(
context,
)).paddingAll(2),
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
child: _newAccountForm(context),
).paddingAll(2),
).withModalHUD(context, displayModalHUD);
}
////////////////////////////////////////////////////////////////////////////
bool _isInAsyncCall = false;
}

View file

@ -5,12 +5,18 @@ import '../../proto/proto.dart' as proto;
import '../../theme/theme.dart';
class ProfileWidget extends StatelessWidget {
////////////////////////////////////////////////////////////////////////////
final proto.Profile _profile;
final String? _byline;
const ProfileWidget({
required proto.Profile profile,
String? byline,
super.key,
}) : _profile = profile,
_byline = byline;
}) : _profile = profile,
_byline = byline;
@override
Widget build(BuildContext context) {
@ -26,48 +32,49 @@ class ProfileWidget extends StatelessWidget {
? scale.primaryScale.elementBackground
: scale.primaryScale.border,
shape: RoundedRectangleBorder(
side: !scaleConfig.useVisualIndicators
? BorderSide.none
: BorderSide(
strokeAlign: BorderSide.strokeAlignCenter,
color: scaleConfig.preferBorders
? scale.primaryScale.border
: scale.primaryScale.borderText,
width: 2),
borderRadius: BorderRadius.all(
Radius.circular(8 * scaleConfig.borderRadiusScale))),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
spacing: 8.scaled(context),
children: [
Text(
_profile.name,
style: textTheme.titleMedium!.copyWith(
side: !scaleConfig.useVisualIndicators
? BorderSide.none
: BorderSide(
strokeAlign: BorderSide.strokeAlignCenter,
color: scaleConfig.preferBorders
? scale.primaryScale.border
: scale.primaryScale.borderText),
: scale.primaryScale.borderText,
width: 2,
),
borderRadius: BorderRadius.all(
Radius.circular(8 * scaleConfig.borderRadiusScale),
),
),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
spacing: 8.scaled(context),
children: [
Text(
_profile.name,
style: textTheme.titleMedium!.copyWith(
color: scaleConfig.preferBorders
? scale.primaryScale.border
: scale.primaryScale.borderText,
),
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.center,
maxLines: 1,
),
if (_byline != null)
Text(
_byline,
style: textTheme.bodySmall!.copyWith(
color: scaleConfig.preferBorders
? scale.primaryScale.border
: scale.primaryScale.primary,
),
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.center,
maxLines: 1,
),
if (_byline != null)
Text(
_byline,
style: textTheme.bodySmall!.copyWith(
color: scaleConfig.preferBorders
? scale.primaryScale.border
: scale.primaryScale.primary),
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.center,
maxLines: 1,
),
]).paddingAll(8.scaled(context)),
],
).paddingAll(8.scaled(context)),
);
}
////////////////////////////////////////////////////////////////////////////
final proto.Profile _profile;
final String? _byline;
}

View file

@ -22,31 +22,41 @@ import '../../tools/tools.dart';
import '../../veilid_processor/veilid_processor.dart';
class ShowRecoveryKeyPage extends StatefulWidget {
const ShowRecoveryKeyPage(
{required WritableSuperIdentity writableSuperIdentity,
required String name,
required bool isFirstAccount,
super.key})
: _writableSuperIdentity = writableSuperIdentity,
_name = name,
_isFirstAccount = isFirstAccount;
final WritableSuperIdentity _writableSuperIdentity;
final String _name;
final bool _isFirstAccount;
const ShowRecoveryKeyPage({
required WritableSuperIdentity writableSuperIdentity,
required String name,
required bool isFirstAccount,
super.key,
}) : _writableSuperIdentity = writableSuperIdentity,
_name = name,
_isFirstAccount = isFirstAccount;
@override
State<ShowRecoveryKeyPage> createState() => _ShowRecoveryKeyPageState();
final WritableSuperIdentity _writableSuperIdentity;
final String _name;
final bool _isFirstAccount;
}
class _ShowRecoveryKeyPageState extends WindowSetupState<ShowRecoveryKeyPage> {
var _codeHandled = false;
var _isInAsyncCall = false;
_ShowRecoveryKeyPageState()
: super(
titleBarStyle: TitleBarStyle.normal,
orientationCapability: OrientationCapability.portraitOnly);
: super(
titleBarStyle: TitleBarStyle.normal,
orientationCapability: OrientationCapability.portraitOnly,
);
Future<void> _shareRecoveryKey(
BuildContext context, Uint8List recoveryKey, String name) async {
BuildContext context,
Uint8List recoveryKey,
String name,
) async {
setState(() {
_isInAsyncCall = true;
});
@ -54,10 +64,11 @@ class _ShowRecoveryKeyPageState extends WindowSetupState<ShowRecoveryKeyPage> {
final screenshotController = ScreenshotController();
final bytes = await screenshotController.captureFromWidget(
Container(
color: Colors.white,
width: 400,
height: 400,
child: _recoveryKeyWidget(context, recoveryKey, name)),
color: Colors.white,
width: 400,
height: 400,
child: _recoveryKeyWidget(context, recoveryKey, name),
),
);
setState(() {
@ -78,79 +89,101 @@ class _ShowRecoveryKeyPageState extends WindowSetupState<ShowRecoveryKeyPage> {
}
static Future<void> _printRecoveryKey(
BuildContext context, Uint8List recoveryKey, String name) async {
BuildContext context,
Uint8List recoveryKey,
String name,
) async {
final wrapped = await WidgetWrapper.fromWidget(
context: context,
widget: SizedBox(
width: 400,
height: 400,
child: _recoveryKeyWidget(context, recoveryKey, name)),
constraints: const BoxConstraints(maxWidth: 400, maxHeight: 400),
pixelRatio: 3);
context: context,
widget: SizedBox(
width: 400,
height: 400,
child: _recoveryKeyWidget(context, recoveryKey, name),
),
constraints: const BoxConstraints(maxWidth: 400, maxHeight: 400),
pixelRatio: 3,
);
final doc = pw.Document()
..addPage(pw.Page(
..addPage(
pw.Page(
build: (context) =>
pw.Center(child: pw.Image(wrapped, width: 400)) // Center
)); // Page
pw.Center(child: pw.Image(wrapped, width: 400)), // Center
),
); // Page
await Printing.layoutPdf(onLayout: (format) async => doc.save());
}
static Widget _recoveryKeyWidget(
BuildContext context, Uint8List recoveryKey, String name) {
BuildContext context,
Uint8List recoveryKey,
String name,
) {
final theme = Theme.of(context);
final textTheme = theme.textTheme;
//final scaleConfig = theme.extension<ScaleConfig>()!;
return Column(mainAxisSize: MainAxisSize.min, children: [
Text(
style: textTheme.headlineSmall!.copyWith(
color: Colors.black,
fontWeight: FontWeight.bold,
),
translate('show_recovery_key_page.recovery_key'))
.paddingLTRB(16, 16, 16, 0),
FittedBox(
child: QrImageView.withQr(
size: 300,
qr: QrCode.fromUint8List(
data: recoveryKey,
errorCorrectLevel: QrErrorCorrectLevel.L)))
.paddingLTRB(16, 16, 16, 8)
.expanded(),
Text(
style: textTheme.labelMedium!.copyWith(
color: Colors.black,
fontWeight: FontWeight.bold,
),
name)
.paddingLTRB(16, 8, 16, 24),
]);
return Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
style: textTheme.headlineSmall!.copyWith(
color: Colors.black,
fontWeight: FontWeight.bold,
),
translate('show_recovery_key_page.recovery_key'),
).paddingLTRB(16, 16, 16, 0),
FittedBox(
child: QrImageView.withQr(
size: 300,
qr: QrCode.fromUint8List(
data: recoveryKey,
errorCorrectLevel: QrErrorCorrectLevel.L,
),
),
).paddingLTRB(16, 16, 16, 8).expanded(),
Text(
style: textTheme.labelMedium!.copyWith(
color: Colors.black,
fontWeight: FontWeight.bold,
),
name,
).paddingLTRB(16, 8, 16, 24),
],
);
}
static Widget _recoveryKeyDialog(
BuildContext context, Uint8List recoveryKey, String name) {
BuildContext context,
Uint8List recoveryKey,
String name,
) {
final theme = Theme.of(context);
//final textTheme = theme.textTheme;
final scaleConfig = theme.extension<ScaleConfig>()!;
final cardsize =
min<double>(MediaQuery.of(context).size.shortestSide - 48.0, 400);
final cardsize = min<double>(
MediaQuery.of(context).size.shortestSide - 48.0,
400,
);
return Dialog(
shape: RoundedRectangleBorder(
side: const BorderSide(width: 2),
borderRadius:
BorderRadius.circular(16 * scaleConfig.borderRadiusScale)),
backgroundColor: Colors.white,
child: ConstrainedBox(
constraints: BoxConstraints(
minWidth: cardsize,
maxWidth: cardsize,
minHeight: cardsize + 16,
maxHeight: cardsize + 16),
child: _recoveryKeyWidget(context, recoveryKey, name)));
shape: RoundedRectangleBorder(
side: const BorderSide(width: 2),
borderRadius: BorderRadius.circular(16 * scaleConfig.borderRadiusScale),
),
backgroundColor: Colors.white,
child: ConstrainedBox(
constraints: BoxConstraints(
minWidth: cardsize,
maxWidth: cardsize,
minHeight: cardsize + 16,
maxHeight: cardsize + 16,
),
child: _recoveryKeyWidget(context, recoveryKey, name),
),
);
}
@override
@ -163,108 +196,126 @@ class _ShowRecoveryKeyPageState extends WindowSetupState<ShowRecoveryKeyPage> {
final displayModalHUD = _isInAsyncCall;
return StyledScaffold(
appBar: DefaultAppBar(
context: context,
title: Text(translate('show_recovery_key_page.titlebar')),
actions: [
const SignalStrengthMeterWidget(),
IconButton(
icon: const Icon(Icons.settings),
tooltip: translate('menu.settings_tooltip'),
onPressed: () async {
await GoRouterHelper(context).push('/settings');
})
]),
body: SingleChildScrollView(
child: Column(children: [
Text(
style: theme.textTheme.headlineSmall,
textAlign: TextAlign.center,
translate('show_recovery_key_page.instructions'))
.paddingAll(24),
ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 400),
child: Text(
softWrap: true,
textAlign: TextAlign.center,
translate('show_recovery_key_page.instructions_details')))
.toCenter()
.paddingLTRB(24, 0, 24, 24),
Text(
textAlign: TextAlign.center,
translate('show_recovery_key_page.instructions_options'))
.paddingLTRB(12, 0, 12, 24),
StyledButtonBox(
instructions:
translate('show_recovery_key_page.instructions_print'),
appBar: DefaultAppBar(
context: context,
title: Text(translate('show_recovery_key_page.titlebar')),
actions: [
const SignalStrengthMeterWidget(),
IconButton(
icon: const Icon(Icons.settings),
tooltip: translate('menu.settings_tooltip'),
onPressed: () async {
await GoRouterHelper(context).push('/settings');
},
),
],
),
body: SingleChildScrollView(
child: Column(
children: [
Text(
style: theme.textTheme.headlineSmall,
textAlign: TextAlign.center,
translate('show_recovery_key_page.instructions'),
).paddingAll(24),
ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 400),
child: Text(
softWrap: true,
textAlign: TextAlign.center,
translate('show_recovery_key_page.instructions_details'),
),
).toCenter().paddingLTRB(24, 0, 24, 24),
Text(
textAlign: TextAlign.center,
translate('show_recovery_key_page.instructions_options'),
).paddingLTRB(12, 0, 12, 24),
StyledButtonBox(
instructions: translate(
'show_recovery_key_page.instructions_print',
),
buttonIcon: Icons.print,
buttonText: translate('show_recovery_key_page.print'),
onClick: () {
//
singleFuture(this, () async {
await _printRecoveryKey(context,
widget._writableSuperIdentity.recoveryKey, widget._name);
await _printRecoveryKey(
context,
widget._writableSuperIdentity.recoveryKey,
widget._name,
);
});
setState(() {
_codeHandled = true;
});
}),
StyledButtonBox(
instructions:
translate('show_recovery_key_page.instructions_view'),
},
),
StyledButtonBox(
instructions: translate(
'show_recovery_key_page.instructions_view',
),
buttonIcon: Icons.edit_document,
buttonText: translate('show_recovery_key_page.view'),
onClick: () {
//
singleFuture(this, () async {
await showDialog<void>(
context: context,
builder: (context) => _recoveryKeyDialog(
context,
widget._writableSuperIdentity.recoveryKey,
widget._name));
context: context,
builder: (context) => _recoveryKeyDialog(
context,
widget._writableSuperIdentity.recoveryKey,
widget._name,
),
);
});
setState(() {
_codeHandled = true;
});
}),
StyledButtonBox(
instructions:
translate('show_recovery_key_page.instructions_share'),
},
),
StyledButtonBox(
instructions: translate(
'show_recovery_key_page.instructions_share',
),
buttonIcon: Icons.ios_share,
buttonText: translate('show_recovery_key_page.share'),
onClick: () {
//
singleFuture(this, () async {
await _shareRecoveryKey(context,
widget._writableSuperIdentity.recoveryKey, widget._name);
await _shareRecoveryKey(
context,
widget._writableSuperIdentity.recoveryKey,
widget._name,
);
});
setState(() {
_codeHandled = true;
});
}),
Offstage(
},
),
Offstage(
offstage: !_codeHandled,
child: ElevatedButton(
onPressed: () {
if (context.mounted) {
if (widget._isFirstAccount) {
GoRouterHelper(context).go('/');
} else {
GoRouterHelper(context).canPop()
? GoRouterHelper(context).pop()
: GoRouterHelper(context).go('/');
}
}
},
child: Text(translate('button.finish')).paddingAll(8))
.paddingAll(12))
]))).withModalHUD(context, displayModalHUD);
onPressed: () {
if (context.mounted) {
if (widget._isFirstAccount) {
GoRouterHelper(context).go('/');
} else {
GoRouterHelper(context).canPop()
? GoRouterHelper(context).pop()
: GoRouterHelper(context).go('/');
}
}
},
child: Text(translate('button.finish')).paddingAll(8),
).paddingAll(12),
),
],
),
),
).withModalHUD(context, displayModalHUD);
}
bool _codeHandled = false;
bool _isInAsyncCall = false;
}

View file

@ -26,122 +26,136 @@ class ScrollBehaviorModified extends ScrollBehavior {
}
class VeilidChatApp extends StatelessWidget {
const VeilidChatApp({
required this.initialThemeData,
super.key,
});
static const name = 'VeilidChat';
final ThemeData initialThemeData;
const VeilidChatApp({required this.initialThemeData, super.key});
Widget appBuilder(
BuildContext context, LocalizationDelegate localizationDelegate) =>
ThemeProvider(
initTheme: initialThemeData,
builder: (context, theme) => LocalizationProvider(
state: LocalizationProvider.of(context).state,
child: MultiBlocProvider(
providers: [
BlocProvider<PreferencesCubit>(
create: (context) =>
PreferencesCubit(PreferencesRepository.instance),
),
BlocProvider<NotificationsCubit>(
create: (context) => NotificationsCubit(
const NotificationsState(queue: IList.empty()))),
BlocProvider<ConnectionStateCubit>(
create: (context) =>
ConnectionStateCubit(ProcessorRepository.instance)),
BlocProvider<RouterCubit>(
create: (context) => RouterCubit(AccountRepository.instance),
),
BlocProvider<LocalAccountsCubit>(
create: (context) =>
LocalAccountsCubit(AccountRepository.instance),
),
BlocProvider<UserLoginsCubit>(
create: (context) =>
UserLoginsCubit(AccountRepository.instance),
),
BlocProvider<ActiveLocalAccountCubit>(
create: (context) =>
ActiveLocalAccountCubit(AccountRepository.instance),
),
BlocProvider<PerAccountCollectionBlocMapCubit>(
create: (context) => PerAccountCollectionBlocMapCubit(
accountRepository: AccountRepository.instance,
locator: context.read)),
],
child: BackgroundTicker(child: Builder(builder: (context) {
final scale = theme.extension<ScaleScheme>()!;
final scaleConfig = theme.extension<ScaleConfig>()!;
BuildContext context,
LocalizationDelegate localizationDelegate,
) => ThemeProvider(
initTheme: initialThemeData,
builder: (context, theme) => LocalizationProvider(
state: LocalizationProvider.of(context).state,
child: MultiBlocProvider(
providers: [
BlocProvider<PreferencesCubit>(
create: (context) =>
PreferencesCubit(PreferencesRepository.instance),
),
BlocProvider<NotificationsCubit>(
create: (context) => NotificationsCubit(
const NotificationsState(queue: IList.empty()),
),
),
BlocProvider<ConnectionStateCubit>(
create: (context) =>
ConnectionStateCubit(ProcessorRepository.instance),
),
BlocProvider<RouterCubit>(
create: (context) => RouterCubit(AccountRepository.instance),
),
BlocProvider<LocalAccountsCubit>(
create: (context) => LocalAccountsCubit(AccountRepository.instance),
),
BlocProvider<UserLoginsCubit>(
create: (context) => UserLoginsCubit(AccountRepository.instance),
),
BlocProvider<ActiveLocalAccountCubit>(
create: (context) =>
ActiveLocalAccountCubit(AccountRepository.instance),
),
BlocProvider<PerAccountCollectionBlocMapCubit>(
create: (context) => PerAccountCollectionBlocMapCubit(
accountRepository: AccountRepository.instance,
locator: context.read,
),
),
],
child: BackgroundTicker(
child: Builder(
builder: (context) {
final scale = theme.extension<ScaleScheme>()!;
final scaleConfig = theme.extension<ScaleConfig>()!;
final gradient = LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: scaleConfig.preferBorders &&
theme.brightness == Brightness.light
? [
scale.grayScale.hoverElementBackground,
scale.grayScale.subtleBackground,
]
: [
scale.primaryScale.hoverElementBackground,
scale.primaryScale.subtleBackground,
]);
final gradient = LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors:
scaleConfig.preferBorders &&
theme.brightness == Brightness.light
? [
scale.grayScale.hoverElementBackground,
scale.grayScale.subtleBackground,
]
: [
scale.primaryScale.hoverElementBackground,
scale.primaryScale.subtleBackground,
],
);
final wallpaper = PreferencesRepository
.instance.value.themePreference
.wallpaper();
final wallpaper = PreferencesRepository
.instance
.value
.themePreference
.wallpaper();
return Stack(
fit: StackFit.expand,
alignment: Alignment.center,
children: [
wallpaper ??
DecoratedBox(
decoration: BoxDecoration(gradient: gradient)),
MaterialApp.router(
scrollBehavior: const ScrollBehaviorModified(),
debugShowCheckedModeBanner: false,
routerConfig: context.read<RouterCubit>().router(),
title: translate('app.title'),
theme: theme,
localizationsDelegates: [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
FormBuilderLocalizations.delegate,
localizationDelegate
],
supportedLocales: localizationDelegate.supportedLocales,
locale: localizationDelegate.currentLocale,
)
]);
})),
)),
);
return Stack(
fit: StackFit.expand,
alignment: Alignment.center,
children: [
wallpaper ??
DecoratedBox(
decoration: BoxDecoration(gradient: gradient),
),
MaterialApp.router(
scrollBehavior: const ScrollBehaviorModified(),
debugShowCheckedModeBanner: false,
routerConfig: context.read<RouterCubit>().router(),
title: translate('app.title'),
theme: theme,
localizationsDelegates: [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
FormBuilderLocalizations.delegate,
localizationDelegate,
],
supportedLocales: localizationDelegate.supportedLocales,
locale: localizationDelegate.currentLocale,
),
],
);
},
),
),
),
),
);
@override
Widget build(BuildContext context) => FutureProvider<VeilidChatGlobalInit?>(
initialData: null,
create: (context) => VeilidChatGlobalInit.initialize(),
builder: (context, __) {
final globalInit = context.watch<VeilidChatGlobalInit?>();
if (globalInit == null) {
// Splash screen until we're done with init
return const Splash();
}
// Once init is done, we proceed with the app
final localizationDelegate = LocalizedApp.of(context).delegate;
initialData: null,
create: (context) => VeilidChatGlobalInit.initialize(),
builder: (context, __) {
final globalInit = context.watch<VeilidChatGlobalInit?>();
if (globalInit == null) {
// Splash screen until we're done with init
return const Splash();
}
// Once init is done, we proceed with the app
final localizationDelegate = LocalizedApp.of(context).delegate;
return SafeArea(child: appBuilder(context, localizationDelegate));
});
return SafeArea(child: appBuilder(context, localizationDelegate));
},
);
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties
.add(DiagnosticsProperty<ThemeData>('themeData', initialThemeData));
properties.add(
DiagnosticsProperty<ThemeData>('themeData', initialThemeData),
);
}
}

View file

@ -24,50 +24,82 @@ const metadataKeyAttachments = 'attachments';
const _sfChangedContacts = 'changed_contacts';
class ChatComponentCubit extends Cubit<ChatComponentState> {
////////////////////////////////////////////////////////////////////////////
final _initWait = WaitSet<void, void>();
final AccountInfo _accountInfo;
final AccountRecordCubit _accountRecordCubit;
final ContactListCubit _contactListCubit;
final List<ActiveConversationCubit> _conversationCubits;
final SingleContactMessagesCubit _messagesCubit;
late final Author _localUserAuthor;
late final StreamSubscription<AsyncValue<proto.Account>>
_accountRecordSubscription;
final Map<Author, StreamSubscription<AsyncValue<ActiveConversationState>>>
_conversationSubscriptions = {};
late StreamSubscription<SingleContactMessagesState> _messagesSubscription;
late StreamSubscription<DHTShortArrayCubitState<proto.Contact>>
_contactListSubscription;
double scrollOffset = 0;
ChatComponentCubit._({
required AccountInfo accountInfo,
required AccountRecordCubit accountRecordCubit,
required ContactListCubit contactListCubit,
required List<ActiveConversationCubit> conversationCubits,
required SingleContactMessagesCubit messagesCubit,
}) : _accountInfo = accountInfo,
_accountRecordCubit = accountRecordCubit,
_contactListCubit = contactListCubit,
_conversationCubits = conversationCubits,
_messagesCubit = messagesCubit,
super(const ChatComponentState(
localUser: null,
remoteUsers: IMap.empty(),
historicalRemoteUsers: IMap.empty(),
unknownUsers: IMap.empty(),
messageWindow: AsyncLoading(),
title: '',
)) {
}) : _accountInfo = accountInfo,
_accountRecordCubit = accountRecordCubit,
_contactListCubit = contactListCubit,
_conversationCubits = conversationCubits,
_messagesCubit = messagesCubit,
super(
const ChatComponentState(
localUser: null,
remoteUsers: IMap.empty(),
historicalRemoteUsers: IMap.empty(),
unknownUsers: IMap.empty(),
messageWindow: AsyncLoading(),
title: '',
),
) {
// Async Init
_initWait.add(_init);
}
factory ChatComponentCubit.singleContact(
{required AccountInfo accountInfo,
required AccountRecordCubit accountRecordCubit,
required ContactListCubit contactListCubit,
required ActiveConversationCubit activeConversationCubit,
required SingleContactMessagesCubit messagesCubit}) =>
ChatComponentCubit._(
accountInfo: accountInfo,
accountRecordCubit: accountRecordCubit,
contactListCubit: contactListCubit,
conversationCubits: [activeConversationCubit],
messagesCubit: messagesCubit,
);
factory ChatComponentCubit.singleContact({
required AccountInfo accountInfo,
required AccountRecordCubit accountRecordCubit,
required ContactListCubit contactListCubit,
required ActiveConversationCubit activeConversationCubit,
required SingleContactMessagesCubit messagesCubit,
}) => ChatComponentCubit._(
accountInfo: accountInfo,
accountRecordCubit: accountRecordCubit,
contactListCubit: contactListCubit,
conversationCubits: [activeConversationCubit],
messagesCubit: messagesCubit,
);
Future<void> _init(Completer<void> cancel) async {
// Get local user info and account record cubit
_localUserAuthor = await _accountInfo.getAuthor();
// Subscribe to local user info
_accountRecordSubscription =
_accountRecordCubit.stream.listen(_onChangedAccountRecord);
_accountRecordSubscription = _accountRecordCubit.stream.listen(
_onChangedAccountRecord,
);
_onChangedAccountRecord(_accountRecordCubit.state);
// Subscribe to messages
@ -75,8 +107,9 @@ class ChatComponentCubit extends Cubit<ChatComponentState> {
_onChangedMessages(_messagesCubit.state);
// Subscribe to contact list changes
_contactListSubscription =
_contactListCubit.stream.listen(_onChangedContacts);
_contactListSubscription = _contactListCubit.stream.listen(
_onChangedContacts,
);
_onChangedContacts(_contactListCubit.state);
// Subscribe to remote user info
@ -103,30 +136,40 @@ class ChatComponentCubit extends Cubit<ChatComponentState> {
// length.
// If tail is positive, the position is absolute from the head of the log
// If follow is enabled, the tail offset will update when the log changes
Future<void> setWindow(
{int? tail, int? count, bool? follow, bool forceRefresh = false}) async {
Future<void> setWindow({
int? tail,
int? count,
bool? follow,
bool forceRefresh = false,
}) async {
//await _initWait();
await _messagesCubit.setWindow(
tail: tail, count: count, follow: follow, forceRefresh: forceRefresh);
tail: tail,
count: count,
follow: follow,
forceRefresh: forceRefresh,
);
}
// Send a message
void sendMessage(
{required String text,
String? replyToMessageId,
Timestamp? expiration,
int? viewLimit,
List<proto.Attachment>? attachments}) {
void sendMessage({
required String text,
String? replyToMessageId,
Timestamp? expiration,
int? viewLimit,
List<proto.Attachment>? attachments,
}) {
final replyId = (replyToMessageId != null)
? base64UrlNoPadDecode(replyToMessageId)
: null;
_addTextMessage(
text: text,
replyId: replyId,
expiration: expiration,
viewLimit: viewLimit,
attachments: attachments ?? []);
text: text,
replyId: replyId,
expiration: expiration,
viewLimit: viewLimit,
attachments: attachments ?? [],
);
}
// Run a chat command
@ -145,14 +188,16 @@ class ChatComponentCubit extends Cubit<ChatComponentState> {
return;
}
final localUser = fccore.User(
id: _localUserAuthor.toString(),
name: account.profile.name,
metadata: {metadataKeyAuthor: _localUserAuthor});
id: _localUserAuthor.toString(),
name: account.profile.name,
metadata: {metadataKeyAuthor: _localUserAuthor},
);
emit(state.copyWith(localUser: localUser));
}
void _onChangedMessages(
AsyncValue<WindowState<MessageState>> avMessagesState) {
AsyncValue<WindowState<MessageState>> avMessagesState,
) {
emit(_convertMessages(state, avMessagesState));
}
@ -172,11 +217,18 @@ class ChatComponentCubit extends Cubit<ChatComponentState> {
return;
}
final remoteUser =
_convertRemoteUser(remoteAuthor, activeConversationState);
final remoteUser = _convertRemoteUser(
remoteAuthor,
activeConversationState,
);
emit(_updateTitle(state.copyWith(
remoteUsers: state.remoteUsers.add(remoteUser.id, remoteUser))));
emit(
_updateTitle(
state.copyWith(
remoteUsers: state.remoteUsers.add(remoteUser.id, remoteUser),
),
),
);
}
static ChatComponentState _updateTitle(ChatComponentState currentState) {
@ -188,36 +240,44 @@ class ChatComponentCubit extends Cubit<ChatComponentState> {
return currentState.copyWith(title: remoteUser.name ?? '<unnamed>');
}
return currentState.copyWith(
title: '<group chat with ${currentState.remoteUsers.length} users>');
title: '<group chat with ${currentState.remoteUsers.length} users>',
);
}
fccore.User _convertRemoteUser(
Author remoteAuthor, ActiveConversationState activeConversationState) {
Author remoteAuthor,
ActiveConversationState activeConversationState,
) {
// See if we have a contact for this remote user
final contacts = _contactListCubit.state.state.asData?.value;
if (contacts != null) {
final contactIdx = contacts
.indexWhere((x) => x.value.author.toDart().id == remoteAuthor.id);
final contactIdx = contacts.indexWhere(
(x) => x.value.author.toDart().id == remoteAuthor.id,
);
if (contactIdx != -1) {
final contact = contacts[contactIdx].value;
return fccore.User(
id: remoteAuthor.id.toString(),
name: contact.displayName,
metadata: {metadataKeyAuthor: remoteAuthor});
id: remoteAuthor.id.toString(),
name: contact.displayName,
metadata: {metadataKeyAuthor: remoteAuthor},
);
}
}
return fccore.User(
id: remoteAuthor.id.toString(),
name: activeConversationState.remoteConversation?.profile.name ??
'<unnamed>',
metadata: {metadataKeyAuthor: remoteAuthor});
id: remoteAuthor.id.toString(),
name:
activeConversationState.remoteConversation?.profile.name ??
'<unnamed>',
metadata: {metadataKeyAuthor: remoteAuthor},
);
}
fccore.User _convertUnknownUser(Author unknownAuthor) => fccore.User(
id: unknownAuthor.id.toString(),
name: '<${unknownAuthor.id}>',
metadata: {metadataKeyAuthor: unknownAuthor});
id: unknownAuthor.id.toString(),
name: '<${unknownAuthor.id}>',
metadata: {metadataKeyAuthor: unknownAuthor},
);
Future<void> _updateConversationSubscriptions() async {
// Get existing subscription keys and state
@ -232,23 +292,29 @@ class ChatComponentCubit extends Cubit<ChatComponentState> {
// If the cubit is already being listened to we have nothing to do
if (existing.remove(remoteAuthor)) {
// If the cubit is not already being listened to we should do that
_conversationSubscriptions[remoteAuthor] = cc.stream
.listen((avConv) => _onChangedConversation(remoteAuthor, avConv));
_conversationSubscriptions[remoteAuthor] = cc.stream.listen(
(avConv) => _onChangedConversation(remoteAuthor, avConv),
);
}
final activeConversationState = cc.state.asData?.value;
if (activeConversationState != null) {
final remoteUser =
_convertRemoteUser(remoteAuthor, activeConversationState);
currentRemoteUsersState =
currentRemoteUsersState.add(remoteUser.id, remoteUser);
final remoteUser = _convertRemoteUser(
remoteAuthor,
activeConversationState,
);
currentRemoteUsersState = currentRemoteUsersState.add(
remoteUser.id,
remoteUser,
);
}
}
// Purge remote users we didn't see in the cubit list any more
final cancels = <Future<void>>[];
for (final deadUser in existing) {
currentRemoteUsersState =
currentRemoteUsersState.remove(deadUser.toString());
currentRemoteUsersState = currentRemoteUsersState.remove(
deadUser.toString(),
);
cancels.add(_conversationSubscriptions.remove(deadUser)!.cancel());
}
await cancels.wait;
@ -258,7 +324,9 @@ class ChatComponentCubit extends Cubit<ChatComponentState> {
}
(ChatComponentState, fccore.Message?) _messageStateToChatMessage(
ChatComponentState currentState, MessageState message) {
ChatComponentState currentState,
MessageState message,
) {
final authorMemberId = message.content.author.toDart();
final authorUserId = authorMemberId.toString();
@ -281,8 +349,11 @@ class ChatComponentCubit extends Cubit<ChatComponentState> {
} else {
final unknownUser = _convertUnknownUser(authorMemberId);
currentState = currentState.copyWith(
unknownUsers:
currentState.unknownUsers.add(authorUserId, unknownUser));
unknownUsers: currentState.unknownUsers.add(
authorUserId,
unknownUser,
),
);
author = unknownUser;
}
}
@ -306,7 +377,8 @@ class ChatComponentCubit extends Cubit<ChatComponentState> {
final reconciledAt = message.reconciledTimestamp == null
? null
: DateTime.fromMicrosecondsSinceEpoch(
message.reconciledTimestamp!.value.toInt());
message.reconciledTimestamp!.value.toInt(),
);
// print('message seqid: ${message.seqId}');
@ -315,18 +387,20 @@ class ChatComponentCubit extends Cubit<ChatComponentState> {
final reconciledId = message.content.authorUniqueIdString;
final contentText = message.content.text;
final textMessage = fccore.TextMessage(
authorId: author.id,
createdAt: DateTime.fromMicrosecondsSinceEpoch(
message.sentTimestamp.value.toInt()),
sentAt: reconciledAt,
id: reconciledId,
//text: '${contentText.text} (${message.seqId})',
text: contentText.text,
metadata: {
kSeqId: message.seqId,
kSending: message.sendState == MessageSendState.sending,
if (fccore.isOnlyEmoji(contentText.text)) 'isOnlyEmoji': true,
});
authorId: author.id,
createdAt: DateTime.fromMicrosecondsSinceEpoch(
message.sentTimestamp.value.toInt(),
),
sentAt: reconciledAt,
id: reconciledId,
//text: '${contentText.text} (${message.seqId})',
text: contentText.text,
metadata: {
kSeqId: message.seqId,
kSending: message.sendState == MessageSendState.sending,
if (fccore.isOnlyEmoji(contentText.text)) 'isOnlyEmoji': true,
},
);
return (currentState, textMessage);
case proto.Message_Kind.secret:
case proto.Message_Kind.delete:
@ -341,8 +415,10 @@ class ChatComponentCubit extends Cubit<ChatComponentState> {
}
}
ChatComponentState _convertMessages(ChatComponentState currentState,
AsyncValue<WindowState<MessageState>> avMessagesState) {
ChatComponentState _convertMessages(
ChatComponentState currentState,
AsyncValue<WindowState<MessageState>> avMessagesState,
) {
// Clear out unknown users
currentState = state.copyWith(unknownUsers: const IMap.empty());
@ -350,12 +426,14 @@ class ChatComponentCubit extends Cubit<ChatComponentState> {
if (asError != null) {
addError(asError.error, asError.stackTrace);
return currentState.copyWith(
unknownUsers: const IMap.empty(),
messageWindow: AsyncValue.error(asError.error, asError.stackTrace));
unknownUsers: const IMap.empty(),
messageWindow: AsyncValue.error(asError.error, asError.stackTrace),
);
} else if (avMessagesState.asLoading != null) {
return currentState.copyWith(
unknownUsers: const IMap.empty(),
messageWindow: const AsyncValue.loading());
unknownUsers: const IMap.empty(),
messageWindow: const AsyncValue.loading(),
);
}
final messagesState = avMessagesState.asData!.value;
@ -363,37 +441,45 @@ class ChatComponentCubit extends Cubit<ChatComponentState> {
final chatMessages = <fccore.Message>[];
final tsSet = <String>{};
for (final message in messagesState.window) {
final (newState, chatMessage) =
_messageStateToChatMessage(currentState, message);
final (newState, chatMessage) = _messageStateToChatMessage(
currentState,
message,
);
currentState = newState;
if (chatMessage == null) {
continue;
}
if (!tsSet.add(chatMessage.id)) {
log.error('duplicate id found: ${chatMessage.id}'
// '\nMessages:\n${messagesState.window}'
// '\nChatMessages:\n$chatMessages'
);
log.error(
'duplicate id found: ${chatMessage.id}',
// '\nMessages:\n${messagesState.window}'
// '\nChatMessages:\n$chatMessages'
);
} else {
chatMessages.add(chatMessage);
}
}
return currentState.copyWith(
messageWindow: AsyncValue.data(WindowState<fccore.Message>(
window: chatMessages.toIList(),
length: messagesState.length,
windowTail: messagesState.windowTail,
windowCount: messagesState.windowCount,
follow: messagesState.follow)));
messageWindow: AsyncValue.data(
WindowState<fccore.Message>(
window: chatMessages.toIList(),
length: messagesState.length,
windowTail: messagesState.windowTail,
windowCount: messagesState.windowCount,
follow: messagesState.follow,
),
),
);
}
void _addTextMessage(
{required String text,
String? topic,
Uint8List? replyId,
Timestamp? expiration,
int? viewLimit,
List<proto.Attachment> attachments = const []}) {
void _addTextMessage({
required String text,
String? topic,
Uint8List? replyId,
Timestamp? expiration,
int? viewLimit,
List<proto.Attachment> attachments = const [],
}) {
final protoMessageText = proto.Message_Text()..text = text;
if (topic != null) {
protoMessageText.topic = topic;
@ -408,23 +494,4 @@ class ChatComponentCubit extends Cubit<ChatComponentState> {
_messagesCubit.sendTextMessage(messageText: protoMessageText);
}
////////////////////////////////////////////////////////////////////////////
final _initWait = WaitSet<void, void>();
final AccountInfo _accountInfo;
final AccountRecordCubit _accountRecordCubit;
final ContactListCubit _contactListCubit;
final List<ActiveConversationCubit> _conversationCubits;
final SingleContactMessagesCubit _messagesCubit;
late final Author _localUserAuthor;
late final StreamSubscription<AsyncValue<proto.Account>>
_accountRecordSubscription;
final Map<Author, StreamSubscription<AsyncValue<ActiveConversationState>>>
_conversationSubscriptions = {};
late StreamSubscription<SingleContactMessagesState> _messagesSubscription;
late StreamSubscription<DHTShortArrayCubitState<proto.Contact>>
_contactListSubscription;
double scrollOffset = 0;
}

View file

@ -11,6 +11,35 @@ import 'coded_message.dart';
import 'message_integrity.dart';
class AuthorInputQueue {
////////////////////////////////////////////////////////////////////////////
/// The author of this messages in the input source
final Author _author;
/// The input source we're pulling messages from
final AuthorInputSource _inputSource;
/// What to call if an error happens
final void Function(Object, StackTrace?) _onError;
/// The message integrity validator
final MessageIntegrity _messageIntegrity;
/// The last message we reconciled/output
CodedMessage? _previousMessage;
/// The current message we're looking at
CodedMessage? _currentMessage;
/// The current position in the input source that we are looking at
int _inputPosition;
/// The current input window from the InputSource;
InputWindow? _currentWindow;
/// Desired maximum window length
static const _maxWindowLength = 256;
AuthorInputQueue._({
required Author author,
required AuthorInputSource inputSource,
@ -18,12 +47,12 @@ class AuthorInputQueue {
required CodedMessage? previousMessage,
required void Function(Object, StackTrace?) onError,
required MessageIntegrity messageIntegrity,
}) : _author = author,
_onError = onError,
_inputSource = inputSource,
_previousMessage = previousMessage,
_messageIntegrity = messageIntegrity,
_inputPosition = inputPosition;
}) : _author = author,
_onError = onError,
_inputSource = inputSource,
_previousMessage = previousMessage,
_messageIntegrity = messageIntegrity,
_inputPosition = inputPosition;
static Future<AuthorInputQueue?> create({
required Author author,
@ -36,12 +65,13 @@ class AuthorInputQueue {
// Create an input queue for the input source
final queue = AuthorInputQueue._(
author: author,
inputSource: inputSource,
inputPosition: inputPosition,
previousMessage: previousMessage,
onError: onError,
messageIntegrity: await MessageIntegrity.create(author: author));
author: author,
inputSource: inputSource,
inputPosition: inputPosition,
previousMessage: previousMessage,
onError: onError,
messageIntegrity: await MessageIntegrity.create(author: author),
);
// Rewind the queue's 'inputPosition' to the first unreconciled message
if (!await queue._rewindInputToAfterLastMessage()) {
@ -108,18 +138,22 @@ class AuthorInputQueue {
// Ensure the timestamp is not moving backward
if (currentMessage.message.timestamp <
_previousMessage!.message.timestamp) {
log.warning('timestamp backward: ${currentMessage.message.timestamp}'
' < ${_previousMessage!.message.timestamp}');
log.warning(
'timestamp backward: ${currentMessage.message.timestamp}'
' < ${_previousMessage!.message.timestamp}',
);
continue;
}
}
// Verify the id chain for the message
final matchId = await _messageIntegrity
.generateMessageId(_previousMessage?.messageBytes);
final matchId = await _messageIntegrity.generateMessageId(
_previousMessage?.messageBytes,
);
if (matchId.compare(currentMessage.message.idBytes) != 0) {
log.warning(
'id chain invalid: $matchId != ${currentMessage.message.idBytes}');
'id chain invalid: $matchId != ${currentMessage.message.idBytes}',
);
continue;
}
@ -148,17 +182,20 @@ class AuthorInputQueue {
}
// Iterate through current window backward
for (var i = currentWindow.elements.length - 1;
i >= 0 && _inputPosition >= 0;
i--, _inputPosition--) {
for (
var i = currentWindow.elements.length - 1;
i >= 0 && _inputPosition >= 0;
i--, _inputPosition--
) {
final elem = currentWindow.elements[i];
// If we've found an input element that is older or same time as our
// last reconciled message for this author, or we find the message
// itself then we stop
if (_previousMessage != null) {
if (elem.value.message.authorUniqueIdBytes
.compare(_previousMessage!.message.authorUniqueIdBytes) ==
if (elem.value.message.authorUniqueIdBytes.compare(
_previousMessage!.message.authorUniqueIdBytes,
) ==
0 ||
elem.value.message.timestamp <=
_previousMessage!.message.timestamp) {
@ -178,7 +215,8 @@ class AuthorInputQueue {
// against it if we can.
if (_inputPosition >= 0) {
_currentMessage = currentWindow
.elements[_inputPosition - currentWindow.firstPosition].value;
.elements[_inputPosition - currentWindow.firstPosition]
.value;
}
// After this advance(), the _inputPosition and _currentMessage should
@ -220,8 +258,10 @@ class AuthorInputQueue {
} else if (_inputPosition > lastPosition) {
// Slide it forward, current position is now first
firstPosition = _inputPosition;
lastPosition =
min((_inputPosition + _maxWindowLength) - 1, inputTailPosition - 1);
lastPosition = min(
(_inputPosition + _maxWindowLength) - 1,
inputTailPosition - 1,
);
}
} else {
// need a new window, start with the input position at the end
@ -231,7 +271,9 @@ class AuthorInputQueue {
// Get another input batch futher back
final avCurrentWindow = await _inputSource.getWindow(
firstPosition, lastPosition - firstPosition + 1);
firstPosition,
lastPosition - firstPosition + 1,
);
final asErr = avCurrentWindow.asError;
if (asErr != null) {
@ -256,8 +298,10 @@ class AuthorInputQueue {
// window than the one requested, possibly due to DHT consistency
// fluctuations and race conditions
if (clampInputPosition) {
_inputPosition = min(max(_inputPosition, nextWindow.firstPosition),
nextWindow.lastPosition);
_inputPosition = min(
max(_inputPosition, nextWindow.firstPosition),
nextWindow.lastPosition,
);
} else if (_inputPosition < nextWindow.firstPosition ||
_inputPosition > nextWindow.lastPosition) {
return null;
@ -265,33 +309,4 @@ class AuthorInputQueue {
return _currentWindow = nextWindow;
}
////////////////////////////////////////////////////////////////////////////
/// The author of this messages in the input source
final Author _author;
/// The input source we're pulling messages from
final AuthorInputSource _inputSource;
/// What to call if an error happens
final void Function(Object, StackTrace?) _onError;
/// The message integrity validator
final MessageIntegrity _messageIntegrity;
/// The last message we reconciled/output
CodedMessage? _previousMessage;
/// The current message we're looking at
CodedMessage? _currentMessage;
/// The current position in the input source that we are looking at
int _inputPosition;
/// The current input window from the InputSource;
InputWindow? _currentWindow;
/// Desired maximum window length
static const _maxWindowLength = 256;
}

View file

@ -9,19 +9,26 @@ import 'coded_message.dart';
@immutable
class InputWindow {
const InputWindow({required this.elements, required this.firstPosition})
: lastPosition = firstPosition + elements.length - 1,
isEmpty = elements.length == 0,
length = elements.length;
final IList<OnlineElementState<CodedMessage>> elements;
final int firstPosition;
final int lastPosition;
final bool isEmpty;
final int length;
const InputWindow({required this.elements, required this.firstPosition})
: lastPosition = firstPosition + elements.length - 1,
isEmpty = elements.length == 0,
length = elements.length;
}
class AuthorInputSource {
////////////////////////////////////////////////////////////////////////////
final DHTLog _dhtLog;
AuthorInputSource.fromDHTLog(DHTLog dhtLog) : _dhtLog = dhtLog;
////////////////////////////////////////////////////////////////////////////
@ -30,46 +37,51 @@ class AuthorInputSource {
_dhtLog.operate((reader) async => reader.length);
Future<AsyncValue<InputWindow?>> getWindow(
int startPosition, int windowLength) =>
_dhtLog.operate((reader) async {
// Don't allow negative length
if (windowLength <= 0) {
return const AsyncValue.data(null);
}
// Trim if we're beyond input source
var endPosition = startPosition + windowLength - 1;
startPosition = max(startPosition, 0);
endPosition = max(endPosition, 0);
int startPosition,
int windowLength,
) => _dhtLog.operate((reader) async {
// Don't allow negative length
if (windowLength <= 0) {
return const AsyncValue.data(null);
}
// Trim if we're beyond input source
var endPosition = startPosition + windowLength - 1;
startPosition = max(startPosition, 0);
endPosition = max(endPosition, 0);
// Get another input batch futher back
try {
Set<int>? offlinePositions;
if (_dhtLog.writer != null) {
offlinePositions = await reader.getOfflinePositions();
}
// Get another input batch futher back
try {
Set<int>? offlinePositions;
if (_dhtLog.writer != null) {
offlinePositions = await reader.getOfflinePositions();
}
final messages = await reader.getRange(startPosition,
length: endPosition - startPosition + 1);
if (messages == null) {
return const AsyncValue.loading();
}
final messages = await reader.getRange(
startPosition,
length: endPosition - startPosition + 1,
);
if (messages == null) {
return const AsyncValue.loading();
}
final elements = messages.indexed
.map((x) => OnlineElementState(
value: CodedMessage(x.$2),
isOffline: offlinePositions?.contains(x.$1 + startPosition) ??
false))
.toIList();
final elements = messages.indexed
.map(
(x) => OnlineElementState(
value: CodedMessage(x.$2),
isOffline:
offlinePositions?.contains(x.$1 + startPosition) ?? false,
),
)
.toIList();
final window =
InputWindow(elements: elements, firstPosition: startPosition);
final window = InputWindow(
elements: elements,
firstPosition: startPosition,
);
return AsyncValue.data(window);
} on Exception catch (e, st) {
return AsyncValue.error(e, st);
}
});
////////////////////////////////////////////////////////////////////////////
final DHTLog _dhtLog;
return AsyncValue.data(window);
} on Exception catch (e, st) {
return AsyncValue.error(e, st);
}
});
}

View file

@ -7,15 +7,16 @@ import '../../../proto/proto.dart' as proto;
@immutable
class CodedMessage extends Equatable {
final Uint8List messageBytes;
final proto.Message message;
CodedMessage(this.messageBytes)
: message = proto.Message.fromBuffer(messageBytes);
: message = proto.Message.fromBuffer(messageBytes);
static int compareTimestamp(CodedMessage a, CodedMessage b) =>
proto.MessageExt.compareTimestamp(a.message, b.message);
final Uint8List messageBytes;
final proto.Message message;
@override
List<Object?> get props => [messageBytes, message];
}

View file

@ -4,11 +4,17 @@ import 'package:veilid_support/veilid_support.dart';
import '../../../conversation/conversation.dart';
class MessageIntegrity {
////////////////////////////////////////////////////////////////////////////
final Author _author;
final VeilidCryptoSystem _crypto;
MessageIntegrity._({
required Author author,
required VeilidCryptoSystem crypto,
}) : _author = author,
_crypto = crypto;
}) : _author = author,
_crypto = crypto;
static Future<MessageIntegrity> create({required Author author}) async {
final crypto = await Veilid.instance.getCryptoSystem(author.id.kind);
return MessageIntegrity._(author: author, crypto: crypto);
@ -37,7 +43,4 @@ class MessageIntegrity {
Future<Uint8List> _hashMessageBytes(Uint8List messageBytes) async =>
(await _crypto.generateHash(messageBytes)).value.toBytes();
////////////////////////////////////////////////////////////////////////////
final Author _author;
final VeilidCryptoSystem _crypto;
}

View file

@ -7,13 +7,20 @@ import '../../../conversation/conversation.dart';
import '../../../proto/proto.deprecated.dart' as proto_deprecated;
class MessageIntegrityDeprecated {
////////////////////////////////////////////////////////////////////////////
final Author _author;
final VeilidCryptoSystem _crypto;
MessageIntegrityDeprecated._({
required Author author,
required VeilidCryptoSystem crypto,
}) : _author = author,
_crypto = crypto;
static Future<MessageIntegrityDeprecated> create(
{required Author author}) async {
}) : _author = author,
_crypto = crypto;
static Future<MessageIntegrityDeprecated> create({
required Author author,
}) async {
final crypto = await Veilid.instance.getCryptoSystem(author.id.kind);
return MessageIntegrityDeprecated._(author: author, crypto: crypto);
}
@ -44,7 +51,10 @@ class MessageIntegrityDeprecated {
// Verify signature
return _crypto.verify(
PublicKey.fromString(_author.id.toString()), data, signature);
PublicKey.fromString(_author.id.toString()),
data,
signature,
);
}
////////////////////////////////////////////////////////////////////////////
@ -54,9 +64,7 @@ class MessageIntegrityDeprecated {
(await _crypto.generateHash(_author.id.toBytes())).toBytes();
Future<Uint8List> _hashSignature(
proto_deprecated.Signature signature) async =>
proto_deprecated.Signature signature,
) async =>
(await _crypto.generateHash(signature.toDart().toBytes())).toBytes();
////////////////////////////////////////////////////////////////////////////
final Author _author;
final VeilidCryptoSystem _crypto;
}

View file

@ -14,11 +14,25 @@ import 'coded_message.dart';
import 'output_position.dart';
class MessageReconciliation {
MessageReconciliation(
{required TableDBArrayProtobufCubit<proto.ReconciledMessage> output,
required void Function(Object, StackTrace?) onError})
: _outputCubit = output,
_onError = onError;
////////////////////////////////////////////////////////////////////////////
final Map<Author, AuthorInputSource> _inputSources = {};
final Map<Author, AuthorInputQueue> _inputQueues = {};
final Map<Author, OutputPosition?> _outputPositions = {};
final TableDBArrayProtobufCubit<proto.ReconciledMessage> _outputCubit;
final void Function(Object, StackTrace?) _onError;
static const _maxReconcileChunk = 65536;
MessageReconciliation({
required TableDBArrayProtobufCubit<proto.ReconciledMessage> output,
required void Function(Object, StackTrace?) onError,
}) : _outputCubit = output,
_onError = onError;
////////////////////////////////////////////////////////////////////////////
@ -34,10 +48,12 @@ class MessageReconciliation {
final activeInputQueues = await _updateAuthorInputQueues();
// Process all input queues together
await _outputCubit.operate((reconciledArray) => _reconcileInputQueues(
reconciledArray: reconciledArray,
activeInputQueues: activeInputQueues,
));
await _outputCubit.operate(
(reconciledArray) => _reconcileInputQueues(
reconciledArray: reconciledArray,
activeInputQueues: activeInputQueues,
),
);
});
}
@ -71,9 +87,10 @@ class MessageReconciliation {
dws.add((_) async {
try {
await _enqueueAuthorInput(
author: author,
inputSource: inputSource,
outputArray: outputArray);
author: author,
inputSource: inputSource,
outputArray: outputArray,
);
// Catch everything so we can avoid ParallelWaitError
// ignore: avoid_catches_without_on_clauses
} catch (e, st) {
@ -89,30 +106,33 @@ class MessageReconciliation {
});
// Get the active input queues
final activeInputQueues = await _inputQueues.entries
.map((entry) async {
if (await entry.value.getCurrentMessage() != null) {
return entry.value;
} else {
return null;
}
})
.toList()
.wait
..removeNulls();
final activeInputQueues =
await _inputQueues.entries
.map((entry) async {
if (await entry.value.getCurrentMessage() != null) {
return entry.value;
} else {
return null;
}
})
.toList()
.wait
..removeNulls();
return activeInputQueues.cast<AuthorInputQueue>();
}
// Set up a single author's message reconciliation
Future<void> _enqueueAuthorInput(
{required Author author,
required AuthorInputSource inputSource,
required TableDBArrayProtobuf<proto.ReconciledMessage>
outputArray}) async {
Future<void> _enqueueAuthorInput({
required Author author,
required AuthorInputSource inputSource,
required TableDBArrayProtobuf<proto.ReconciledMessage> outputArray,
}) async {
// Get the position of our most recent reconciled message from this author
final outputPosition =
await _findLastOutputPosition(author: author, outputArray: outputArray);
final outputPosition = await _findLastOutputPosition(
author: author,
outputArray: outputArray,
);
// Find oldest message we have not yet reconciled
final inputQueue = await AuthorInputQueue.create(
@ -134,10 +154,10 @@ class MessageReconciliation {
// Get the position of our most recent reconciled message from this author
// XXX: For a group chat, this should find when the author
// was added to the membership so we don't just go back in time forever
Future<OutputPosition?> _findLastOutputPosition(
{required Author author,
required TableDBArrayProtobuf<proto.ReconciledMessage>
outputArray}) async {
Future<OutputPosition?> _findLastOutputPosition({
required Author author,
required TableDBArrayProtobuf<proto.ReconciledMessage> outputArray,
}) async {
var pos = outputArray.length - 1;
while (pos >= 0) {
final message = await outputArray.get(pos);
@ -191,8 +211,10 @@ class MessageReconciliation {
for (final inputQueue in activeInputQueues) {
final inputCurrent = await inputQueue.getCurrentMessage();
if (inputCurrent == null) {
log.error('Active input queue did not have a current message: '
'${inputQueue.author}');
log.error(
'Active input queue did not have a current message: '
'${inputQueue.author}',
);
continue;
}
if (currentOutputPosition == null ||
@ -222,12 +244,14 @@ class MessageReconciliation {
// Add reconciled timestamps
final reconciledInserts = toInsert
.map((message) => (
message,
proto.ReconciledMessage()
..reconciledTime = reconciledTime
..content = message.messageBytes
))
.map(
(message) => (
message,
proto.ReconciledMessage()
..reconciledTime = reconciledTime
..content = message.messageBytes,
),
)
.toList();
// Figure out where to insert the reconciled messages
@ -235,7 +259,9 @@ class MessageReconciliation {
// Insert them all at once
await reconciledArray.insertAll(
insertPos, reconciledInserts.map((x) => x.$2).toList());
insertPos,
reconciledInserts.map((x) => x.$2).toList(),
);
// Update output positions for input queues
final updatePositions = _outputPositions.keys.toSet();
@ -263,19 +289,11 @@ class MessageReconciliation {
currentOutputPosition = null;
} else {
currentOutputPosition = OutputPosition(
await reconciledArray.get(nextOutputPos), nextOutputPos);
await reconciledArray.get(nextOutputPos),
nextOutputPos,
);
}
}
}
}
////////////////////////////////////////////////////////////////////////////
final Map<Author, AuthorInputSource> _inputSources = {};
final Map<Author, AuthorInputQueue> _inputQueues = {};
final Map<Author, OutputPosition?> _outputPositions = {};
final TableDBArrayProtobufCubit<proto.ReconciledMessage> _outputCubit;
final void Function(Object, StackTrace?) _onError;
static const _maxReconcileChunk = 65536;
}

View file

@ -6,13 +6,14 @@ import 'coded_message.dart';
@immutable
class OutputPosition extends Equatable {
final proto.ReconciledMessage message;
final int pos;
const OutputPosition(this.message, this.pos);
CodedMessage get codedMessage => CodedMessage(message.contentBytes);
final proto.ReconciledMessage message;
final int pos;
@override
List<Object?> get props => [message, pos];
}

View file

@ -19,13 +19,26 @@ import 'reconciliation/reconciliation.dart';
const _sfSendMessageTag = 'sfSendMessageTag';
class RenderStateElement {
RenderStateElement(
{required this.seqId,
required this.message,
required this.isLocal,
this.reconciledTimestamp,
this.sent = false,
this.sentOffline = false});
int seqId;
proto.Message message;
bool isLocal;
Timestamp? reconciledTimestamp;
bool sent;
bool sentOffline;
RenderStateElement({
required this.seqId,
required this.message,
required this.isLocal,
this.reconciledTimestamp,
this.sent = false,
this.sentOffline = false,
});
MessageSendState? get sendState {
if (!isLocal) {
@ -43,13 +56,6 @@ class RenderStateElement {
}
return null;
}
int seqId;
proto.Message message;
bool isLocal;
Timestamp? reconciledTimestamp;
bool sent;
bool sentOffline;
}
typedef SingleContactMessagesState = AsyncValue<WindowState<MessageState>>;
@ -57,6 +63,53 @@ typedef SingleContactMessagesState = AsyncValue<WindowState<MessageState>>;
// Cubit that processes single-contact chats
// Builds the reconciled chat record from the local and remote conversation keys
class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
/////////////////////////////////////////////////////////////////////////
final WaitSet<void, void> _initWait = WaitSet();
late final AccountInfo _accountInfo;
late final Author _localAuthor;
final Author _remoteAuthor;
final RecordKey _localConversationRecordKey;
final RecordKey _localMessagesRecordKey;
final RecordKey _remoteConversationRecordKey;
RecordKey? _remoteMessagesRecordKey;
late final VeilidCrypto _conversationCrypto;
late final MessageIntegrity _senderMessageIntegrity;
DHTLog? _sentMessagesDHTLog;
DHTLog? _rcvdMessagesDHTLog;
TableDBArrayProtobufCubit<proto.ReconciledMessage>? _reconciledMessagesCubit;
late final MessageReconciliation _reconciliation;
late final PersistentQueue<proto.Message> _unsentMessagesQueue;
StreamSubscription<void>? _sentSubscription;
StreamSubscription<void>? _rcvdSubscription;
StreamSubscription<TableDBArrayProtobufBusyState<proto.ReconciledMessage>>?
_reconciledSubscription;
final StreamController<Future<void> Function()> _commandController;
late final Future<void> _commandRunnerFut;
final _sspRemoteConversationRecordKey = SingleStateProcessor<RecordKey?>();
final _uuidGen = const UuidV4();
SingleContactMessagesCubit({
required AccountInfo accountInfo,
required Author remoteAuthor,
@ -64,14 +117,14 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
required RecordKey localMessagesRecordKey,
required RecordKey remoteConversationRecordKey,
required RecordKey? remoteMessagesRecordKey,
}) : _accountInfo = accountInfo,
_remoteAuthor = remoteAuthor,
_localConversationRecordKey = localConversationRecordKey,
_localMessagesRecordKey = localMessagesRecordKey,
_remoteConversationRecordKey = remoteConversationRecordKey,
_remoteMessagesRecordKey = remoteMessagesRecordKey,
_commandController = StreamController(),
super(const AsyncValue.loading()) {
}) : _accountInfo = accountInfo,
_remoteAuthor = remoteAuthor,
_localConversationRecordKey = localConversationRecordKey,
_localMessagesRecordKey = localMessagesRecordKey,
_remoteConversationRecordKey = remoteConversationRecordKey,
_remoteMessagesRecordKey = remoteMessagesRecordKey,
_commandController = StreamController(),
super(const AsyncValue.loading()) {
// Async Init
_initWait.add(_init);
}
@ -94,11 +147,13 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
// If the local conversation record is gone, then delete the reconciled
// messages table as well
final conversationDead = await DHTRecordPool.instance
.isDeletedRecordKey(_localConversationRecordKey);
final conversationDead = await DHTRecordPool.instance.isDeletedRecordKey(
_localConversationRecordKey,
);
if (conversationDead) {
await SingleContactMessagesCubit.cleanupAndDeleteMessages(
localConversationRecordKey: _localConversationRecordKey);
localConversationRecordKey: _localConversationRecordKey,
);
}
await super.close();
@ -107,14 +162,15 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
// Initialize everything
Future<void> _init(Completer<void> _) async {
_unsentMessagesQueue = PersistentQueue<proto.Message>(
table: 'SingleContactUnsentMessages',
key: _remoteConversationRecordKey.toString(),
fromBuffer: proto.Message.fromBuffer,
toBuffer: (x) => x.writeToBuffer(),
closure: _processUnsentMessages,
onError: (e, st) {
log.error('Exception while processing unsent messages: $e\n$st\n');
});
table: 'SingleContactUnsentMessages',
key: _remoteConversationRecordKey.toString(),
fromBuffer: proto.Message.fromBuffer,
toBuffer: (x) => x.writeToBuffer(),
closure: _processUnsentMessages,
onError: (e, st) {
log.error('Exception while processing unsent messages: $e\n$st\n');
},
);
// Make local Author
await _initLocalAuthor();
@ -141,31 +197,38 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
// Make local Author
Future<void> _initLocalAuthor() async {
_localAuthor = await _accountInfo.getAuthor();
_senderMessageIntegrity =
await MessageIntegrity.create(author: _localAuthor);
_senderMessageIntegrity = await MessageIntegrity.create(
author: _localAuthor,
);
}
// Make crypto
Future<void> _initConversationCrypto() async {
_conversationCrypto =
await _accountInfo.makeConversationCrypto(_remoteAuthor);
_conversationCrypto = await _accountInfo.makeConversationCrypto(
_remoteAuthor,
);
}
// Open local messages key
Future<void> _initSentMessagesDHTLog() async {
final writer = _accountInfo.identityWriter;
final sentMessagesDHTLog =
await DHTLog.openWrite(_localMessagesRecordKey, writer,
debugName: 'SingleContactMessagesCubit::_initSentMessagesCubit::'
'SentMessages',
parent: _localConversationRecordKey,
crypto: _conversationCrypto);
final sentMessagesDHTLog = await DHTLog.openWrite(
_localMessagesRecordKey,
writer,
debugName:
'SingleContactMessagesCubit::_initSentMessagesCubit::'
'SentMessages',
parent: _localConversationRecordKey,
crypto: _conversationCrypto,
);
_sentSubscription = await sentMessagesDHTLog.listen(_updateSentMessages);
_sentMessagesDHTLog = sentMessagesDHTLog;
_reconciliation.addInputSourceFromDHTLog(
await _accountInfo.getAuthor(), sentMessagesDHTLog);
await _accountInfo.getAuthor(),
sentMessagesDHTLog,
);
}
// Open remote messages key
@ -176,11 +239,14 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
}
// Open new cubit if one is desired
final rcvdMessagesDHTLog = await DHTLog.openRead(_remoteMessagesRecordKey!,
debugName: 'SingleContactMessagesCubit::_initRcvdMessagesCubit::'
'RcvdMessages',
parent: _remoteConversationRecordKey,
crypto: _conversationCrypto);
final rcvdMessagesDHTLog = await DHTLog.openRead(
_remoteMessagesRecordKey!,
debugName:
'SingleContactMessagesCubit::_initRcvdMessagesCubit::'
'RcvdMessages',
parent: _remoteConversationRecordKey,
crypto: _conversationCrypto,
);
_rcvdSubscription = await rcvdMessagesDHTLog.listen(_updateRcvdMessages);
_rcvdMessagesDHTLog = rcvdMessagesDHTLog;
@ -188,8 +254,9 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
}
void updateRemoteMessagesRecordKey(RecordKey? remoteMessagesRecordKey) {
_sspRemoteConversationRecordKey.updateState(remoteMessagesRecordKey,
(remoteMessagesRecordKey) async {
_sspRemoteConversationRecordKey.updateState(remoteMessagesRecordKey, (
remoteMessagesRecordKey,
) async {
await _initWait();
// Don't bother if nothing is changing
@ -216,31 +283,37 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
Future<VeilidCrypto> _makeLocalMessagesCrypto() =>
VeilidCryptoPrivate.fromSecretKey(
_accountInfo.userLogin!.identitySecret, 'tabledb');
_accountInfo.userLogin!.identitySecret,
'tabledb',
);
// Open reconciled chat record key
Future<void> _initReconciledMessagesCubit() async {
final tableName =
_reconciledMessagesTableDBName(_localConversationRecordKey);
final tableName = _reconciledMessagesTableDBName(
_localConversationRecordKey,
);
final crypto = await _makeLocalMessagesCrypto();
_reconciledMessagesCubit = TableDBArrayProtobufCubit(
open: () => TableDBArrayProtobuf.make(
table: tableName,
crypto: crypto,
fromBuffer: proto.ReconciledMessage.fromBuffer),
table: tableName,
crypto: crypto,
fromBuffer: proto.ReconciledMessage.fromBuffer,
),
);
_reconciliation = MessageReconciliation(
output: _reconciledMessagesCubit!,
onError: (e, st) {
addError(e, st);
emit(AsyncValue.error(e, st));
});
output: _reconciledMessagesCubit!,
onError: (e, st) {
addError(e, st);
emit(AsyncValue.error(e, st));
},
);
_reconciledSubscription =
_reconciledMessagesCubit!.stream.listen(_updateReconciledMessagesState);
_reconciledSubscription = _reconciledMessagesCubit!.stream.listen(
_updateReconciledMessagesState,
);
_updateReconciledMessagesState(_reconciledMessagesCubit!.state);
}
@ -253,14 +326,22 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
// length.
// If tail is positive, the position is absolute from the head of the log
// If follow is enabled, the tail offset will update when the log changes
Future<void> setWindow(
{int? tail, int? count, bool? follow, bool forceRefresh = false}) async {
Future<void> setWindow({
int? tail,
int? count,
bool? follow,
bool forceRefresh = false,
}) async {
await _initWait();
// print('setWindow: tail=$tail count=$count, follow=$follow');
await _reconciledMessagesCubit!.setWindow(
tail: tail, count: count, follow: follow, forceRefresh: forceRefresh);
tail: tail,
count: count,
follow: follow,
forceRefresh: forceRefresh,
);
}
// Set a user-visible 'text' message with possible attachments
@ -313,17 +394,21 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
// Called when the reconciled messages window gets a change
void _updateReconciledMessagesState(
TableDBArrayProtobufBusyState<proto.ReconciledMessage> avmessages) {
TableDBArrayProtobufBusyState<proto.ReconciledMessage> avmessages,
) {
// Update the view
_renderState();
}
Future<Uint8List> _processMessageToSend(
proto.Message message, Uint8List? previousMessageBytes) async {
proto.Message message,
Uint8List? previousMessageBytes,
) async {
// It's possible we had an id from a previous
// operateAppendEventual attempt, so clear it and make a new one
message.id =
await _senderMessageIntegrity.generateMessageId(previousMessageBytes);
message.id = await _senderMessageIntegrity.generateMessageId(
previousMessageBytes,
);
// Now sign it
// await _senderMessageIntegrity.signMessage(
@ -337,16 +422,19 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
try {
await _sentMessagesDHTLog!.operateAppendEventual((writer) async {
// Get the previous message if we have one
var previousMessageBytes =
writer.length == 0 ? null : await writer.get(writer.length - 1);
var previousMessageBytes = writer.length == 0
? null
: await writer.get(writer.length - 1);
// Sign all messages
final processedMessages = messages.toList();
final byteMessages = <Uint8List>[];
for (final message in processedMessages) {
try {
final messageBytes =
await _processMessageToSend(message, previousMessageBytes);
final messageBytes = await _processMessageToSend(
message,
previousMessageBytes,
);
byteMessages.add(messageBytes);
previousMessageBytes = messageBytes;
} on Exception catch (e, st) {
@ -404,7 +492,8 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
// and the reconciled messages as the async state catches up
final renderedIds = <String>{};
var seqId = (reconciledMessages.windowTail == 0
var seqId =
(reconciledMessages.windowTail == 0
? reconciledMessages.length
: reconciledMessages.windowTail) -
reconciledMessages.windowElements.length;
@ -417,20 +506,21 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
//final sent = isLocal && sm != null;
//final sentOffline = isLocal && sm != null && sm.isOffline;
final sent = isLocal;
final sentOffline = false; //
const sentOffline = false; //
if (renderedIds.contains(message.authorUniqueIdString)) {
seqId++;
continue;
}
renderedElements.add(RenderStateElement(
seqId: seqId,
message: message,
isLocal: isLocal,
reconciledTimestamp: reconciledTimestamp,
sent: sent,
sentOffline: sentOffline,
));
renderedElements.add(
RenderStateElement(
seqId: seqId,
message: message,
isLocal: isLocal,
reconciledTimestamp: reconciledTimestamp,
sent: sent,
),
);
renderedIds.add(message.authorUniqueIdString);
seqId++;
}
@ -442,34 +532,44 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
seqId++;
continue;
}
renderedElements.add(RenderStateElement(
seqId: seqId,
message: m,
isLocal: true,
sent: true,
sentOffline: true,
));
renderedElements.add(
RenderStateElement(
seqId: seqId,
message: m,
isLocal: true,
sent: true,
sentOffline: true,
),
);
renderedIds.add(m.authorUniqueIdString);
seqId++;
}
// Render the state
final messages = renderedElements
.map((x) => MessageState(
.map(
(x) => MessageState(
seqId: x.seqId,
content: x.message,
sentTimestamp: Timestamp.fromInt64(x.message.timestamp),
reconciledTimestamp: x.reconciledTimestamp,
sendState: x.sendState))
sendState: x.sendState,
),
)
.toIList();
// Emit the rendered state
emit(AsyncValue.data(WindowState<MessageState>(
window: messages,
length: reconciledMessages.length,
windowTail: reconciledMessages.windowTail,
windowCount: reconciledMessages.windowCount,
follow: reconciledMessages.follow)));
emit(
AsyncValue.data(
WindowState<MessageState>(
window: messages,
length: reconciledMessages.length,
windowTail: reconciledMessages.windowTail,
windowCount: reconciledMessages.windowCount,
follow: reconciledMessages.follow,
),
),
);
}
void _sendMessage({required proto.Message message}) {
@ -505,45 +605,16 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
/////////////////////////////////////////////////////////////////////////
// Static utility functions
static Future<void> cleanupAndDeleteMessages(
{required RecordKey localConversationRecordKey}) async {
final recmsgdbname =
_reconciledMessagesTableDBName(localConversationRecordKey);
static Future<void> cleanupAndDeleteMessages({
required RecordKey localConversationRecordKey,
}) async {
final recmsgdbname = _reconciledMessagesTableDBName(
localConversationRecordKey,
);
await Veilid.instance.deleteTableDB(recmsgdbname);
}
static String _reconciledMessagesTableDBName(
RecordKey localConversationRecordKey) =>
'msg_${localConversationRecordKey.toString().replaceAll(':', '_')}';
/////////////////////////////////////////////////////////////////////////
final WaitSet<void, void> _initWait = WaitSet();
late final AccountInfo _accountInfo;
late final Author _localAuthor;
final Author _remoteAuthor;
final RecordKey _localConversationRecordKey;
final RecordKey _localMessagesRecordKey;
final RecordKey _remoteConversationRecordKey;
RecordKey? _remoteMessagesRecordKey;
late final VeilidCrypto _conversationCrypto;
late final MessageIntegrity _senderMessageIntegrity;
DHTLog? _sentMessagesDHTLog;
DHTLog? _rcvdMessagesDHTLog;
TableDBArrayProtobufCubit<proto.ReconciledMessage>? _reconciledMessagesCubit;
late final MessageReconciliation _reconciliation;
late final PersistentQueue<proto.Message> _unsentMessagesQueue;
StreamSubscription<void>? _sentSubscription;
StreamSubscription<void>? _rcvdSubscription;
StreamSubscription<TableDBArrayProtobufBusyState<proto.ReconciledMessage>>?
_reconciledSubscription;
final StreamController<Future<void> Function()> _commandController;
late final Future<void> _commandRunnerFut;
final _sspRemoteConversationRecordKey = SingleStateProcessor<RecordKey?>();
final _uuidGen = const UuidV4();
RecordKey localConversationRecordKey,
) => 'msg_${localConversationRecordKey.toString().replaceAll(':', '_')}';
}

View file

@ -1,6 +1,5 @@
// dart format width=80
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// coverage:ignore-file
// 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
@ -12,72 +11,53 @@ part of 'chat_component_state.dart';
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$ChatComponentState {
// Local user
fccore.User? get localUser; // Active remote users
IMap<fccore.UserID, fccore.User> get remoteUsers; // Historical remote users
IMap<fccore.UserID, fccore.User> get historicalRemoteUsers; // Unknown users
IMap<fccore.UserID, fccore.User> get unknownUsers; // Messages state
AsyncValue<WindowState<fccore.Message>>
get messageWindow; // Title of the chat
String get title;
fccore.User? get localUser;// Active remote users
IMap<fccore.UserID, fccore.User> get remoteUsers;// Historical remote users
IMap<fccore.UserID, fccore.User> get historicalRemoteUsers;// Unknown users
IMap<fccore.UserID, fccore.User> get unknownUsers;// Messages state
AsyncValue<WindowState<fccore.Message>> get messageWindow;// Title of the chat
String get title;
/// Create a copy of ChatComponentState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$ChatComponentStateCopyWith<ChatComponentState> get copyWith => _$ChatComponentStateCopyWithImpl<ChatComponentState>(this as ChatComponentState, _$identity);
/// Create a copy of ChatComponentState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$ChatComponentStateCopyWith<ChatComponentState> get copyWith =>
_$ChatComponentStateCopyWithImpl<ChatComponentState>(
this as ChatComponentState, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is ChatComponentState &&
(identical(other.localUser, localUser) ||
other.localUser == localUser) &&
(identical(other.remoteUsers, remoteUsers) ||
other.remoteUsers == remoteUsers) &&
(identical(other.historicalRemoteUsers, historicalRemoteUsers) ||
other.historicalRemoteUsers == historicalRemoteUsers) &&
(identical(other.unknownUsers, unknownUsers) ||
other.unknownUsers == unknownUsers) &&
(identical(other.messageWindow, messageWindow) ||
other.messageWindow == messageWindow) &&
(identical(other.title, title) || other.title == title));
}
@override
int get hashCode => Object.hash(runtimeType, localUser, remoteUsers,
historicalRemoteUsers, unknownUsers, messageWindow, title);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is ChatComponentState&&(identical(other.localUser, localUser) || other.localUser == localUser)&&(identical(other.remoteUsers, remoteUsers) || other.remoteUsers == remoteUsers)&&(identical(other.historicalRemoteUsers, historicalRemoteUsers) || other.historicalRemoteUsers == historicalRemoteUsers)&&(identical(other.unknownUsers, unknownUsers) || other.unknownUsers == unknownUsers)&&(identical(other.messageWindow, messageWindow) || other.messageWindow == messageWindow)&&(identical(other.title, title) || other.title == title));
}
@override
int get hashCode => Object.hash(runtimeType,localUser,remoteUsers,historicalRemoteUsers,unknownUsers,messageWindow,title);
@override
String toString() {
return 'ChatComponentState(localUser: $localUser, remoteUsers: $remoteUsers, historicalRemoteUsers: $historicalRemoteUsers, unknownUsers: $unknownUsers, messageWindow: $messageWindow, title: $title)';
}
@override
String toString() {
return 'ChatComponentState(localUser: $localUser, remoteUsers: $remoteUsers, historicalRemoteUsers: $historicalRemoteUsers, unknownUsers: $unknownUsers, messageWindow: $messageWindow, title: $title)';
}
}
/// @nodoc
abstract mixin class $ChatComponentStateCopyWith<$Res> {
factory $ChatComponentStateCopyWith(
ChatComponentState value, $Res Function(ChatComponentState) _then) =
_$ChatComponentStateCopyWithImpl;
@useResult
$Res call(
{fccore.User? localUser,
IMap<fccore.UserID, fccore.User> remoteUsers,
IMap<fccore.UserID, fccore.User> historicalRemoteUsers,
IMap<fccore.UserID, fccore.User> unknownUsers,
AsyncValue<WindowState<fccore.Message>> messageWindow,
String title});
abstract mixin class $ChatComponentStateCopyWith<$Res> {
factory $ChatComponentStateCopyWith(ChatComponentState value, $Res Function(ChatComponentState) _then) = _$ChatComponentStateCopyWithImpl;
@useResult
$Res call({
fccore.User? localUser, IMap<fccore.UserID, fccore.User> remoteUsers, IMap<fccore.UserID, fccore.User> historicalRemoteUsers, IMap<fccore.UserID, fccore.User> unknownUsers, AsyncValue<WindowState<fccore.Message>> messageWindow, String title
});
$UserCopyWith<$Res>? get localUser;$AsyncValueCopyWith<WindowState<Message>, $Res> get messageWindow;
$UserCopyWith<$Res>? get localUser;
$AsyncValueCopyWith<WindowState<Message>, $Res> get messageWindow;
}
/// @nodoc
class _$ChatComponentStateCopyWithImpl<$Res>
implements $ChatComponentStateCopyWith<$Res> {
@ -86,160 +66,225 @@ class _$ChatComponentStateCopyWithImpl<$Res>
final ChatComponentState _self;
final $Res Function(ChatComponentState) _then;
/// Create a copy of ChatComponentState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? localUser = freezed,
Object? remoteUsers = null,
Object? historicalRemoteUsers = null,
Object? unknownUsers = null,
Object? messageWindow = null,
Object? title = null,
}) {
return _then(_self.copyWith(
localUser: freezed == localUser
? _self.localUser
: localUser // ignore: cast_nullable_to_non_nullable
as fccore.User?,
remoteUsers: null == remoteUsers
? _self.remoteUsers
: remoteUsers // ignore: cast_nullable_to_non_nullable
as IMap<fccore.UserID, fccore.User>,
historicalRemoteUsers: null == historicalRemoteUsers
? _self.historicalRemoteUsers
: historicalRemoteUsers // ignore: cast_nullable_to_non_nullable
as IMap<fccore.UserID, fccore.User>,
unknownUsers: null == unknownUsers
? _self.unknownUsers
: unknownUsers // ignore: cast_nullable_to_non_nullable
as IMap<fccore.UserID, fccore.User>,
messageWindow: null == messageWindow
? _self.messageWindow
: messageWindow // ignore: cast_nullable_to_non_nullable
as AsyncValue<WindowState<fccore.Message>>,
title: null == title
? _self.title
: title // ignore: cast_nullable_to_non_nullable
as String,
));
}
/// Create a copy of ChatComponentState
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$UserCopyWith<$Res>? get localUser {
/// Create a copy of ChatComponentState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? localUser = freezed,Object? remoteUsers = null,Object? historicalRemoteUsers = null,Object? unknownUsers = null,Object? messageWindow = null,Object? title = null,}) {
return _then(_self.copyWith(
localUser: freezed == localUser ? _self.localUser : localUser // ignore: cast_nullable_to_non_nullable
as fccore.User?,remoteUsers: null == remoteUsers ? _self.remoteUsers : remoteUsers // ignore: cast_nullable_to_non_nullable
as IMap<fccore.UserID, fccore.User>,historicalRemoteUsers: null == historicalRemoteUsers ? _self.historicalRemoteUsers : historicalRemoteUsers // ignore: cast_nullable_to_non_nullable
as IMap<fccore.UserID, fccore.User>,unknownUsers: null == unknownUsers ? _self.unknownUsers : unknownUsers // ignore: cast_nullable_to_non_nullable
as IMap<fccore.UserID, fccore.User>,messageWindow: null == messageWindow ? _self.messageWindow : messageWindow // ignore: cast_nullable_to_non_nullable
as AsyncValue<WindowState<fccore.Message>>,title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable
as String,
));
}
/// Create a copy of ChatComponentState
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$UserCopyWith<$Res>? get localUser {
if (_self.localUser == null) {
return null;
}
return $UserCopyWith<$Res>(_self.localUser!, (value) {
return _then(_self.copyWith(localUser: value));
});
return null;
}
/// Create a copy of ChatComponentState
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$AsyncValueCopyWith<WindowState<Message>, $Res> get messageWindow {
return $AsyncValueCopyWith<WindowState<Message>, $Res>(_self.messageWindow,
(value) {
return _then(_self.copyWith(messageWindow: value));
});
}
return $UserCopyWith<$Res>(_self.localUser!, (value) {
return _then(_self.copyWith(localUser: value));
});
}/// Create a copy of ChatComponentState
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$AsyncValueCopyWith<WindowState<Message>, $Res> get messageWindow {
return $AsyncValueCopyWith<WindowState<Message>, $Res>(_self.messageWindow, (value) {
return _then(_self.copyWith(messageWindow: value));
});
}
}
/// Adds pattern-matching-related methods to [ChatComponentState].
extension ChatComponentStatePatterns on ChatComponentState {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _ChatComponentState value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _ChatComponentState() when $default != null:
return $default(_that);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _ChatComponentState value) $default,){
final _that = this;
switch (_that) {
case _ChatComponentState():
return $default(_that);}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _ChatComponentState value)? $default,){
final _that = this;
switch (_that) {
case _ChatComponentState() when $default != null:
return $default(_that);case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( fccore.User? localUser, IMap<fccore.UserID, fccore.User> remoteUsers, IMap<fccore.UserID, fccore.User> historicalRemoteUsers, IMap<fccore.UserID, fccore.User> unknownUsers, AsyncValue<WindowState<fccore.Message>> messageWindow, String title)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _ChatComponentState() when $default != null:
return $default(_that.localUser,_that.remoteUsers,_that.historicalRemoteUsers,_that.unknownUsers,_that.messageWindow,_that.title);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( fccore.User? localUser, IMap<fccore.UserID, fccore.User> remoteUsers, IMap<fccore.UserID, fccore.User> historicalRemoteUsers, IMap<fccore.UserID, fccore.User> unknownUsers, AsyncValue<WindowState<fccore.Message>> messageWindow, String title) $default,) {final _that = this;
switch (_that) {
case _ChatComponentState():
return $default(_that.localUser,_that.remoteUsers,_that.historicalRemoteUsers,_that.unknownUsers,_that.messageWindow,_that.title);}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( fccore.User? localUser, IMap<fccore.UserID, fccore.User> remoteUsers, IMap<fccore.UserID, fccore.User> historicalRemoteUsers, IMap<fccore.UserID, fccore.User> unknownUsers, AsyncValue<WindowState<fccore.Message>> messageWindow, String title)? $default,) {final _that = this;
switch (_that) {
case _ChatComponentState() when $default != null:
return $default(_that.localUser,_that.remoteUsers,_that.historicalRemoteUsers,_that.unknownUsers,_that.messageWindow,_that.title);case _:
return null;
}
}
}
/// @nodoc
class _ChatComponentState implements ChatComponentState {
const _ChatComponentState(
{required this.localUser,
required this.remoteUsers,
required this.historicalRemoteUsers,
required this.unknownUsers,
required this.messageWindow,
required this.title});
const _ChatComponentState({required this.localUser, required this.remoteUsers, required this.historicalRemoteUsers, required this.unknownUsers, required this.messageWindow, required this.title});
// Local user
@override
final fccore.User? localUser;
@override final fccore.User? localUser;
// Active remote users
@override
final IMap<fccore.UserID, fccore.User> remoteUsers;
@override final IMap<fccore.UserID, fccore.User> remoteUsers;
// Historical remote users
@override
final IMap<fccore.UserID, fccore.User> historicalRemoteUsers;
@override final IMap<fccore.UserID, fccore.User> historicalRemoteUsers;
// Unknown users
@override
final IMap<fccore.UserID, fccore.User> unknownUsers;
@override final IMap<fccore.UserID, fccore.User> unknownUsers;
// Messages state
@override
final AsyncValue<WindowState<fccore.Message>> messageWindow;
@override final AsyncValue<WindowState<fccore.Message>> messageWindow;
// Title of the chat
@override
final String title;
@override final String title;
/// Create a copy of ChatComponentState
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$ChatComponentStateCopyWith<_ChatComponentState> get copyWith =>
__$ChatComponentStateCopyWithImpl<_ChatComponentState>(this, _$identity);
/// Create a copy of ChatComponentState
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$ChatComponentStateCopyWith<_ChatComponentState> get copyWith => __$ChatComponentStateCopyWithImpl<_ChatComponentState>(this, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _ChatComponentState &&
(identical(other.localUser, localUser) ||
other.localUser == localUser) &&
(identical(other.remoteUsers, remoteUsers) ||
other.remoteUsers == remoteUsers) &&
(identical(other.historicalRemoteUsers, historicalRemoteUsers) ||
other.historicalRemoteUsers == historicalRemoteUsers) &&
(identical(other.unknownUsers, unknownUsers) ||
other.unknownUsers == unknownUsers) &&
(identical(other.messageWindow, messageWindow) ||
other.messageWindow == messageWindow) &&
(identical(other.title, title) || other.title == title));
}
@override
int get hashCode => Object.hash(runtimeType, localUser, remoteUsers,
historicalRemoteUsers, unknownUsers, messageWindow, title);
@override
String toString() {
return 'ChatComponentState(localUser: $localUser, remoteUsers: $remoteUsers, historicalRemoteUsers: $historicalRemoteUsers, unknownUsers: $unknownUsers, messageWindow: $messageWindow, title: $title)';
}
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _ChatComponentState&&(identical(other.localUser, localUser) || other.localUser == localUser)&&(identical(other.remoteUsers, remoteUsers) || other.remoteUsers == remoteUsers)&&(identical(other.historicalRemoteUsers, historicalRemoteUsers) || other.historicalRemoteUsers == historicalRemoteUsers)&&(identical(other.unknownUsers, unknownUsers) || other.unknownUsers == unknownUsers)&&(identical(other.messageWindow, messageWindow) || other.messageWindow == messageWindow)&&(identical(other.title, title) || other.title == title));
}
@override
int get hashCode => Object.hash(runtimeType,localUser,remoteUsers,historicalRemoteUsers,unknownUsers,messageWindow,title);
@override
String toString() {
return 'ChatComponentState(localUser: $localUser, remoteUsers: $remoteUsers, historicalRemoteUsers: $historicalRemoteUsers, unknownUsers: $unknownUsers, messageWindow: $messageWindow, title: $title)';
}
}
/// @nodoc
abstract mixin class _$ChatComponentStateCopyWith<$Res>
implements $ChatComponentStateCopyWith<$Res> {
factory _$ChatComponentStateCopyWith(
_ChatComponentState value, $Res Function(_ChatComponentState) _then) =
__$ChatComponentStateCopyWithImpl;
@override
@useResult
$Res call(
{fccore.User? localUser,
IMap<fccore.UserID, fccore.User> remoteUsers,
IMap<fccore.UserID, fccore.User> historicalRemoteUsers,
IMap<fccore.UserID, fccore.User> unknownUsers,
AsyncValue<WindowState<fccore.Message>> messageWindow,
String title});
abstract mixin class _$ChatComponentStateCopyWith<$Res> implements $ChatComponentStateCopyWith<$Res> {
factory _$ChatComponentStateCopyWith(_ChatComponentState value, $Res Function(_ChatComponentState) _then) = __$ChatComponentStateCopyWithImpl;
@override @useResult
$Res call({
fccore.User? localUser, IMap<fccore.UserID, fccore.User> remoteUsers, IMap<fccore.UserID, fccore.User> historicalRemoteUsers, IMap<fccore.UserID, fccore.User> unknownUsers, AsyncValue<WindowState<fccore.Message>> messageWindow, String title
});
@override $UserCopyWith<$Res>? get localUser;@override $AsyncValueCopyWith<WindowState<Message>, $Res> get messageWindow;
@override
$UserCopyWith<$Res>? get localUser;
@override
$AsyncValueCopyWith<WindowState<Message>, $Res> get messageWindow;
}
/// @nodoc
class __$ChatComponentStateCopyWithImpl<$Res>
implements _$ChatComponentStateCopyWith<$Res> {
@ -248,70 +293,42 @@ class __$ChatComponentStateCopyWithImpl<$Res>
final _ChatComponentState _self;
final $Res Function(_ChatComponentState) _then;
/// Create a copy of ChatComponentState
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$Res call({
Object? localUser = freezed,
Object? remoteUsers = null,
Object? historicalRemoteUsers = null,
Object? unknownUsers = null,
Object? messageWindow = null,
Object? title = null,
}) {
return _then(_ChatComponentState(
localUser: freezed == localUser
? _self.localUser
: localUser // ignore: cast_nullable_to_non_nullable
as fccore.User?,
remoteUsers: null == remoteUsers
? _self.remoteUsers
: remoteUsers // ignore: cast_nullable_to_non_nullable
as IMap<fccore.UserID, fccore.User>,
historicalRemoteUsers: null == historicalRemoteUsers
? _self.historicalRemoteUsers
: historicalRemoteUsers // ignore: cast_nullable_to_non_nullable
as IMap<fccore.UserID, fccore.User>,
unknownUsers: null == unknownUsers
? _self.unknownUsers
: unknownUsers // ignore: cast_nullable_to_non_nullable
as IMap<fccore.UserID, fccore.User>,
messageWindow: null == messageWindow
? _self.messageWindow
: messageWindow // ignore: cast_nullable_to_non_nullable
as AsyncValue<WindowState<fccore.Message>>,
title: null == title
? _self.title
: title // ignore: cast_nullable_to_non_nullable
as String,
));
}
/// Create a copy of ChatComponentState
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? localUser = freezed,Object? remoteUsers = null,Object? historicalRemoteUsers = null,Object? unknownUsers = null,Object? messageWindow = null,Object? title = null,}) {
return _then(_ChatComponentState(
localUser: freezed == localUser ? _self.localUser : localUser // ignore: cast_nullable_to_non_nullable
as fccore.User?,remoteUsers: null == remoteUsers ? _self.remoteUsers : remoteUsers // ignore: cast_nullable_to_non_nullable
as IMap<fccore.UserID, fccore.User>,historicalRemoteUsers: null == historicalRemoteUsers ? _self.historicalRemoteUsers : historicalRemoteUsers // ignore: cast_nullable_to_non_nullable
as IMap<fccore.UserID, fccore.User>,unknownUsers: null == unknownUsers ? _self.unknownUsers : unknownUsers // ignore: cast_nullable_to_non_nullable
as IMap<fccore.UserID, fccore.User>,messageWindow: null == messageWindow ? _self.messageWindow : messageWindow // ignore: cast_nullable_to_non_nullable
as AsyncValue<WindowState<fccore.Message>>,title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable
as String,
));
}
/// Create a copy of ChatComponentState
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$UserCopyWith<$Res>? get localUser {
/// Create a copy of ChatComponentState
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$UserCopyWith<$Res>? get localUser {
if (_self.localUser == null) {
return null;
}
return $UserCopyWith<$Res>(_self.localUser!, (value) {
return _then(_self.copyWith(localUser: value));
});
return null;
}
/// Create a copy of ChatComponentState
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$AsyncValueCopyWith<WindowState<Message>, $Res> get messageWindow {
return $AsyncValueCopyWith<WindowState<Message>, $Res>(_self.messageWindow,
(value) {
return _then(_self.copyWith(messageWindow: value));
});
}
return $UserCopyWith<$Res>(_self.localUser!, (value) {
return _then(_self.copyWith(localUser: value));
});
}/// Create a copy of ChatComponentState
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$AsyncValueCopyWith<WindowState<Message>, $Res> get messageWindow {
return $AsyncValueCopyWith<WindowState<Message>, $Res>(_self.messageWindow, (value) {
return _then(_self.copyWith(messageWindow: value));
});
}
}
// dart format on

View file

@ -1,6 +1,5 @@
// dart format width=80
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// coverage:ignore-file
// 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
@ -15,220 +14,270 @@ T _$identity<T>(T value) => value;
/// @nodoc
mixin _$MessageState implements DiagnosticableTreeMixin {
// Sequence number of the message for display purposes
int get seqId; // Content of the message
@JsonKey(fromJson: messageFromJson, toJson: messageToJson)
proto.Message get content; // Sent timestamp
Timestamp get sentTimestamp; // Reconciled timestamp
Timestamp? get reconciledTimestamp; // The state of the message
MessageSendState? get sendState;
/// Create a copy of MessageState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$MessageStateCopyWith<MessageState> get copyWith =>
_$MessageStateCopyWithImpl<MessageState>(
this as MessageState, _$identity);
// Sequence number of the message for display purposes
int get seqId;// Content of the message
@JsonKey(fromJson: messageFromJson, toJson: messageToJson) proto.Message get content;// Sent timestamp
Timestamp get sentTimestamp;// Reconciled timestamp
Timestamp? get reconciledTimestamp;// The state of the message
MessageSendState? get sendState;
/// Create a copy of MessageState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$MessageStateCopyWith<MessageState> get copyWith => _$MessageStateCopyWithImpl<MessageState>(this as MessageState, _$identity);
/// Serializes this MessageState to a JSON map.
Map<String, dynamic> toJson();
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
properties
..add(DiagnosticsProperty('type', 'MessageState'))
..add(DiagnosticsProperty('seqId', seqId))
..add(DiagnosticsProperty('content', content))
..add(DiagnosticsProperty('sentTimestamp', sentTimestamp))
..add(DiagnosticsProperty('reconciledTimestamp', reconciledTimestamp))
..add(DiagnosticsProperty('sendState', sendState));
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
properties
..add(DiagnosticsProperty('type', 'MessageState'))
..add(DiagnosticsProperty('seqId', seqId))..add(DiagnosticsProperty('content', content))..add(DiagnosticsProperty('sentTimestamp', sentTimestamp))..add(DiagnosticsProperty('reconciledTimestamp', reconciledTimestamp))..add(DiagnosticsProperty('sendState', sendState));
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is MessageState &&
(identical(other.seqId, seqId) || other.seqId == seqId) &&
(identical(other.content, content) || other.content == content) &&
(identical(other.sentTimestamp, sentTimestamp) ||
other.sentTimestamp == sentTimestamp) &&
(identical(other.reconciledTimestamp, reconciledTimestamp) ||
other.reconciledTimestamp == reconciledTimestamp) &&
(identical(other.sendState, sendState) ||
other.sendState == sendState));
}
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is MessageState&&(identical(other.seqId, seqId) || other.seqId == seqId)&&(identical(other.content, content) || other.content == content)&&(identical(other.sentTimestamp, sentTimestamp) || other.sentTimestamp == sentTimestamp)&&(identical(other.reconciledTimestamp, reconciledTimestamp) || other.reconciledTimestamp == reconciledTimestamp)&&(identical(other.sendState, sendState) || other.sendState == sendState));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,seqId,content,sentTimestamp,reconciledTimestamp,sendState);
@override
String toString({ DiagnosticLevel minLevel = DiagnosticLevel.info }) {
return 'MessageState(seqId: $seqId, content: $content, sentTimestamp: $sentTimestamp, reconciledTimestamp: $reconciledTimestamp, sendState: $sendState)';
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType, seqId, content, sentTimestamp,
reconciledTimestamp, sendState);
@override
String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) {
return 'MessageState(seqId: $seqId, content: $content, sentTimestamp: $sentTimestamp, reconciledTimestamp: $reconciledTimestamp, sendState: $sendState)';
}
}
/// @nodoc
abstract mixin class $MessageStateCopyWith<$Res> {
factory $MessageStateCopyWith(
MessageState value, $Res Function(MessageState) _then) =
_$MessageStateCopyWithImpl;
@useResult
$Res call(
{int seqId,
@JsonKey(fromJson: messageFromJson, toJson: messageToJson)
proto.Message content,
Timestamp sentTimestamp,
Timestamp? reconciledTimestamp,
MessageSendState? sendState});
}
abstract mixin class $MessageStateCopyWith<$Res> {
factory $MessageStateCopyWith(MessageState value, $Res Function(MessageState) _then) = _$MessageStateCopyWithImpl;
@useResult
$Res call({
int seqId,@JsonKey(fromJson: messageFromJson, toJson: messageToJson) proto.Message content, Timestamp sentTimestamp, Timestamp? reconciledTimestamp, MessageSendState? sendState
});
}
/// @nodoc
class _$MessageStateCopyWithImpl<$Res> implements $MessageStateCopyWith<$Res> {
class _$MessageStateCopyWithImpl<$Res>
implements $MessageStateCopyWith<$Res> {
_$MessageStateCopyWithImpl(this._self, this._then);
final MessageState _self;
final $Res Function(MessageState) _then;
/// Create a copy of MessageState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? seqId = null,
Object? content = null,
Object? sentTimestamp = null,
Object? reconciledTimestamp = freezed,
Object? sendState = freezed,
}) {
return _then(_self.copyWith(
seqId: null == seqId
? _self.seqId
: seqId // ignore: cast_nullable_to_non_nullable
as int,
content: null == content
? _self.content
: content // ignore: cast_nullable_to_non_nullable
as proto.Message,
sentTimestamp: null == sentTimestamp
? _self.sentTimestamp
: sentTimestamp // ignore: cast_nullable_to_non_nullable
as Timestamp,
reconciledTimestamp: freezed == reconciledTimestamp
? _self.reconciledTimestamp
: reconciledTimestamp // ignore: cast_nullable_to_non_nullable
as Timestamp?,
sendState: freezed == sendState
? _self.sendState
: sendState // ignore: cast_nullable_to_non_nullable
as MessageSendState?,
));
}
/// Create a copy of MessageState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? seqId = null,Object? content = null,Object? sentTimestamp = null,Object? reconciledTimestamp = freezed,Object? sendState = freezed,}) {
return _then(_self.copyWith(
seqId: null == seqId ? _self.seqId : seqId // ignore: cast_nullable_to_non_nullable
as int,content: null == content ? _self.content : content // ignore: cast_nullable_to_non_nullable
as proto.Message,sentTimestamp: null == sentTimestamp ? _self.sentTimestamp : sentTimestamp // ignore: cast_nullable_to_non_nullable
as Timestamp,reconciledTimestamp: freezed == reconciledTimestamp ? _self.reconciledTimestamp : reconciledTimestamp // ignore: cast_nullable_to_non_nullable
as Timestamp?,sendState: freezed == sendState ? _self.sendState : sendState // ignore: cast_nullable_to_non_nullable
as MessageSendState?,
));
}
}
/// Adds pattern-matching-related methods to [MessageState].
extension MessageStatePatterns on MessageState {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _MessageState value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _MessageState() when $default != null:
return $default(_that);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _MessageState value) $default,){
final _that = this;
switch (_that) {
case _MessageState():
return $default(_that);}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _MessageState value)? $default,){
final _that = this;
switch (_that) {
case _MessageState() when $default != null:
return $default(_that);case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( int seqId, @JsonKey(fromJson: messageFromJson, toJson: messageToJson) proto.Message content, Timestamp sentTimestamp, Timestamp? reconciledTimestamp, MessageSendState? sendState)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _MessageState() when $default != null:
return $default(_that.seqId,_that.content,_that.sentTimestamp,_that.reconciledTimestamp,_that.sendState);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( int seqId, @JsonKey(fromJson: messageFromJson, toJson: messageToJson) proto.Message content, Timestamp sentTimestamp, Timestamp? reconciledTimestamp, MessageSendState? sendState) $default,) {final _that = this;
switch (_that) {
case _MessageState():
return $default(_that.seqId,_that.content,_that.sentTimestamp,_that.reconciledTimestamp,_that.sendState);}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( int seqId, @JsonKey(fromJson: messageFromJson, toJson: messageToJson) proto.Message content, Timestamp sentTimestamp, Timestamp? reconciledTimestamp, MessageSendState? sendState)? $default,) {final _that = this;
switch (_that) {
case _MessageState() when $default != null:
return $default(_that.seqId,_that.content,_that.sentTimestamp,_that.reconciledTimestamp,_that.sendState);case _:
return null;
}
}
}
/// @nodoc
@JsonSerializable()
class _MessageState with DiagnosticableTreeMixin implements MessageState {
const _MessageState(
{required this.seqId,
@JsonKey(fromJson: messageFromJson, toJson: messageToJson)
required this.content,
required this.sentTimestamp,
required this.reconciledTimestamp,
required this.sendState});
factory _MessageState.fromJson(Map<String, dynamic> json) =>
_$MessageStateFromJson(json);
const _MessageState({required this.seqId, @JsonKey(fromJson: messageFromJson, toJson: messageToJson) required this.content, required this.sentTimestamp, required this.reconciledTimestamp, required this.sendState});
factory _MessageState.fromJson(Map<String, dynamic> json) => _$MessageStateFromJson(json);
// Sequence number of the message for display purposes
@override
final int seqId;
@override final int seqId;
// Content of the message
@override
@JsonKey(fromJson: messageFromJson, toJson: messageToJson)
final proto.Message content;
@override@JsonKey(fromJson: messageFromJson, toJson: messageToJson) final proto.Message content;
// Sent timestamp
@override
final Timestamp sentTimestamp;
@override final Timestamp sentTimestamp;
// Reconciled timestamp
@override
final Timestamp? reconciledTimestamp;
@override final Timestamp? reconciledTimestamp;
// The state of the message
@override
final MessageSendState? sendState;
@override final MessageSendState? sendState;
/// Create a copy of MessageState
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$MessageStateCopyWith<_MessageState> get copyWith =>
__$MessageStateCopyWithImpl<_MessageState>(this, _$identity);
/// Create a copy of MessageState
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$MessageStateCopyWith<_MessageState> get copyWith => __$MessageStateCopyWithImpl<_MessageState>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$MessageStateToJson(
this,
);
}
@override
Map<String, dynamic> toJson() {
return _$MessageStateToJson(this, );
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
properties
..add(DiagnosticsProperty('type', 'MessageState'))
..add(DiagnosticsProperty('seqId', seqId))..add(DiagnosticsProperty('content', content))..add(DiagnosticsProperty('sentTimestamp', sentTimestamp))..add(DiagnosticsProperty('reconciledTimestamp', reconciledTimestamp))..add(DiagnosticsProperty('sendState', sendState));
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
properties
..add(DiagnosticsProperty('type', 'MessageState'))
..add(DiagnosticsProperty('seqId', seqId))
..add(DiagnosticsProperty('content', content))
..add(DiagnosticsProperty('sentTimestamp', sentTimestamp))
..add(DiagnosticsProperty('reconciledTimestamp', reconciledTimestamp))
..add(DiagnosticsProperty('sendState', sendState));
}
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _MessageState&&(identical(other.seqId, seqId) || other.seqId == seqId)&&(identical(other.content, content) || other.content == content)&&(identical(other.sentTimestamp, sentTimestamp) || other.sentTimestamp == sentTimestamp)&&(identical(other.reconciledTimestamp, reconciledTimestamp) || other.reconciledTimestamp == reconciledTimestamp)&&(identical(other.sendState, sendState) || other.sendState == sendState));
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _MessageState &&
(identical(other.seqId, seqId) || other.seqId == seqId) &&
(identical(other.content, content) || other.content == content) &&
(identical(other.sentTimestamp, sentTimestamp) ||
other.sentTimestamp == sentTimestamp) &&
(identical(other.reconciledTimestamp, reconciledTimestamp) ||
other.reconciledTimestamp == reconciledTimestamp) &&
(identical(other.sendState, sendState) ||
other.sendState == sendState));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,seqId,content,sentTimestamp,reconciledTimestamp,sendState);
@override
String toString({ DiagnosticLevel minLevel = DiagnosticLevel.info }) {
return 'MessageState(seqId: $seqId, content: $content, sentTimestamp: $sentTimestamp, reconciledTimestamp: $reconciledTimestamp, sendState: $sendState)';
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType, seqId, content, sentTimestamp,
reconciledTimestamp, sendState);
@override
String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) {
return 'MessageState(seqId: $seqId, content: $content, sentTimestamp: $sentTimestamp, reconciledTimestamp: $reconciledTimestamp, sendState: $sendState)';
}
}
/// @nodoc
abstract mixin class _$MessageStateCopyWith<$Res>
implements $MessageStateCopyWith<$Res> {
factory _$MessageStateCopyWith(
_MessageState value, $Res Function(_MessageState) _then) =
__$MessageStateCopyWithImpl;
@override
@useResult
$Res call(
{int seqId,
@JsonKey(fromJson: messageFromJson, toJson: messageToJson)
proto.Message content,
Timestamp sentTimestamp,
Timestamp? reconciledTimestamp,
MessageSendState? sendState});
}
abstract mixin class _$MessageStateCopyWith<$Res> implements $MessageStateCopyWith<$Res> {
factory _$MessageStateCopyWith(_MessageState value, $Res Function(_MessageState) _then) = __$MessageStateCopyWithImpl;
@override @useResult
$Res call({
int seqId,@JsonKey(fromJson: messageFromJson, toJson: messageToJson) proto.Message content, Timestamp sentTimestamp, Timestamp? reconciledTimestamp, MessageSendState? sendState
});
}
/// @nodoc
class __$MessageStateCopyWithImpl<$Res>
implements _$MessageStateCopyWith<$Res> {
@ -237,40 +286,20 @@ class __$MessageStateCopyWithImpl<$Res>
final _MessageState _self;
final $Res Function(_MessageState) _then;
/// Create a copy of MessageState
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$Res call({
Object? seqId = null,
Object? content = null,
Object? sentTimestamp = null,
Object? reconciledTimestamp = freezed,
Object? sendState = freezed,
}) {
return _then(_MessageState(
seqId: null == seqId
? _self.seqId
: seqId // ignore: cast_nullable_to_non_nullable
as int,
content: null == content
? _self.content
: content // ignore: cast_nullable_to_non_nullable
as proto.Message,
sentTimestamp: null == sentTimestamp
? _self.sentTimestamp
: sentTimestamp // ignore: cast_nullable_to_non_nullable
as Timestamp,
reconciledTimestamp: freezed == reconciledTimestamp
? _self.reconciledTimestamp
: reconciledTimestamp // ignore: cast_nullable_to_non_nullable
as Timestamp?,
sendState: freezed == sendState
? _self.sendState
: sendState // ignore: cast_nullable_to_non_nullable
as MessageSendState?,
));
}
/// Create a copy of MessageState
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? seqId = null,Object? content = null,Object? sentTimestamp = null,Object? reconciledTimestamp = freezed,Object? sendState = freezed,}) {
return _then(_MessageState(
seqId: null == seqId ? _self.seqId : seqId // ignore: cast_nullable_to_non_nullable
as int,content: null == content ? _self.content : content // ignore: cast_nullable_to_non_nullable
as proto.Message,sentTimestamp: null == sentTimestamp ? _self.sentTimestamp : sentTimestamp // ignore: cast_nullable_to_non_nullable
as Timestamp,reconciledTimestamp: freezed == reconciledTimestamp ? _self.reconciledTimestamp : reconciledTimestamp // ignore: cast_nullable_to_non_nullable
as Timestamp?,sendState: freezed == sendState ? _self.sendState : sendState // ignore: cast_nullable_to_non_nullable
as MessageSendState?,
));
}
}
// dart format on

View file

@ -1,6 +1,5 @@
// dart format width=80
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// coverage:ignore-file
// 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
@ -12,254 +11,289 @@ part of 'window_state.dart';
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$WindowState<T> implements DiagnosticableTreeMixin {
// List of objects in the window
IList<T> get window; // Total number of objects (windowTail max)
int get length; // One past the end of the last element
int get windowTail; // The total number of elements to try to keep in the window
int get windowCount; // If we should have the tail following the array
bool get follow;
IList<T> get window;// Total number of objects (windowTail max)
int get length;// One past the end of the last element
int get windowTail;// The total number of elements to try to keep in the window
int get windowCount;// If we should have the tail following the array
bool get follow;
/// Create a copy of WindowState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$WindowStateCopyWith<T, WindowState<T>> get copyWith => _$WindowStateCopyWithImpl<T, WindowState<T>>(this as WindowState<T>, _$identity);
/// Create a copy of WindowState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$WindowStateCopyWith<T, WindowState<T>> get copyWith =>
_$WindowStateCopyWithImpl<T, WindowState<T>>(
this as WindowState<T>, _$identity);
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
properties
..add(DiagnosticsProperty('type', 'WindowState<$T>'))
..add(DiagnosticsProperty('window', window))
..add(DiagnosticsProperty('length', length))
..add(DiagnosticsProperty('windowTail', windowTail))
..add(DiagnosticsProperty('windowCount', windowCount))
..add(DiagnosticsProperty('follow', follow));
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
properties
..add(DiagnosticsProperty('type', 'WindowState<$T>'))
..add(DiagnosticsProperty('window', window))..add(DiagnosticsProperty('length', length))..add(DiagnosticsProperty('windowTail', windowTail))..add(DiagnosticsProperty('windowCount', windowCount))..add(DiagnosticsProperty('follow', follow));
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is WindowState<T> &&
const DeepCollectionEquality().equals(other.window, window) &&
(identical(other.length, length) || other.length == length) &&
(identical(other.windowTail, windowTail) ||
other.windowTail == windowTail) &&
(identical(other.windowCount, windowCount) ||
other.windowCount == windowCount) &&
(identical(other.follow, follow) || other.follow == follow));
}
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is WindowState<T>&&const DeepCollectionEquality().equals(other.window, window)&&(identical(other.length, length) || other.length == length)&&(identical(other.windowTail, windowTail) || other.windowTail == windowTail)&&(identical(other.windowCount, windowCount) || other.windowCount == windowCount)&&(identical(other.follow, follow) || other.follow == follow));
}
@override
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(window),length,windowTail,windowCount,follow);
@override
String toString({ DiagnosticLevel minLevel = DiagnosticLevel.info }) {
return 'WindowState<$T>(window: $window, length: $length, windowTail: $windowTail, windowCount: $windowCount, follow: $follow)';
}
@override
int get hashCode => Object.hash(
runtimeType,
const DeepCollectionEquality().hash(window),
length,
windowTail,
windowCount,
follow);
@override
String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) {
return 'WindowState<$T>(window: $window, length: $length, windowTail: $windowTail, windowCount: $windowCount, follow: $follow)';
}
}
/// @nodoc
abstract mixin class $WindowStateCopyWith<T, $Res> {
factory $WindowStateCopyWith(
WindowState<T> value, $Res Function(WindowState<T>) _then) =
_$WindowStateCopyWithImpl;
@useResult
$Res call(
{IList<T> window,
int length,
int windowTail,
int windowCount,
bool follow});
}
abstract mixin class $WindowStateCopyWith<T,$Res> {
factory $WindowStateCopyWith(WindowState<T> value, $Res Function(WindowState<T>) _then) = _$WindowStateCopyWithImpl;
@useResult
$Res call({
IList<T> window, int length, int windowTail, int windowCount, bool follow
});
}
/// @nodoc
class _$WindowStateCopyWithImpl<T, $Res>
class _$WindowStateCopyWithImpl<T,$Res>
implements $WindowStateCopyWith<T, $Res> {
_$WindowStateCopyWithImpl(this._self, this._then);
final WindowState<T> _self;
final $Res Function(WindowState<T>) _then;
/// Create a copy of WindowState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? window = null,
Object? length = null,
Object? windowTail = null,
Object? windowCount = null,
Object? follow = null,
}) {
return _then(_self.copyWith(
window: null == window
? _self.window
: window // ignore: cast_nullable_to_non_nullable
as IList<T>,
length: null == length
? _self.length
: length // ignore: cast_nullable_to_non_nullable
as int,
windowTail: null == windowTail
? _self.windowTail
: windowTail // ignore: cast_nullable_to_non_nullable
as int,
windowCount: null == windowCount
? _self.windowCount
: windowCount // ignore: cast_nullable_to_non_nullable
as int,
follow: null == follow
? _self.follow
: follow // ignore: cast_nullable_to_non_nullable
as bool,
));
}
/// Create a copy of WindowState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? window = null,Object? length = null,Object? windowTail = null,Object? windowCount = null,Object? follow = null,}) {
return _then(_self.copyWith(
window: null == window ? _self.window : window // ignore: cast_nullable_to_non_nullable
as IList<T>,length: null == length ? _self.length : length // ignore: cast_nullable_to_non_nullable
as int,windowTail: null == windowTail ? _self.windowTail : windowTail // ignore: cast_nullable_to_non_nullable
as int,windowCount: null == windowCount ? _self.windowCount : windowCount // ignore: cast_nullable_to_non_nullable
as int,follow: null == follow ? _self.follow : follow // ignore: cast_nullable_to_non_nullable
as bool,
));
}
}
/// Adds pattern-matching-related methods to [WindowState].
extension WindowStatePatterns<T> on WindowState<T> {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _WindowState<T> value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _WindowState() when $default != null:
return $default(_that);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _WindowState<T> value) $default,){
final _that = this;
switch (_that) {
case _WindowState():
return $default(_that);}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _WindowState<T> value)? $default,){
final _that = this;
switch (_that) {
case _WindowState() when $default != null:
return $default(_that);case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( IList<T> window, int length, int windowTail, int windowCount, bool follow)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _WindowState() when $default != null:
return $default(_that.window,_that.length,_that.windowTail,_that.windowCount,_that.follow);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( IList<T> window, int length, int windowTail, int windowCount, bool follow) $default,) {final _that = this;
switch (_that) {
case _WindowState():
return $default(_that.window,_that.length,_that.windowTail,_that.windowCount,_that.follow);}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( IList<T> window, int length, int windowTail, int windowCount, bool follow)? $default,) {final _that = this;
switch (_that) {
case _WindowState() when $default != null:
return $default(_that.window,_that.length,_that.windowTail,_that.windowCount,_that.follow);case _:
return null;
}
}
}
/// @nodoc
class _WindowState<T> with DiagnosticableTreeMixin implements WindowState<T> {
const _WindowState(
{required this.window,
required this.length,
required this.windowTail,
required this.windowCount,
required this.follow});
const _WindowState({required this.window, required this.length, required this.windowTail, required this.windowCount, required this.follow});
// List of objects in the window
@override
final IList<T> window;
@override final IList<T> window;
// Total number of objects (windowTail max)
@override
final int length;
@override final int length;
// One past the end of the last element
@override
final int windowTail;
@override final int windowTail;
// The total number of elements to try to keep in the window
@override
final int windowCount;
@override final int windowCount;
// If we should have the tail following the array
@override
final bool follow;
@override final bool follow;
/// Create a copy of WindowState
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$WindowStateCopyWith<T, _WindowState<T>> get copyWith =>
__$WindowStateCopyWithImpl<T, _WindowState<T>>(this, _$identity);
/// Create a copy of WindowState
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$WindowStateCopyWith<T, _WindowState<T>> get copyWith => __$WindowStateCopyWithImpl<T, _WindowState<T>>(this, _$identity);
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
properties
..add(DiagnosticsProperty('type', 'WindowState<$T>'))
..add(DiagnosticsProperty('window', window))
..add(DiagnosticsProperty('length', length))
..add(DiagnosticsProperty('windowTail', windowTail))
..add(DiagnosticsProperty('windowCount', windowCount))
..add(DiagnosticsProperty('follow', follow));
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _WindowState<T> &&
const DeepCollectionEquality().equals(other.window, window) &&
(identical(other.length, length) || other.length == length) &&
(identical(other.windowTail, windowTail) ||
other.windowTail == windowTail) &&
(identical(other.windowCount, windowCount) ||
other.windowCount == windowCount) &&
(identical(other.follow, follow) || other.follow == follow));
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
properties
..add(DiagnosticsProperty('type', 'WindowState<$T>'))
..add(DiagnosticsProperty('window', window))..add(DiagnosticsProperty('length', length))..add(DiagnosticsProperty('windowTail', windowTail))..add(DiagnosticsProperty('windowCount', windowCount))..add(DiagnosticsProperty('follow', follow));
}
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _WindowState<T>&&const DeepCollectionEquality().equals(other.window, window)&&(identical(other.length, length) || other.length == length)&&(identical(other.windowTail, windowTail) || other.windowTail == windowTail)&&(identical(other.windowCount, windowCount) || other.windowCount == windowCount)&&(identical(other.follow, follow) || other.follow == follow));
}
@override
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(window),length,windowTail,windowCount,follow);
@override
String toString({ DiagnosticLevel minLevel = DiagnosticLevel.info }) {
return 'WindowState<$T>(window: $window, length: $length, windowTail: $windowTail, windowCount: $windowCount, follow: $follow)';
}
@override
int get hashCode => Object.hash(
runtimeType,
const DeepCollectionEquality().hash(window),
length,
windowTail,
windowCount,
follow);
@override
String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) {
return 'WindowState<$T>(window: $window, length: $length, windowTail: $windowTail, windowCount: $windowCount, follow: $follow)';
}
}
/// @nodoc
abstract mixin class _$WindowStateCopyWith<T, $Res>
implements $WindowStateCopyWith<T, $Res> {
factory _$WindowStateCopyWith(
_WindowState<T> value, $Res Function(_WindowState<T>) _then) =
__$WindowStateCopyWithImpl;
@override
@useResult
$Res call(
{IList<T> window,
int length,
int windowTail,
int windowCount,
bool follow});
}
abstract mixin class _$WindowStateCopyWith<T,$Res> implements $WindowStateCopyWith<T, $Res> {
factory _$WindowStateCopyWith(_WindowState<T> value, $Res Function(_WindowState<T>) _then) = __$WindowStateCopyWithImpl;
@override @useResult
$Res call({
IList<T> window, int length, int windowTail, int windowCount, bool follow
});
}
/// @nodoc
class __$WindowStateCopyWithImpl<T, $Res>
class __$WindowStateCopyWithImpl<T,$Res>
implements _$WindowStateCopyWith<T, $Res> {
__$WindowStateCopyWithImpl(this._self, this._then);
final _WindowState<T> _self;
final $Res Function(_WindowState<T>) _then;
/// Create a copy of WindowState
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$Res call({
Object? window = null,
Object? length = null,
Object? windowTail = null,
Object? windowCount = null,
Object? follow = null,
}) {
return _then(_WindowState<T>(
window: null == window
? _self.window
: window // ignore: cast_nullable_to_non_nullable
as IList<T>,
length: null == length
? _self.length
: length // ignore: cast_nullable_to_non_nullable
as int,
windowTail: null == windowTail
? _self.windowTail
: windowTail // ignore: cast_nullable_to_non_nullable
as int,
windowCount: null == windowCount
? _self.windowCount
: windowCount // ignore: cast_nullable_to_non_nullable
as int,
follow: null == follow
? _self.follow
: follow // ignore: cast_nullable_to_non_nullable
as bool,
));
}
/// Create a copy of WindowState
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? window = null,Object? length = null,Object? windowTail = null,Object? windowCount = null,Object? follow = null,}) {
return _then(_WindowState<T>(
window: null == window ? _self.window : window // ignore: cast_nullable_to_non_nullable
as IList<T>,length: null == length ? _self.length : length // ignore: cast_nullable_to_non_nullable
as int,windowTail: null == windowTail ? _self.windowTail : windowTail // ignore: cast_nullable_to_non_nullable
as int,windowCount: null == windowCount ? _self.windowCount : windowCount // ignore: cast_nullable_to_non_nullable
as int,follow: null == follow ? _self.follow : follow // ignore: cast_nullable_to_non_nullable
as bool,
));
}
}
// dart format on

View file

@ -6,7 +6,6 @@ import 'package:flutter/services.dart';
import 'package:flutter_chat_ui/flutter_chat_ui.dart';
// Typedefs need to come out
// ignore: implementation_imports
import 'package:flutter_chat_ui/src/utils/typedefs.dart';
import 'package:provider/provider.dart';
import '../../../theme/theme.dart';
@ -19,44 +18,6 @@ enum ShiftEnterAction { newline, send }
/// Includes a text input field, an optional attachment button,
/// and a send button.
class VcComposerWidget extends StatefulWidget {
/// Creates a message composer widget.
const VcComposerWidget({
super.key,
this.textEditingController,
this.left = 0,
this.right = 0,
this.top,
this.bottom = 0,
this.sigmaX = 20,
this.sigmaY = 20,
this.padding = const EdgeInsets.all(8),
this.attachmentIcon = const Icon(Icons.attachment),
this.sendIcon = const Icon(Icons.send),
this.gap = 8,
this.inputBorder,
this.filled,
this.topWidget,
this.handleSafeArea = true,
this.backgroundColor,
this.attachmentIconColor,
this.sendIconColor,
this.hintColor,
this.textColor,
this.inputFillColor,
this.hintText = 'Type a message',
this.keyboardAppearance,
this.autocorrect,
this.autofocus = false,
this.textCapitalization = TextCapitalization.sentences,
this.keyboardType,
this.textInputAction = TextInputAction.newline,
this.shiftEnterAction = ShiftEnterAction.send,
this.focusNode,
this.maxLength,
this.minLines = 1,
this.maxLines = 3,
});
/// Optional controller for the text input field.
final TextEditingController? textEditingController;
@ -156,6 +117,44 @@ class VcComposerWidget extends StatefulWidget {
/// Maximum number of lines the input field can expand to.
final int? maxLines;
/// Creates a message composer widget.
const VcComposerWidget({
super.key,
this.textEditingController,
this.left = 0,
this.right = 0,
this.top,
this.bottom = 0,
this.sigmaX = 20,
this.sigmaY = 20,
this.padding = const EdgeInsets.all(8),
this.attachmentIcon = const Icon(Icons.attachment),
this.sendIcon = const Icon(Icons.send),
this.gap = 8,
this.inputBorder,
this.filled,
this.topWidget,
this.handleSafeArea = true,
this.backgroundColor,
this.attachmentIconColor,
this.sendIconColor,
this.hintColor,
this.textColor,
this.inputFillColor,
this.hintText = 'Type a message',
this.keyboardAppearance,
this.autocorrect,
this.autofocus = false,
this.textCapitalization = TextCapitalization.sentences,
this.keyboardType,
this.textInputAction = TextInputAction.newline,
this.shiftEnterAction = ShiftEnterAction.send,
this.focusNode,
this.maxLength,
this.minLines = 1,
this.maxLines = 3,
});
@override
State<VcComposerWidget> createState() => _VcComposerState();
@ -163,8 +162,12 @@ class VcComposerWidget extends StatefulWidget {
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties
..add(DiagnosticsProperty<TextEditingController?>(
'textEditingController', textEditingController))
..add(
DiagnosticsProperty<TextEditingController?>(
'textEditingController',
textEditingController,
),
)
..add(DoubleProperty('left', left))
..add(DoubleProperty('right', right))
..add(DoubleProperty('top', top))
@ -186,12 +189,17 @@ class VcComposerWidget extends StatefulWidget {
..add(EnumProperty<Brightness?>('keyboardAppearance', keyboardAppearance))
..add(DiagnosticsProperty<bool?>('autocorrect', autocorrect))
..add(DiagnosticsProperty<bool>('autofocus', autofocus))
..add(EnumProperty<TextCapitalization>(
'textCapitalization', textCapitalization))
..add(
EnumProperty<TextCapitalization>(
'textCapitalization',
textCapitalization,
),
)
..add(DiagnosticsProperty<TextInputType?>('keyboardType', keyboardType))
..add(EnumProperty<TextInputAction>('textInputAction', textInputAction))
..add(
EnumProperty<ShiftEnterAction>('shiftEnterAction', shiftEnterAction))
EnumProperty<ShiftEnterAction>('shiftEnterAction', shiftEnterAction),
)
..add(DiagnosticsProperty<FocusNode?>('focusNode', focusNode))
..add(IntProperty('maxLength', maxLength))
..add(IntProperty('minLines', minLines))
@ -267,8 +275,9 @@ class _VcComposerState extends State<VcComposerWidget> {
@override
Widget build(BuildContext context) {
final bottomSafeArea =
widget.handleSafeArea ? MediaQuery.of(context).padding.bottom : 0.0;
final bottomSafeArea = widget.handleSafeArea
? MediaQuery.of(context).padding.bottom
: 0.0;
final onAttachmentTap = context.read<OnAttachmentTapCallback?>();
final theme = Theme.of(context);
final scaleTheme = theme.extension<ScaleTheme>()!;
@ -279,8 +288,9 @@ class _VcComposerState extends State<VcComposerWidget> {
final scaleChatTheme = scaleTheme.chatTheme();
final chatTheme = scaleChatTheme.chatTheme;
final suffixTextStyle =
textTheme.bodySmall!.copyWith(color: scale.subtleText);
final suffixTextStyle = textTheme.bodySmall!.copyWith(
color: scale.subtleText,
);
return Positioned(
left: widget.left,
@ -291,21 +301,22 @@ class _VcComposerState extends State<VcComposerWidget> {
child: DecoratedBox(
key: _key,
decoration: BoxDecoration(
border: config.preferBorders
? Border(top: BorderSide(color: scale.border, width: 2))
: null,
color: config.preferBorders
? scale.elementBackground
: scale.border),
border: config.preferBorders
? Border(top: BorderSide(color: scale.border, width: 2))
: null,
color: config.preferBorders
? scale.elementBackground
: scale.border,
),
child: Column(
children: [
if (widget.topWidget != null) widget.topWidget!,
Padding(
padding: widget.handleSafeArea
? (widget.padding?.add(
EdgeInsets.only(bottom: bottomSafeArea),
) ??
EdgeInsets.only(bottom: bottomSafeArea))
EdgeInsets.only(bottom: bottomSafeArea),
) ??
EdgeInsets.only(bottom: bottomSafeArea))
: (widget.padding ?? EdgeInsets.zero),
child: Row(
children: [
@ -313,7 +324,8 @@ class _VcComposerState extends State<VcComposerWidget> {
onAttachmentTap != null)
IconButton(
icon: widget.attachmentIcon!,
color: widget.attachmentIconColor ??
color:
widget.attachmentIconColor ??
chatTheme.colors.onSurface.withValues(alpha: 0.5),
onPressed: onAttachmentTap,
)
@ -324,46 +336,58 @@ class _VcComposerState extends State<VcComposerWidget> {
child: TextField(
controller: _textController,
decoration: InputDecoration(
filled: widget.filled ?? !config.preferBorders,
fillColor: widget.inputFillColor ??
scheme.primaryScale.subtleBackground,
isDense: true,
contentPadding:
const EdgeInsets.fromLTRB(8, 8, 8, 8),
disabledBorder: OutlineInputBorder(
borderSide: config.preferBorders
? BorderSide(
color: scheme.grayScale.border,
width: 2)
: BorderSide.none,
borderRadius: BorderRadius.all(Radius.circular(
8 * config.borderRadiusScale))),
enabledBorder: OutlineInputBorder(
borderSide: config.preferBorders
? BorderSide(
color: scheme.primaryScale.border,
width: 2)
: BorderSide.none,
borderRadius: BorderRadius.all(Radius.circular(
8 * config.borderRadiusScale))),
focusedBorder: OutlineInputBorder(
borderSide: config.preferBorders
? BorderSide(
color: scheme.primaryScale.border,
width: 2)
: BorderSide.none,
borderRadius: BorderRadius.all(Radius.circular(
8 * config.borderRadiusScale))),
hintText: widget.hintText,
hintMaxLines: 1,
hintStyle: chatTheme.typography.bodyMedium.copyWith(
color: widget.hintColor ??
chatTheme.colors.onSurface
.withValues(alpha: 0.5),
filled: widget.filled ?? !config.preferBorders,
fillColor:
widget.inputFillColor ??
scheme.primaryScale.subtleBackground,
isDense: true,
contentPadding: const EdgeInsets.fromLTRB(8, 8, 8, 8),
disabledBorder: OutlineInputBorder(
borderSide: config.preferBorders
? BorderSide(
color: scheme.grayScale.border,
width: 2,
)
: BorderSide.none,
borderRadius: BorderRadius.all(
Radius.circular(8 * config.borderRadiusScale),
),
border: widget.inputBorder,
hoverColor: Colors.transparent,
suffix: Text(_suffixText, style: suffixTextStyle)),
),
enabledBorder: OutlineInputBorder(
borderSide: config.preferBorders
? BorderSide(
color: scheme.primaryScale.border,
width: 2,
)
: BorderSide.none,
borderRadius: BorderRadius.all(
Radius.circular(8 * config.borderRadiusScale),
),
),
focusedBorder: OutlineInputBorder(
borderSide: config.preferBorders
? BorderSide(
color: scheme.primaryScale.border,
width: 2,
)
: BorderSide.none,
borderRadius: BorderRadius.all(
Radius.circular(8 * config.borderRadiusScale),
),
),
hintText: widget.hintText,
hintMaxLines: 1,
hintStyle: chatTheme.typography.bodyMedium.copyWith(
color:
widget.hintColor ??
chatTheme.colors.onSurface.withValues(
alpha: 0.5,
),
),
border: widget.inputBorder,
hoverColor: Colors.transparent,
suffix: Text(_suffixText, style: suffixTextStyle),
),
onSubmitted: _handleSubmitted,
onChanged: (value) {
setState(_updateSuffixText);
@ -381,7 +405,8 @@ class _VcComposerState extends State<VcComposerWidget> {
maxLengthEnforcement: MaxLengthEnforcement.none,
inputFormatters: [
Utf8LengthLimitingTextInputFormatter(
maxLength: widget.maxLength),
maxLength: widget.maxLength,
),
],
),
),
@ -417,9 +442,9 @@ class _VcComposerState extends State<VcComposerWidget> {
final bottomSafeArea = MediaQuery.of(context).padding.bottom;
context.read<ComposerHeightNotifier>().setHeight(
// only set real height of the composer, ignoring safe area
widget.handleSafeArea ? height - bottomSafeArea : height,
);
// only set real height of the composer, ignoring safe area
widget.handleSafeArea ? height - bottomSafeArea : height,
);
}
}

View file

@ -8,23 +8,6 @@ import '../date_formatter.dart';
/// A widget that displays a text message.
class VcTextMessageWidget extends StatelessWidget {
/// Creates a widget to display a simple text message.
const VcTextMessageWidget({
required this.message,
required this.index,
this.padding,
this.borderRadius,
this.onlyEmojiFontSize,
this.sentBackgroundColor,
this.receivedBackgroundColor,
this.sentTextStyle,
this.receivedTextStyle,
this.timeStyle,
this.showTime = true,
this.showStatus = true,
super.key,
});
/// The text message data model.
final fccore.TextMessage message;
@ -62,6 +45,23 @@ class VcTextMessageWidget extends StatelessWidget {
/// for sent messages.
final bool showStatus;
/// Creates a widget to display a simple text message.
const VcTextMessageWidget({
required this.message,
required this.index,
this.padding,
this.borderRadius,
this.onlyEmojiFontSize,
this.sentBackgroundColor,
this.receivedBackgroundColor,
this.sentTextStyle,
this.receivedTextStyle,
this.timeStyle,
this.showTime = true,
this.showStatus = true,
super.key,
});
bool get _isOnlyEmoji => message.metadata?['isOnlyEmoji'] == true;
@override
@ -79,7 +79,7 @@ class VcTextMessageWidget extends StatelessWidget {
final timeAndStatus = showTime || (isSentByMe && showStatus)
? TimeAndStatus(
time: message.time,
time: message.resolvedTime,
status: message.status,
showTime: showTime,
showStatus: isSentByMe && showStatus,
@ -95,25 +95,28 @@ class VcTextMessageWidget extends StatelessWidget {
);
return Column(
crossAxisAlignment:
isSentByMe ? CrossAxisAlignment.end : CrossAxisAlignment.start,
children: [
Container(
padding: _isOnlyEmoji
? EdgeInsets.symmetric(
horizontal: (padding?.horizontal ?? 0) / 2,
// vertical: 0,
)
: padding,
decoration: _isOnlyEmoji
? null
: BoxDecoration(
color: backgroundColor,
borderRadius: borderRadius ?? chatTheme.shape,
),
child: textContent),
if (timeAndStatus != null) timeAndStatus,
]);
crossAxisAlignment: isSentByMe
? CrossAxisAlignment.end
: CrossAxisAlignment.start,
children: [
Container(
padding: _isOnlyEmoji
? EdgeInsets.symmetric(
horizontal: (padding?.horizontal ?? 0) / 2,
// vertical: 0,
)
: padding,
decoration: _isOnlyEmoji
? null
: BoxDecoration(
color: backgroundColor,
borderRadius: borderRadius ?? chatTheme.shape,
),
child: textContent,
),
if (timeAndStatus != null) timeAndStatus,
],
);
}
Color _resolveBackgroundColor(bool isSentByMe, ScaleChatTheme theme) {
@ -140,14 +143,19 @@ class VcTextMessageWidget extends StatelessWidget {
..add(DiagnosticsProperty<fccore.TextMessage>('message', message))
..add(IntProperty('index', index))
..add(DiagnosticsProperty<EdgeInsetsGeometry?>('padding', padding))
..add(DiagnosticsProperty<BorderRadiusGeometry?>(
'borderRadius', borderRadius))
..add(
DiagnosticsProperty<BorderRadiusGeometry?>(
'borderRadius',
borderRadius,
),
)
..add(DoubleProperty('onlyEmojiFontSize', onlyEmojiFontSize))
..add(ColorProperty('sentBackgroundColor', sentBackgroundColor))
..add(ColorProperty('receivedBackgroundColor', receivedBackgroundColor))
..add(DiagnosticsProperty<TextStyle?>('sentTextStyle', sentTextStyle))
..add(DiagnosticsProperty<TextStyle?>(
'receivedTextStyle', receivedTextStyle))
..add(
DiagnosticsProperty<TextStyle?>('receivedTextStyle', receivedTextStyle),
)
..add(DiagnosticsProperty<TextStyle?>('timeStyle', timeStyle))
..add(DiagnosticsProperty<bool>('showTime', showTime))
..add(DiagnosticsProperty<bool>('showStatus', showStatus));
@ -156,16 +164,6 @@ class VcTextMessageWidget extends StatelessWidget {
/// A widget to display the message timestamp and status indicator.
class TimeAndStatus extends StatelessWidget {
/// Creates a widget for displaying time and status.
const TimeAndStatus({
required this.time,
this.status,
this.showTime = true,
this.showStatus = true,
this.textStyle,
super.key,
});
/// The time the message was created.
final DateTime? time;
@ -181,6 +179,16 @@ class TimeAndStatus extends StatelessWidget {
/// The text style for the time and status.
final TextStyle? textStyle;
/// Creates a widget for displaying time and status.
const TimeAndStatus({
required this.time,
this.status,
this.showTime = true,
this.showStatus = true,
this.textStyle,
super.key,
});
@override
Widget build(BuildContext context) {
final dformat = DateFormatter();
@ -202,8 +210,11 @@ class TimeAndStatus extends StatelessWidget {
),
)
else
Icon(fccore.getIconForStatus(status!),
color: textStyle?.color, size: 12.scaled(context)),
Icon(
fccore.getIconForStatus(status!),
color: textStyle?.color,
size: 12.scaled(context),
),
],
);
}

View file

@ -27,14 +27,21 @@ const kSending = 'sending';
const maxMessageLength = 2048;
class ChatComponentWidget extends StatefulWidget {
////////////////////////////////////////////////////////////////////////////
final RecordKey _localConversationRecordKey;
final void Function() _onCancel;
final void Function() _onClose;
const ChatComponentWidget._({
required super.key,
required RecordKey localConversationRecordKey,
required void Function() onCancel,
required void Function() onClose,
}) : _localConversationRecordKey = localConversationRecordKey,
_onCancel = onCancel,
_onClose = onClose;
}) : _localConversationRecordKey = localConversationRecordKey,
_onCancel = onCancel,
_onClose = onClose;
// Create a single-contact chat and its associated state
static Widget singleContact({
@ -54,45 +61,45 @@ class ChatComponentWidget extends StatefulWidget {
final contactListCubit = context.watch<ContactListCubit>();
// Get the active conversation cubit
final activeConversationCubit = context.select<
ActiveConversationsBlocMapCubit,
ActiveConversationCubit?>((x) => x.entry(localConversationRecordKey));
final activeConversationCubit = context
.select<ActiveConversationsBlocMapCubit, ActiveConversationCubit?>(
(x) => x.entry(localConversationRecordKey),
);
if (activeConversationCubit == null) {
return waitingPage(onCancel: onCancel);
}
// Get the messages cubit
final messagesCubit = context.select<ActiveSingleContactChatBlocMapCubit,
SingleContactMessagesCubit?>(
(x) => x.entry(localConversationRecordKey));
final messagesCubit = context
.select<
ActiveSingleContactChatBlocMapCubit,
SingleContactMessagesCubit?
>((x) => x.entry(localConversationRecordKey));
if (messagesCubit == null) {
return waitingPage(onCancel: onCancel);
}
// Make chat component state
return BlocProvider(
key: key,
create: (context) => ChatComponentCubit.singleContact(
accountInfo: accountInfo,
accountRecordCubit: accountRecordCubit,
contactListCubit: contactListCubit,
activeConversationCubit: activeConversationCubit,
messagesCubit: messagesCubit,
),
child: ChatComponentWidget._(
key: ValueKey(localConversationRecordKey),
localConversationRecordKey: localConversationRecordKey,
onCancel: onCancel,
onClose: onClose));
key: key,
create: (context) => ChatComponentCubit.singleContact(
accountInfo: accountInfo,
accountRecordCubit: accountRecordCubit,
contactListCubit: contactListCubit,
activeConversationCubit: activeConversationCubit,
messagesCubit: messagesCubit,
),
child: ChatComponentWidget._(
key: ValueKey(localConversationRecordKey),
localConversationRecordKey: localConversationRecordKey,
onCancel: onCancel,
onClose: onClose,
),
);
}
@override
State<ChatComponentWidget> createState() => _ChatComponentWidgetState();
////////////////////////////////////////////////////////////////////////////
final RecordKey _localConversationRecordKey;
final void Function() _onCancel;
final void Function() _onClose;
}
class _ChatComponentWidgetState extends State<ChatComponentWidget> {
@ -108,7 +115,10 @@ class _ChatComponentWidgetState extends State<ChatComponentWidget> {
final chatComponentCubit = context.read<ChatComponentCubit>();
_chatStateProcessor.follow(
chatComponentCubit.stream, chatComponentCubit.state, _updateChatState);
chatComponentCubit.stream,
chatComponentCubit.state,
_updateChatState,
);
super.initState();
}
@ -153,7 +163,8 @@ class _ChatComponentWidgetState extends State<ChatComponentWidget> {
if (chatComponentCubit.scrollOffset != 0) {
_scrollController.position.correctPixels(
_scrollController.position.pixels + chatComponentCubit.scrollOffset);
_scrollController.position.pixels + chatComponentCubit.scrollOffset,
);
chatComponentCubit.scrollOffset = 0;
}
@ -162,163 +173,185 @@ class _ChatComponentWidgetState extends State<ChatComponentWidget> {
children: [
Container(
height: 40.scaledNoShrink(context),
decoration: BoxDecoration(
color: scale.border,
),
child: Row(children: [
Align(
decoration: BoxDecoration(color: scale.border),
child: Row(
children: [
Align(
alignment: AlignmentDirectional.centerStart,
child: Padding(
padding: const EdgeInsetsDirectional.fromSTEB(16, 0, 16, 0),
child: Text(title,
textAlign: TextAlign.start,
style: textTheme.titleMedium!
.copyWith(color: scale.borderText)),
)),
const Spacer(),
IconButton(
iconSize: 24.scaledNoShrink(context),
icon: Icon(Icons.close, color: scale.borderText),
onPressed: widget._onClose)
.paddingLTRB(0, 0, 8, 0)
]),
child: Text(
title,
textAlign: TextAlign.start,
style: textTheme.titleMedium!.copyWith(
color: scale.borderText,
),
),
),
),
const Spacer(),
IconButton(
iconSize: 24.scaledNoShrink(context),
icon: Icon(Icons.close, color: scale.borderText),
onPressed: widget._onClose,
).paddingLTRB(0, 0, 8, 0),
],
),
),
DecoratedBox(
decoration: const BoxDecoration(color: Colors.transparent),
child: NotificationListener<ScrollNotification>(
onNotification: (notification) {
if (chatComponentCubit.scrollOffset != 0) {
return false;
}
decoration: const BoxDecoration(color: Colors.transparent),
child: NotificationListener<ScrollNotification>(
onNotification: (notification) {
if (chatComponentCubit.scrollOffset != 0) {
return false;
}
if (!isFirstPage &&
notification.metrics.pixels <=
((notification.metrics.maxScrollExtent -
notification.metrics.minScrollExtent) *
(1.0 - onEndReachedThreshold) +
notification.metrics.minScrollExtent)) {
//
final scrollOffset = (notification.metrics.maxScrollExtent -
notification.metrics.minScrollExtent) *
(1.0 - onEndReachedThreshold);
if (!isFirstPage &&
notification.metrics.pixels <=
((notification.metrics.maxScrollExtent -
notification.metrics.minScrollExtent) *
(1.0 - onEndReachedThreshold) +
notification.metrics.minScrollExtent)) {
//
final scrollOffset =
(notification.metrics.maxScrollExtent -
notification.metrics.minScrollExtent) *
(1.0 - onEndReachedThreshold);
chatComponentCubit.scrollOffset = scrollOffset;
chatComponentCubit.scrollOffset = scrollOffset;
//
singleFuture((chatComponentCubit, _kScrollTag), () async {
await _handlePageForward(
chatComponentCubit, messageWindow, notification);
});
} else if (!isLastPage &&
notification.metrics.pixels >=
((notification.metrics.maxScrollExtent -
notification.metrics.minScrollExtent) *
onEndReachedThreshold +
notification.metrics.minScrollExtent)) {
//
final scrollOffset =
-(notification.metrics.maxScrollExtent -
notification.metrics.minScrollExtent) *
(1.0 - onEndReachedThreshold);
//
singleFuture((chatComponentCubit, _kScrollTag), () async {
await _handlePageForward(
chatComponentCubit,
messageWindow,
notification,
);
});
} else if (!isLastPage &&
notification.metrics.pixels >=
((notification.metrics.maxScrollExtent -
notification.metrics.minScrollExtent) *
onEndReachedThreshold +
notification.metrics.minScrollExtent)) {
//
final scrollOffset =
-(notification.metrics.maxScrollExtent -
notification.metrics.minScrollExtent) *
(1.0 - onEndReachedThreshold);
chatComponentCubit.scrollOffset = scrollOffset;
//
singleFuture((chatComponentCubit, _kScrollTag), () async {
await _handlePageBackward(
chatComponentCubit, messageWindow, notification);
});
}
return false;
},
child: ValueListenableBuilder(
valueListenable: _textEditingController,
builder: (context, textEditingValue, __) {
final messageIsValid =
_messageIsValid(textEditingValue.text);
var sendIconColor = scaleTheme.config.preferBorders
? scale.border
: scale.borderText;
chatComponentCubit.scrollOffset = scrollOffset;
//
singleFuture((chatComponentCubit, _kScrollTag), () async {
await _handlePageBackward(
chatComponentCubit,
messageWindow,
notification,
);
});
}
return false;
},
child: ValueListenableBuilder(
valueListenable: _textEditingController,
builder: (context, textEditingValue, _) {
final messageIsValid = _messageIsValid(textEditingValue.text);
var sendIconColor = scaleTheme.config.preferBorders
? scale.border
: scale.borderText;
if (!messageIsValid ||
_textEditingController.text.isEmpty) {
sendIconColor = sendIconColor.withAlpha(128);
}
if (!messageIsValid || _textEditingController.text.isEmpty) {
sendIconColor = sendIconColor.withAlpha(128);
}
return fcui.Chat(
currentUserId: localUser.id,
resolveUser: (id) async {
if (id == localUser.id) {
return localUser;
}
return chatComponentState.remoteUsers.get(id);
},
chatController: _chatController,
onMessageSend: (text) =>
_handleSendPressed(chatComponentCubit, text),
theme: scaleChatTheme.chatTheme,
builders: fccore.Builders(
// Chat list builder
chatAnimatedListBuilder: (context, itemBuilder) =>
fcui.ChatAnimatedListReversed(
scrollController: _scrollController,
messageGroupingTimeoutInSeconds: 60,
itemBuilder: itemBuilder),
// Text message builder
textMessageBuilder: (context, message, index) {
var showTime = true;
if (_chatController.messages.length > 1 &&
index < _chatController.messages.length - 1 &&
message.time != null) {
final nextMessage =
_chatController.messages[index + 1];
if (nextMessage.time != null) {
if (nextMessage.time!
.difference(message.time!)
.inSeconds <
60 &&
nextMessage.authorId == message.authorId) {
showTime = false;
}
return fcui.Chat(
currentUserId: localUser.id,
resolveUser: (id) async {
if (id == localUser.id) {
return localUser;
}
return chatComponentState.remoteUsers.get(id);
},
chatController: _chatController,
onMessageSend: (text) =>
_handleSendPressed(chatComponentCubit, text),
theme: scaleChatTheme.chatTheme,
builders: fccore.Builders(
// Chat list builder
chatAnimatedListBuilder: (context, itemBuilder) =>
fcui.ChatAnimatedListReversed(
scrollController: _scrollController,
messageGroupingTimeoutInSeconds: 60,
itemBuilder: itemBuilder,
),
// Text message builder
textMessageBuilder:
(
context,
message,
index, {
required isSentByMe,
groupStatus,
}) {
var showTime = true;
if (_chatController.messages.length > 1 &&
index < _chatController.messages.length - 1 &&
message.resolvedTime != null) {
final nextMessage =
_chatController.messages[index + 1];
if (nextMessage.resolvedTime != null) {
if (nextMessage.resolvedTime!
.difference(message.resolvedTime!)
.inSeconds <
60 &&
nextMessage.authorId == message.authorId) {
showTime = false;
}
}
return VcTextMessageWidget(
message: message,
index: index,
padding: const EdgeInsets.symmetric(
vertical: 12, horizontal: 16)
.scaled(context),
showTime: showTime,
showStatus: showTime,
);
},
// Composer builder
composerBuilder: (ctx) => VcComposerWidget(
autofocus: true,
padding: const EdgeInsets.all(4).scaled(context),
gap: 8.scaled(context),
focusNode: _focusNode,
textInputAction: isAnyMobile
? TextInputAction.newline
: TextInputAction.send,
shiftEnterAction: isAnyMobile
? ShiftEnterAction.send
: ShiftEnterAction.newline,
textEditingController: _textEditingController,
maxLength: maxMessageLength,
keyboardType: TextInputType.multiline,
sendIconColor: sendIconColor,
topWidget: messageIsValid
? null
: Text(translate('chat.message_too_long'),
style: TextStyle(
color: scaleTheme
.scheme.errorScale.primary))
.toCenter(),
),
),
timeFormat: fccore.DateFormat.jm(),
);
}))).expanded(),
}
return VcTextMessageWidget(
message: message,
index: index,
padding: const EdgeInsets.symmetric(
vertical: 12,
horizontal: 16,
).scaled(context),
showTime: showTime,
showStatus: showTime,
);
},
// Composer builder
composerBuilder: (ctx) => VcComposerWidget(
autofocus: true,
padding: const EdgeInsets.all(4).scaled(context),
gap: 8.scaled(context),
focusNode: _focusNode,
textInputAction: isAnyMobile
? TextInputAction.newline
: TextInputAction.send,
shiftEnterAction: isAnyMobile
? ShiftEnterAction.send
: ShiftEnterAction.newline,
textEditingController: _textEditingController,
maxLength: maxMessageLength,
keyboardType: TextInputType.multiline,
sendIconColor: sendIconColor,
topWidget: messageIsValid
? null
: Text(
translate('chat.message_too_long'),
style: TextStyle(
color: scaleTheme.scheme.errorScale.primary,
),
).toCenter(),
),
),
timeFormat: fccore.DateFormat.jm(),
);
},
),
),
).expanded(),
],
);
}
@ -341,10 +374,12 @@ class _ChatComponentWidgetState extends State<ChatComponentWidget> {
// await _chatController.setMessages(windowState.window.toList());
final newMessagesSet = windowState.window.toSet();
final newMessagesById =
Map.fromEntries(newMessagesSet.map((m) => MapEntry(m.id, m)));
final newMessagesById = Map.fromEntries(
newMessagesSet.map((m) => MapEntry(m.id, m)),
);
final newMessagesBySeqId = Map.fromEntries(
newMessagesSet.map((m) => MapEntry(m.metadata![kSeqId], m)));
newMessagesSet.map((m) => MapEntry(m.metadata![kSeqId], m)),
);
final oldMessagesSet = _chatController.messages.toSet();
if (oldMessagesSet.isEmpty) {
@ -355,8 +390,11 @@ class _ChatComponentWidgetState extends State<ChatComponentWidget> {
// See how many messages differ by equality (not identity)
// If there are more than `replaceAllMessagesThreshold` differences
// just replace the whole list of messages
final diffs = newMessagesSet.diffAndIntersect(oldMessagesSet,
diffThisMinusOther: true, diffOtherMinusThis: true);
final diffs = newMessagesSet.diffAndIntersect(
oldMessagesSet,
diffThisMinusOther: true,
diffOtherMinusThis: true,
);
final addedMessages = diffs.diffThisMinusOther!;
final removedMessages = diffs.diffOtherMinusThis!;
@ -423,9 +461,9 @@ class _ChatComponentWidgetState extends State<ChatComponentWidget> {
}
if (!_messageIsValid(text)) {
context
.read<NotificationsCubit>()
.error(text: translate('chat.message_too_long'));
context.read<NotificationsCubit>().error(
text: translate('chat.message_too_long'),
);
return;
}
@ -434,22 +472,28 @@ class _ChatComponentWidgetState extends State<ChatComponentWidget> {
// void _handleAttachmentPressed() async {
Future<void> _handlePageForward(
ChatComponentCubit chatComponentCubit,
WindowState<fccore.Message> messageWindow,
ScrollNotification notification) async {
ChatComponentCubit chatComponentCubit,
WindowState<fccore.Message> messageWindow,
ScrollNotification notification,
) async {
debugPrint(
'_handlePageForward: messagesState.length=${messageWindow.length} '
'messagesState.windowTail=${messageWindow.windowTail} '
'messagesState.windowCount=${messageWindow.windowCount} '
'ScrollNotification=$notification');
'_handlePageForward: messagesState.length=${messageWindow.length} '
'messagesState.windowTail=${messageWindow.windowTail} '
'messagesState.windowCount=${messageWindow.windowCount} '
'ScrollNotification=$notification',
);
// Go forward a page
final tail = min(messageWindow.length,
messageWindow.windowTail + (messageWindow.windowCount ~/ 4)) %
final tail =
min(
messageWindow.length,
messageWindow.windowTail + (messageWindow.windowCount ~/ 4),
) %
messageWindow.length;
// Set follow
final follow = messageWindow.length == 0 ||
final follow =
messageWindow.length == 0 ||
tail == 0; // xxx incorporate scroll position
// final scrollOffset = (notification.metrics.maxScrollExtent -
@ -459,7 +503,10 @@ class _ChatComponentWidgetState extends State<ChatComponentWidget> {
// chatComponentCubit.scrollOffset = scrollOffset;
await chatComponentCubit.setWindow(
tail: tail, count: messageWindow.windowCount, follow: follow);
tail: tail,
count: messageWindow.windowCount,
follow: follow,
);
// chatComponentCubit.state.scrollController.position.jumpTo(
// chatComponentCubit.state.scrollController.offset + scrollOffset);
@ -473,19 +520,22 @@ class _ChatComponentWidgetState extends State<ChatComponentWidget> {
ScrollNotification notification,
) async {
debugPrint(
'_handlePageBackward: messagesState.length=${messageWindow.length} '
'messagesState.windowTail=${messageWindow.windowTail} '
'messagesState.windowCount=${messageWindow.windowCount} '
'ScrollNotification=$notification');
'_handlePageBackward: messagesState.length=${messageWindow.length} '
'messagesState.windowTail=${messageWindow.windowTail} '
'messagesState.windowCount=${messageWindow.windowCount} '
'ScrollNotification=$notification',
);
// Go back a page
final tail = max(
messageWindow.windowCount,
(messageWindow.windowTail - (messageWindow.windowCount ~/ 4)) %
messageWindow.length);
messageWindow.windowCount,
(messageWindow.windowTail - (messageWindow.windowCount ~/ 4)) %
messageWindow.length,
);
// Set follow
final follow = messageWindow.length == 0 ||
final follow =
messageWindow.length == 0 ||
tail == 0; // xxx incorporate scroll position
// final scrollOffset = -(notification.metrics.maxScrollExtent -
@ -495,7 +545,10 @@ class _ChatComponentWidgetState extends State<ChatComponentWidget> {
// chatComponentCubit.scrollOffset = scrollOffset;
await chatComponentCubit.setWindow(
tail: tail, count: messageWindow.windowCount, follow: follow);
tail: tail,
count: messageWindow.windowCount,
follow: follow,
);
// chatComponentCubit.scrollOffset = scrollOffset;

View file

@ -5,11 +5,11 @@ import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
class Utf8LengthLimitingTextInputFormatter extends TextInputFormatter {
Utf8LengthLimitingTextInputFormatter({this.maxLength})
: assert(maxLength != null || maxLength! >= 0, 'maxLength is invalid');
final int? maxLength;
Utf8LengthLimitingTextInputFormatter({this.maxLength})
: assert(maxLength != null || maxLength! >= 0, 'maxLength is invalid');
@override
TextEditingValue formatEditUpdate(
TextEditingValue oldValue,

View file

@ -18,26 +18,36 @@ typedef ChatListCubitState = DHTShortArrayCubitState<proto.Chat>;
class ChatListCubit extends DHTShortArrayCubit<proto.Chat>
with StateMapFollowable<ChatListCubitState, RecordKey, proto.Chat> {
////////////////////////////////////////////////////////////////////////////
final ActiveChatCubit _activeChatCubit;
ChatListCubit({
required AccountInfo accountInfo,
required OwnedDHTRecordPointer chatListRecordPointer,
required ActiveChatCubit activeChatCubit,
}) : _activeChatCubit = activeChatCubit,
super(
open: () => _open(accountInfo, chatListRecordPointer),
decodeElement: proto.Chat.fromBuffer);
}) : _activeChatCubit = activeChatCubit,
super(
open: () => _open(accountInfo, chatListRecordPointer),
decodeElement: proto.Chat.fromBuffer,
);
static Future<DHTShortArray> _open(AccountInfo accountInfo,
OwnedDHTRecordPointer chatListRecordPointer) async {
final dhtRecord = await DHTShortArray.openOwned(chatListRecordPointer,
debugName: 'ChatListCubit::_open::ChatList',
parent: accountInfo.accountRecordKey);
static Future<DHTShortArray> _open(
AccountInfo accountInfo,
OwnedDHTRecordPointer chatListRecordPointer,
) async {
final dhtRecord = await DHTShortArray.openOwned(
chatListRecordPointer,
debugName: 'ChatListCubit::_open::ChatList',
parent: accountInfo.accountRecordKey,
);
return dhtRecord;
}
Future<proto.ChatSettings> getDefaultChatSettings(
proto.Contact contact) async {
proto.Contact contact,
) async {
final pronouns = contact.profile.pronouns.isEmpty
? ''
: ' [${contact.profile.pronouns}])';
@ -52,11 +62,11 @@ class ChatListCubit extends DHTShortArrayCubit<proto.Chat>
required proto.Contact contact,
}) async {
// Make local copy so we don't share the buffer
final localConversationRecordKey =
contact.localConversationRecordKey.toDart();
final localConversationRecordKey = contact.localConversationRecordKey
.toDart();
final remoteAuthor = contact.author.toDart();
final remoteConversationRecordKey =
contact.remoteConversationRecordKey.toDart();
final remoteConversationRecordKey = contact.remoteConversationRecordKey
.toDart();
// Create 1:1 conversation type Chat
final chatMember = proto.ChatMember()
@ -90,8 +100,10 @@ class ChatListCubit extends DHTShortArrayCubit<proto.Chat>
case proto.Chat_Kind.group:
if (c.group.localConversationRecordKey ==
contact.localConversationRecordKey) {
throw StateError('direct conversation record key should'
' not be used for group chats!');
throw StateError(
'direct conversation record key should'
' not be used for group chats!',
);
}
case proto.Chat_Kind.notSet:
throw StateError('unknown chat kind');
@ -104,8 +116,9 @@ class ChatListCubit extends DHTShortArrayCubit<proto.Chat>
}
/// Delete a chat
Future<void> deleteChat(
{required RecordKey localConversationRecordKey}) async {
Future<void> deleteChat({
required RecordKey localConversationRecordKey,
}) async {
// Remove Chat from account's list
await operateWriteEventual((writer) async {
if (_activeChatCubit.state == localConversationRecordKey) {
@ -132,12 +145,10 @@ class ChatListCubit extends DHTShortArrayCubit<proto.Chat>
if (stateValue == null) {
return IMap();
}
return IMap.fromIterable(stateValue,
keyMapper: (e) => e.value.localConversationRecordKey,
valueMapper: (e) => e.value);
return IMap.fromIterable(
stateValue,
keyMapper: (e) => e.value.localConversationRecordKey,
valueMapper: (e) => e.value,
);
}
////////////////////////////////////////////////////////////////////////////
final ActiveChatCubit _activeChatCubit;
}

View file

@ -10,23 +10,23 @@ import '../../theme/theme.dart';
import '../chat_list.dart';
class ChatSingleContactItemWidget extends StatelessWidget {
final RecordKey _localConversationRecordKey;
final proto.Contact? _contact;
final bool _disabled;
const ChatSingleContactItemWidget({
required RecordKey localConversationRecordKey,
required proto.Contact? contact,
bool disabled = false,
super.key,
}) : _localConversationRecordKey = localConversationRecordKey,
_contact = contact,
_disabled = disabled;
final RecordKey _localConversationRecordKey;
final proto.Contact? _contact;
final bool _disabled;
}) : _localConversationRecordKey = localConversationRecordKey,
_contact = contact,
_disabled = disabled;
@override
Widget build(
BuildContext context,
) {
Widget build(BuildContext context) {
final scaleTheme = Theme.of(context).extension<ScaleTheme>()!;
final activeChatCubit = context.watch<ActiveChatCubit>();
@ -46,10 +46,7 @@ class ChatSingleContactItemWidget extends StatelessWidget {
selected: selected,
);
final avatar = StyledAvatar(
name: name,
size: 32.scaled(context),
);
final avatar = StyledAvatar(name: name, size: 32.scaled(context));
return StyledSlideTile(
key: ValueKey(_localConversationRecordKey),
@ -70,14 +67,16 @@ class ChatSingleContactItemWidget extends StatelessWidget {
},
endActions: [
SlideTileAction(
//icon: Icons.delete,
label: translate('button.delete'),
actionScale: ScaleKind.tertiary,
onPressed: (context) async {
final chatListCubit = context.read<ChatListCubit>();
await chatListCubit.deleteChat(
localConversationRecordKey: _localConversationRecordKey);
})
//icon: Icons.delete,
label: translate('button.delete'),
actionScale: ScaleKind.tertiary,
onPressed: (context) async {
final chatListCubit = context.read<ChatListCubit>();
await chatListCubit.deleteChat(
localConversationRecordKey: _localConversationRecordKey,
);
},
),
],
);
}

View file

@ -16,70 +16,89 @@ import '../models/models.dart';
//////////////////////////////////////////////////
class ContactInviteInvalidKeyException implements Exception {
const ContactInviteInvalidKeyException(this.type) : super();
final EncryptionKeyType type;
const ContactInviteInvalidKeyException(this.type) : super();
}
class ContactInviteInvalidIdentityException implements Exception {
const ContactInviteInvalidIdentityException(
this.contactSuperIdentityRecordKey)
: super();
final RecordKey contactSuperIdentityRecordKey;
const ContactInviteInvalidIdentityException(
this.contactSuperIdentityRecordKey,
) : super();
}
typedef GetEncryptionKeyCallback = Future<SecretKey?> Function(
VeilidCryptoSystem cs,
EncryptionKeyType encryptionKeyType,
Uint8List encryptedSecret);
typedef GetEncryptionKeyCallback =
Future<SecretKey?> Function(
VeilidCryptoSystem cs,
EncryptionKeyType encryptionKeyType,
Uint8List encryptedSecret,
);
//////////////////////////////////////////////////
typedef ContactInvitiationListState
= DHTShortArrayCubitState<proto.ContactInvitationRecord>;
typedef ContactInvitiationListState =
DHTShortArrayCubitState<proto.ContactInvitationRecord>;
//////////////////////////////////////////////////
// Mutable state for per-account contact invitations
class ContactInvitationListCubit
extends DHTShortArrayCubit<proto.ContactInvitationRecord>
with
StateMapFollowable<ContactInvitiationListState, RecordKey,
proto.ContactInvitationRecord> {
StateMapFollowable<
ContactInvitiationListState,
RecordKey,
proto.ContactInvitationRecord
> {
//
final AccountInfo _accountInfo;
ContactInvitationListCubit({
required AccountInfo accountInfo,
required OwnedDHTRecordPointer contactInvitationListRecordPointer,
}) : _accountInfo = accountInfo,
super(
open: () => _open(accountInfo.accountRecordKey,
contactInvitationListRecordPointer),
decodeElement: proto.ContactInvitationRecord.fromBuffer);
}) : _accountInfo = accountInfo,
super(
open: () => _open(
accountInfo.accountRecordKey,
contactInvitationListRecordPointer,
),
decodeElement: proto.ContactInvitationRecord.fromBuffer,
);
static Future<DHTShortArray> _open(RecordKey accountRecordKey,
OwnedDHTRecordPointer contactInvitationListRecordPointer) async {
static Future<DHTShortArray> _open(
RecordKey accountRecordKey,
OwnedDHTRecordPointer contactInvitationListRecordPointer,
) async {
final dhtRecord = await DHTShortArray.openOwned(
contactInvitationListRecordPointer,
debugName: 'ContactInvitationListCubit::_open::ContactInvitationList',
parent: accountRecordKey);
contactInvitationListRecordPointer,
debugName: 'ContactInvitationListCubit::_open::ContactInvitationList',
parent: accountRecordKey,
);
return dhtRecord;
}
Future<SharedSecret> convertToSharedSecret(SecretKey secret) async {
final crcs =
await DHTRecordPool.instance.veilid.getCryptoSystem(secret.kind);
final crcs = await DHTRecordPool.instance.veilid.getCryptoSystem(
secret.kind,
);
return SharedSecret(
kind: secret.kind,
value: BareSharedSecret.fromBytes(secret.value
.toBytes()
.sublist(0, await crcs.sharedSecretLength())));
kind: secret.kind,
value: BareSharedSecret.fromBytes(
secret.value.toBytes().sublist(0, await crcs.sharedSecretLength()),
),
);
}
Future<(Uint8List, RecordKey)> createInvitation(
{required proto.Profile profile,
required EncryptionKeyType encryptionKeyType,
required String encryptionKey,
required String recipient,
required String message,
required Timestamp? expiration}) async {
Future<(Uint8List, RecordKey)> createInvitation({
required proto.Profile profile,
required EncryptionKeyType encryptionKeyType,
required String encryptionKey,
required String recipient,
required String message,
required Timestamp? expiration,
}) async {
final pool = DHTRecordPool.instance;
// Generate writer keypair to share with new contact
@ -88,8 +107,9 @@ class ContactInvitationListCubit
// Make a shared secret for encryption from the writer secret as well
// That way we only have to transfer one secret
final contactRequestEncryptionKey =
await convertToSharedSecret(contactRequestWriter.secret);
final contactRequestEncryptionKey = await convertToSharedSecret(
contactRequestWriter.secret,
);
final idcs = await _accountInfo.identityCryptoSystem;
final identityWriter = _accountInfo.identityWriter;
@ -109,14 +129,21 @@ class ContactInvitationListCubit
late final Uint8List signedContactInvitationBytes;
late final RecordKey contactRequestInboxKey;
await (await pool.createRecord(
debugName: 'ContactInvitationListCubit::createInvitation::'
'LocalConversation',
parent: _accountInfo.accountRecordKey,
schema: DHTSchema.smpl(oCnt: 0, members: [
await DHTSchemaMember.fromPublicKey(
pool.veilid, identityWriter.key, 1)
])))
.deleteScope((localConversation) async {
debugName:
'ContactInvitationListCubit::createInvitation::'
'LocalConversation',
parent: _accountInfo.accountRecordKey,
schema: DHTSchema.smpl(
oCnt: 0,
members: [
await DHTSchemaMember.fromPublicKey(
pool.veilid,
identityWriter.key,
1,
),
],
),
)).deleteScope((localConversation) async {
// dont bother reopening localConversation with writer
// Make ContactRequestPrivate and encrypt with the writer secret
final crpriv = proto.ContactRequestPrivate()
@ -127,7 +154,9 @@ class ContactInvitationListCubit
..expiration = expiration?.toInt64() ?? Int64.ZERO;
final crprivbytes = crpriv.writeToBuffer();
final encryptedContactRequestPrivate = await crcs.encryptAeadWithNonce(
crprivbytes, contactRequestEncryptionKey);
crprivbytes,
contactRequestEncryptionKey,
);
// Create ContactRequest and embed contactrequestprivate
final creq = proto.ContactRequest()
@ -138,26 +167,36 @@ class ContactInvitationListCubit
// Subkey 0 is the ContactRequest from the initiator
// Subkey 1 will contain the invitation response accept/reject eventually
await (await pool.createRecord(
debugName: 'ContactInvitationListCubit::createInvitation::'
'ContactRequestInbox',
parent: _accountInfo.accountRecordKey,
schema: DHTSchema.smpl(oCnt: 1, members: [
await DHTSchemaMember.fromPublicKey(
Veilid.instance, contactRequestWriter.key, 1)
]),
crypto: const VeilidCryptoPublic()))
.deleteScope((contactRequestInbox) async {
debugName:
'ContactInvitationListCubit::createInvitation::'
'ContactRequestInbox',
parent: _accountInfo.accountRecordKey,
schema: DHTSchema.smpl(
oCnt: 1,
members: [
await DHTSchemaMember.fromPublicKey(
Veilid.instance,
contactRequestWriter.key,
1,
),
],
),
crypto: const VeilidCryptoPublic(),
)).deleteScope((contactRequestInbox) async {
// Keep the contact request inbox key
contactRequestInboxKey = contactRequestInbox.key;
// Store ContactRequest in owner subkey
await contactRequestInbox.eventualWriteProtobuf(creq);
// Store an empty invitation response
await contactRequestInbox.eventualWriteBytes(Uint8List(0),
subkey: 1,
writer: contactRequestWriter,
crypto: await DHTRecordPool.privateCryptoFromSecretKey(
contactRequestWriter.secret));
await contactRequestInbox.eventualWriteBytes(
Uint8List(0),
subkey: 1,
writer: contactRequestWriter,
crypto: await DHTRecordPool.privateCryptoFromSecretKey(
contactRequestWriter.secret,
),
);
// Create ContactInvitation and SignedContactInvitation
final cinv = proto.ContactInvitation()
@ -166,14 +205,16 @@ class ContactInvitationListCubit
final cinvbytes = cinv.writeToBuffer();
final scinv = proto.SignedContactInvitation()
..contactInvitation = cinvbytes
..identitySignature =
(await idcs.signWithKeyPair(identityWriter, cinvbytes)).toProto();
..identitySignature = (await idcs.signWithKeyPair(
identityWriter,
cinvbytes,
)).toProto();
signedContactInvitationBytes = scinv.writeToBuffer();
// Create ContactInvitationRecord
final cinvrec = proto.ContactInvitationRecord()
..contactRequestInbox =
contactRequestInbox.ownedDHTRecordPointer!.toProto()
..contactRequestInbox = contactRequestInbox.ownedDHTRecordPointer!
.toProto()
..writerKey = contactRequestWriter.key.toProto()
..writerSecret = contactRequestWriter.secret.toProto()
..localConversationRecordKey = localConversation.key.toProto()
@ -189,24 +230,29 @@ class ContactInvitationListCubit
});
});
log.debug('createInvitation:\n'
'contactRequestInboxKey=$contactRequestInboxKey\n'
'bytes=${signedContactInvitationBytes.lengthInBytes}\n'
'${hex.encode(signedContactInvitationBytes)}');
log.debug(
'createInvitation:\n'
'contactRequestInboxKey=$contactRequestInboxKey\n'
'bytes=${signedContactInvitationBytes.lengthInBytes}\n'
'${hex.encode(signedContactInvitationBytes)}',
);
return (signedContactInvitationBytes, contactRequestInboxKey);
}
Future<void> deleteInvitation(
{required bool accepted,
required RecordKey contactRequestInboxRecordKey}) async {
Future<void> deleteInvitation({
required bool accepted,
required RecordKey contactRequestInboxRecordKey,
}) async {
final pool = DHTRecordPool.instance;
// Remove ContactInvitationRecord from account's list
final deletedItem = await operateWrite((writer) async {
for (var i = 0; i < writer.length; i++) {
final item = await writer.getProtobuf(
proto.ContactInvitationRecord.fromBuffer, i);
proto.ContactInvitationRecord.fromBuffer,
i,
);
if (item == null) {
throw Exception('Failed to get contact invitation record');
}
@ -222,11 +268,13 @@ class ContactInvitationListCubit
if (deletedItem != null) {
// Delete the contact request inbox
final contactRequestInbox = deletedItem.contactRequestInbox.toDart();
await (await pool.openRecordOwned(contactRequestInbox,
debugName: 'ContactInvitationListCubit::deleteInvitation::'
'ContactRequestInbox',
parent: _accountInfo.accountRecordKey))
.scope((contactRequestInbox) async {
await (await pool.openRecordOwned(
contactRequestInbox,
debugName:
'ContactInvitationListCubit::deleteInvitation::'
'ContactRequestInbox',
parent: _accountInfo.accountRecordKey,
)).scope((contactRequestInbox) async {
// Wipe out old invitation so it shows up as invalid
await contactRequestInbox.tryWriteBytes(Uint8List(0));
});
@ -237,8 +285,9 @@ class ContactInvitationListCubit
}
if (!accepted) {
try {
await pool
.deleteRecord(deletedItem.localConversationRecordKey.toDart());
await pool.deleteRecord(
deletedItem.localConversationRecordKey.toDart(),
);
} on Exception catch (e) {
log.debug('error removing local conversation record: $e', e);
}
@ -251,21 +300,26 @@ class ContactInvitationListCubit
required GetEncryptionKeyCallback getEncryptionKeyCallback,
required CancelRequest cancelRequest,
}) async {
log.debug('validateInvitation:\n'
'bytes=${inviteData.lengthInBytes}\n'
'${hex.encode(inviteData)}');
log.debug(
'validateInvitation:\n'
'bytes=${inviteData.lengthInBytes}\n'
'${hex.encode(inviteData)}',
);
final pool = DHTRecordPool.instance;
// Get contact request inbox from invitation
final signedContactInvitation =
proto.SignedContactInvitation.fromBuffer(inviteData);
final contactInvitationBytes =
Uint8List.fromList(signedContactInvitation.contactInvitation);
final contactInvitation =
proto.ContactInvitation.fromBuffer(contactInvitationBytes);
final contactRequestInboxKey =
contactInvitation.contactRequestInboxKey.toDart();
final signedContactInvitation = proto.SignedContactInvitation.fromBuffer(
inviteData,
);
final contactInvitationBytes = Uint8List.fromList(
signedContactInvitation.contactInvitation,
);
final contactInvitation = proto.ContactInvitation.fromBuffer(
contactInvitationBytes,
);
final contactRequestInboxKey = contactInvitation.contactRequestInboxKey
.toDart();
ValidContactInvitation? out;
@ -278,77 +332,101 @@ class ContactInvitationListCubit
return null;
}
final isSelf = contactInvitationList.indexWhere((cir) =>
cir.value.contactRequestInbox.recordKey.toDart() ==
contactRequestInboxKey) !=
final isSelf =
contactInvitationList.indexWhere(
(cir) =>
cir.value.contactRequestInbox.recordKey.toDart() ==
contactRequestInboxKey,
) !=
-1;
await (await pool
.openRecordRead(contactRequestInboxKey,
debugName: 'ContactInvitationListCubit::validateInvitation::'
'ContactRequestInbox',
parent: await pool.getParentRecordKey(contactRequestInboxKey) ??
_accountInfo.accountRecordKey)
.openRecordRead(
contactRequestInboxKey,
debugName:
'ContactInvitationListCubit::validateInvitation::'
'ContactRequestInbox',
parent:
await pool.getParentRecordKey(contactRequestInboxKey) ??
_accountInfo.accountRecordKey,
)
.withCancel(cancelRequest))
.maybeDeleteScope(!isSelf, (contactRequestInbox) async {
//
final contactRequest = await contactRequestInbox
.getProtobuf(proto.ContactRequest.fromBuffer)
.withCancel(cancelRequest);
//
final contactRequest = await contactRequestInbox
.getProtobuf(proto.ContactRequest.fromBuffer)
.withCancel(cancelRequest);
final cs = await pool.veilid.getCryptoSystem(contactRequestInboxKey.kind);
final cs = await pool.veilid.getCryptoSystem(
contactRequestInboxKey.kind,
);
// Decrypt contact request private
final encryptionKeyType =
EncryptionKeyType.fromProto(contactRequest!.encryptionKeyType);
late final SecretKey? writerSecret;
try {
writerSecret = await getEncryptionKeyCallback(cs, encryptionKeyType,
Uint8List.fromList(contactInvitation.writerSecret));
} on Exception catch (_) {
throw ContactInviteInvalidKeyException(encryptionKeyType);
}
if (writerSecret == null) {
return null;
}
// Decrypt contact request private
final encryptionKeyType = EncryptionKeyType.fromProto(
contactRequest!.encryptionKeyType,
);
late final SecretKey? writerSecret;
try {
writerSecret = await getEncryptionKeyCallback(
cs,
encryptionKeyType,
Uint8List.fromList(contactInvitation.writerSecret),
);
} on Exception catch (_) {
throw ContactInviteInvalidKeyException(encryptionKeyType);
}
if (writerSecret == null) {
return null;
}
final contactRequestEncryptionKey =
await convertToSharedSecret(writerSecret);
final contactRequestEncryptionKey = await convertToSharedSecret(
writerSecret,
);
final contactRequestPrivateBytes = await cs.decryptAeadWithNonce(
Uint8List.fromList(contactRequest.private),
contactRequestEncryptionKey);
final contactRequestPrivateBytes = await cs.decryptAeadWithNonce(
Uint8List.fromList(contactRequest.private),
contactRequestEncryptionKey,
);
final contactRequestPrivate =
proto.ContactRequestPrivate.fromBuffer(contactRequestPrivateBytes);
final contactSuperIdentityRecordKey =
contactRequestPrivate.superIdentityRecordKey.toDart();
final contactRequestPrivate = proto.ContactRequestPrivate.fromBuffer(
contactRequestPrivateBytes,
);
final contactSuperIdentityRecordKey = contactRequestPrivate
.superIdentityRecordKey
.toDart();
// Fetch the account master
final contactSuperIdentity = await SuperIdentity.open(
superRecordKey: contactSuperIdentityRecordKey)
.withCancel(cancelRequest);
if (contactSuperIdentity == null) {
throw ContactInviteInvalidIdentityException(
contactSuperIdentityRecordKey);
}
// Fetch the account master
final contactSuperIdentity = await SuperIdentity.open(
superRecordKey: contactSuperIdentityRecordKey,
).withCancel(cancelRequest);
if (contactSuperIdentity == null) {
throw ContactInviteInvalidIdentityException(
contactSuperIdentityRecordKey,
);
}
// Verify
final idcs = await contactSuperIdentity.currentInstance.cryptoSystem;
final signature = signedContactInvitation.identitySignature.toDart();
await idcs.verify(contactSuperIdentity.currentInstance.publicKey,
contactInvitationBytes, signature);
// Verify
final idcs = await contactSuperIdentity.currentInstance.cryptoSystem;
final signature = signedContactInvitation.identitySignature.toDart();
await idcs.verify(
contactSuperIdentity.currentInstance.publicKey,
contactInvitationBytes,
signature,
);
final writer = KeyPair(
key: contactRequestPrivate.writerKey.toDart(), secret: writerSecret);
final writer = KeyPair(
key: contactRequestPrivate.writerKey.toDart(),
secret: writerSecret,
);
out = ValidContactInvitation(
accountInfo: _accountInfo,
contactRequestInboxKey: contactRequestInboxKey,
contactRequestPrivate: contactRequestPrivate,
contactSuperIdentity: contactSuperIdentity,
writer: writer);
});
out = ValidContactInvitation(
accountInfo: _accountInfo,
contactRequestInboxKey: contactRequestInboxKey,
contactRequestPrivate: contactRequestPrivate,
contactSuperIdentity: contactSuperIdentity,
writer: writer,
);
});
return out;
}
@ -356,16 +434,16 @@ class ContactInvitationListCubit
/// StateMapFollowable /////////////////////////
@override
IMap<RecordKey, proto.ContactInvitationRecord> getStateMap(
ContactInvitiationListState state) {
ContactInvitiationListState state,
) {
final stateValue = state.state.asData?.value;
if (stateValue == null) {
return IMap();
}
return IMap.fromIterable(stateValue,
keyMapper: (e) => e.value.contactRequestInbox.recordKey.toDart(),
valueMapper: (e) => e.value);
return IMap.fromIterable(
stateValue,
keyMapper: (e) => e.value.contactRequestInbox.recordKey.toDart(),
valueMapper: (e) => e.value,
);
}
//
final AccountInfo _accountInfo;
}

View file

@ -9,34 +9,40 @@ typedef ContactRequestInboxState = AsyncValue<proto.SignedContactResponse?>;
class ContactRequestInboxCubit
extends DefaultDHTRecordCubit<proto.SignedContactResponse?> {
ContactRequestInboxCubit(
{required AccountInfo accountInfo, required this.contactInvitationRecord})
: super(
open: () => _open(
accountInfo: accountInfo,
contactInvitationRecord: contactInvitationRecord),
decodeState: (buf) => buf.isEmpty
? null
: proto.SignedContactResponse.fromBuffer(buf));
final proto.ContactInvitationRecord contactInvitationRecord;
static Future<DHTRecord> _open(
{required AccountInfo accountInfo,
required proto.ContactInvitationRecord contactInvitationRecord}) async {
ContactRequestInboxCubit({
required AccountInfo accountInfo,
required this.contactInvitationRecord,
}) : super(
open: () => _open(
accountInfo: accountInfo,
contactInvitationRecord: contactInvitationRecord,
),
decodeState: (buf) =>
buf.isEmpty ? null : proto.SignedContactResponse.fromBuffer(buf),
);
static Future<DHTRecord> _open({
required AccountInfo accountInfo,
required proto.ContactInvitationRecord contactInvitationRecord,
}) async {
final pool = DHTRecordPool.instance;
final accountRecordKey = accountInfo.accountRecordKey;
final writerSecret = contactInvitationRecord.writerSecret.toDart();
final recordKey =
contactInvitationRecord.contactRequestInbox.recordKey.toDart();
final recordKey = contactInvitationRecord.contactRequestInbox.recordKey
.toDart();
return pool.openRecordRead(recordKey,
debugName: 'ContactRequestInboxCubit::_open::'
'ContactRequestInbox',
crypto: await DHTRecordPool.privateCryptoFromSecretKey(writerSecret),
parent: accountRecordKey,
defaultSubkey: 1);
return pool.openRecordRead(
recordKey,
debugName:
'ContactRequestInboxCubit::_open::'
'ContactRequestInbox',
crypto: await DHTRecordPool.privateCryptoFromSecretKey(writerSecret),
parent: accountRecordKey,
defaultSubkey: 1,
);
}
final proto.ContactInvitationRecord contactInvitationRecord;
}

View file

@ -15,18 +15,23 @@ import 'contact_request_inbox_cubit.dart';
/// State of WaitingInvitationCubit
sealed class WaitingInvitationState
implements StateMachineState<WaitingInvitationState> {
WaitingInvitationState({required this.global});
final WaitingInvitationStateGlobal global;
WaitingInvitationState({required this.global});
}
class WaitingInvitationStateGlobal {
WaitingInvitationStateGlobal(
{required this.accountInfo,
required this.accountRecordCubit,
required this.contactInvitationRecord});
final AccountInfo accountInfo;
final AccountRecordCubit accountRecordCubit;
final proto.ContactInvitationRecord contactInvitationRecord;
WaitingInvitationStateGlobal({
required this.accountInfo,
required this.accountRecordCubit,
required this.contactInvitationRecord,
});
}
/// State of WaitingInvitationCubit:
@ -34,10 +39,10 @@ class WaitingInvitationStateGlobal {
class WaitingInvitationStateInvalidSignature
with StateMachineEndState<WaitingInvitationState>
implements WaitingInvitationState {
const WaitingInvitationStateInvalidSignature({required this.global});
@override
final WaitingInvitationStateGlobal global;
const WaitingInvitationStateInvalidSignature({required this.global});
}
/// State of WaitingInvitationCubit:
@ -45,12 +50,15 @@ class WaitingInvitationStateInvalidSignature
class WaitingInvitationStateInitFailed
with StateMachineEndState<WaitingInvitationState>
implements WaitingInvitationState {
const WaitingInvitationStateInitFailed(
{required this.global, required this.exception});
@override
final WaitingInvitationStateGlobal global;
final Exception exception;
const WaitingInvitationStateInitFailed({
required this.global,
required this.exception,
});
}
/// State of WaitingInvitationCubit:
@ -58,18 +66,23 @@ class WaitingInvitationStateInitFailed
class WaitingInvitationStateInvitationStatus
with StateMachineEndState<WaitingInvitationState>
implements WaitingInvitationState {
const WaitingInvitationStateInvitationStatus(
{required this.global, required this.status});
@override
final WaitingInvitationStateGlobal global;
final InvitationStatus status;
const WaitingInvitationStateInvitationStatus({
required this.global,
required this.status,
});
}
@immutable
class InvitationStatus extends Equatable {
const InvitationStatus({required this.acceptedContact});
final AcceptedContact? acceptedContact;
const InvitationStatus({required this.acceptedContact});
@override
List<Object?> get props => [acceptedContact];
}
@ -77,141 +90,177 @@ class InvitationStatus extends Equatable {
/// State of WaitingInvitationCubit:
/// Waiting for the invited contact to accept/reject the invitation
class WaitingInvitationStateWaitForContactResponse
extends AsyncCubitReactorState<
WaitingInvitationState,
ContactRequestInboxState,
ContactRequestInboxCubit> implements WaitingInvitationState {
WaitingInvitationStateWaitForContactResponse(super.create,
{required this.global})
: super(onState: (ctx) async {
final signedContactResponse = ctx.state.asData?.value;
if (signedContactResponse == null) {
return null;
}
final contactResponse = proto.ContactResponse.fromBuffer(
signedContactResponse.contactResponse);
final contactSuperRecordKey =
contactResponse.superIdentityRecordKey.toDart();
// Fetch the remote contact's account superidentity
return WaitingInvitationStateWaitForContactSuperIdentity(
() => SuperIdentityCubit(superRecordKey: contactSuperRecordKey),
global: global,
signedContactResponse: signedContactResponse);
});
extends
AsyncCubitReactorState<
WaitingInvitationState,
ContactRequestInboxState,
ContactRequestInboxCubit
>
implements WaitingInvitationState {
@override
final WaitingInvitationStateGlobal global;
WaitingInvitationStateWaitForContactResponse(
super.create, {
required this.global,
}) : super(
onState: (ctx) async {
final signedContactResponse = ctx.state.asData?.value;
if (signedContactResponse == null) {
return null;
}
final contactResponse = proto.ContactResponse.fromBuffer(
signedContactResponse.contactResponse,
);
final contactSuperRecordKey = contactResponse.superIdentityRecordKey
.toDart();
// Fetch the remote contact's account superidentity
return WaitingInvitationStateWaitForContactSuperIdentity(
() => SuperIdentityCubit(superRecordKey: contactSuperRecordKey),
global: global,
signedContactResponse: signedContactResponse,
);
},
);
}
/// State of WaitingInvitationCubit:
/// Once an accept/reject happens, get the SuperIdentity of the recipient
class WaitingInvitationStateWaitForContactSuperIdentity
extends AsyncCubitReactorState<WaitingInvitationState, SuperIdentityState,
SuperIdentityCubit> implements WaitingInvitationState {
WaitingInvitationStateWaitForContactSuperIdentity(super.create,
{required this.global,
required proto.SignedContactResponse signedContactResponse})
: super(onState: (ctx) async {
final contactSuperIdentity = ctx.state.asData?.value;
if (contactSuperIdentity == null) {
return null;
}
final remoteAuthor =
await contactSuperIdentity.currentInstance.getAuthor();
final contactResponseBytes =
Uint8List.fromList(signedContactResponse.contactResponse);
final contactResponse =
proto.ContactResponse.fromBuffer(contactResponseBytes);
// Verify
final idcs = await contactSuperIdentity.currentInstance.cryptoSystem;
final signature = signedContactResponse.identitySignature.toDart();
if (!await idcs.verify(contactSuperIdentity.currentInstance.publicKey,
contactResponseBytes, signature)) {
// Could not verify signature of contact response
return WaitingInvitationStateInvalidSignature(
global: global,
);
}
// Check for rejection
if (!contactResponse.accept) {
// Rejection
return WaitingInvitationStateInvitationStatus(
global: global,
status: const InvitationStatus(acceptedContact: null),
);
}
// Pull profile from remote conversation key
final remoteConversationRecordKey =
contactResponse.remoteConversationRecordKey.toDart();
return WaitingInvitationStateWaitForConversation(
() => ConversationCubit(
accountInfo: global.accountInfo,
remoteAuthor: remoteAuthor,
remoteConversationRecordKey: remoteConversationRecordKey),
global: global,
remoteConversationRecordKey: remoteConversationRecordKey,
contactSuperIdentity: contactSuperIdentity,
);
});
extends
AsyncCubitReactorState<
WaitingInvitationState,
SuperIdentityState,
SuperIdentityCubit
>
implements WaitingInvitationState {
@override
final WaitingInvitationStateGlobal global;
WaitingInvitationStateWaitForContactSuperIdentity(
super.create, {
required this.global,
required proto.SignedContactResponse signedContactResponse,
}) : super(
onState: (ctx) async {
final contactSuperIdentity = ctx.state.asData?.value;
if (contactSuperIdentity == null) {
return null;
}
final remoteAuthor = await contactSuperIdentity.currentInstance
.getAuthor();
final contactResponseBytes = Uint8List.fromList(
signedContactResponse.contactResponse,
);
final contactResponse = proto.ContactResponse.fromBuffer(
contactResponseBytes,
);
// Verify
final idcs = await contactSuperIdentity.currentInstance.cryptoSystem;
final signature = signedContactResponse.identitySignature.toDart();
if (!await idcs.verify(
contactSuperIdentity.currentInstance.publicKey,
contactResponseBytes,
signature,
)) {
// Could not verify signature of contact response
return WaitingInvitationStateInvalidSignature(global: global);
}
// Check for rejection
if (!contactResponse.accept) {
// Rejection
return WaitingInvitationStateInvitationStatus(
global: global,
status: const InvitationStatus(acceptedContact: null),
);
}
// Pull profile from remote conversation key
final remoteConversationRecordKey = contactResponse
.remoteConversationRecordKey
.toDart();
return WaitingInvitationStateWaitForConversation(
() => ConversationCubit(
accountInfo: global.accountInfo,
remoteAuthor: remoteAuthor,
remoteConversationRecordKey: remoteConversationRecordKey,
),
global: global,
remoteConversationRecordKey: remoteConversationRecordKey,
contactSuperIdentity: contactSuperIdentity,
);
},
);
}
/// State of WaitingInvitationCubit:
/// Wait for the conversation cubit to initialize so we can return the
/// accepted invitation
class WaitingInvitationStateWaitForConversation extends AsyncCubitReactorState<
WaitingInvitationState,
AsyncValue<ConversationState>,
ConversationCubit> implements WaitingInvitationState {
WaitingInvitationStateWaitForConversation(super.create,
{required this.global,
required RecordKey remoteConversationRecordKey,
required SuperIdentity contactSuperIdentity})
: super(onState: (ctx) async {
final remoteConversation = ctx.state.asData?.value.remoteConversation;
final localConversation = ctx.state.asData?.value.localConversation;
if (remoteConversation == null || localConversation != null) {
return null;
}
// Stop reacting to the conversation cubit
ctx.stop();
// Complete the local conversation now that we have the remote profile
final remoteProfile = remoteConversation.profile;
final localConversationRecordKey = global
.contactInvitationRecord.localConversationRecordKey
.toDart();
try {
await ctx.cubit.initLocalConversation(
profile: global.accountRecordCubit.state.asData!.value.profile,
existingConversationRecordKey: localConversationRecordKey);
} on Exception catch (e) {
return WaitingInvitationStateInitFailed(
global: global, exception: e);
}
return WaitingInvitationStateInvitationStatus(
global: global,
status: InvitationStatus(
acceptedContact: AcceptedContact(
remoteProfile: remoteProfile,
remoteIdentity: contactSuperIdentity,
remoteConversationRecordKey: remoteConversationRecordKey,
localConversationRecordKey: localConversationRecordKey)));
});
class WaitingInvitationStateWaitForConversation
extends
AsyncCubitReactorState<
WaitingInvitationState,
AsyncValue<ConversationState>,
ConversationCubit
>
implements WaitingInvitationState {
@override
final WaitingInvitationStateGlobal global;
WaitingInvitationStateWaitForConversation(
super.create, {
required this.global,
required RecordKey remoteConversationRecordKey,
required SuperIdentity contactSuperIdentity,
}) : super(
onState: (ctx) async {
final remoteConversation =
ctx.state.asData?.value.remoteConversation;
final localConversation = ctx.state.asData?.value.localConversation;
if (remoteConversation == null || localConversation != null) {
return null;
}
// Stop reacting to the conversation cubit
ctx.stop();
// Complete the local conversation now that we have the remote profile
final remoteProfile = remoteConversation.profile;
final localConversationRecordKey = global
.contactInvitationRecord
.localConversationRecordKey
.toDart();
try {
await ctx.cubit.initLocalConversation(
profile: global.accountRecordCubit.state.asData!.value.profile,
existingConversationRecordKey: localConversationRecordKey,
);
} on Exception catch (e) {
return WaitingInvitationStateInitFailed(
global: global,
exception: e,
);
}
return WaitingInvitationStateInvitationStatus(
global: global,
status: InvitationStatus(
acceptedContact: AcceptedContact(
remoteProfile: remoteProfile,
remoteIdentity: contactSuperIdentity,
remoteConversationRecordKey: remoteConversationRecordKey,
localConversationRecordKey: localConversationRecordKey,
),
),
);
},
);
}
/// Invitation state processor for sent invitations
@ -222,12 +271,13 @@ class WaitingInvitationCubit extends StateMachineCubit<WaitingInvitationState> {
required AccountRecordCubit accountRecordCubit,
required proto.ContactInvitationRecord contactInvitationRecord,
}) : super(
WaitingInvitationStateWaitForContactResponse(
initialStateCreate,
global: WaitingInvitationStateGlobal(
accountInfo: accountInfo,
accountRecordCubit: accountRecordCubit,
contactInvitationRecord: contactInvitationRecord),
),
);
WaitingInvitationStateWaitForContactResponse(
initialStateCreate,
global: WaitingInvitationStateGlobal(
accountInfo: accountInfo,
accountRecordCubit: accountRecordCubit,
contactInvitationRecord: contactInvitationRecord,
),
),
);
}

View file

@ -9,31 +9,52 @@ import '../../notifications/notifications.dart';
import '../../proto/proto.dart' as proto;
import 'cubits.dart';
typedef WaitingInvitationsBlocMapState
= BlocMapState<RecordKey, WaitingInvitationState>;
typedef WaitingInvitationsBlocMapState =
BlocMapState<RecordKey, WaitingInvitationState>;
// Map of contactRequestInboxRecordKey to WaitingInvitationCubit
// Wraps a contact invitation cubit to watch for accept/reject
// Automatically follows the state of a ContactInvitationListCubit.
class WaitingInvitationsBlocMapCubit extends BlocMapCubit<RecordKey,
WaitingInvitationState, WaitingInvitationCubit>
class WaitingInvitationsBlocMapCubit
extends
BlocMapCubit<RecordKey, WaitingInvitationState, WaitingInvitationCubit>
with
StateMapFollower<DHTShortArrayCubitState<proto.ContactInvitationRecord>,
RecordKey, proto.ContactInvitationRecord> {
WaitingInvitationsBlocMapCubit(
{required AccountInfo accountInfo,
required AccountRecordCubit accountRecordCubit,
required ContactInvitationListCubit contactInvitationListCubit,
required ContactListCubit contactListCubit,
required NotificationsCubit notificationsCubit})
: _accountInfo = accountInfo,
_accountRecordCubit = accountRecordCubit,
_contactInvitationListCubit = contactInvitationListCubit,
_contactListCubit = contactListCubit,
_notificationsCubit = notificationsCubit {
StateMapFollower<
DHTShortArrayCubitState<proto.ContactInvitationRecord>,
RecordKey,
proto.ContactInvitationRecord
> {
////
final AccountInfo _accountInfo;
final AccountRecordCubit _accountRecordCubit;
final ContactInvitationListCubit _contactInvitationListCubit;
final ContactListCubit _contactListCubit;
final NotificationsCubit _notificationsCubit;
final _singleInvitationStatusProcessor =
SingleStateProcessor<WaitingInvitationsBlocMapState>();
WaitingInvitationsBlocMapCubit({
required AccountInfo accountInfo,
required AccountRecordCubit accountRecordCubit,
required ContactInvitationListCubit contactInvitationListCubit,
required ContactListCubit contactListCubit,
required NotificationsCubit notificationsCubit,
}) : _accountInfo = accountInfo,
_accountRecordCubit = accountRecordCubit,
_contactInvitationListCubit = contactInvitationListCubit,
_contactListCubit = contactListCubit,
_notificationsCubit = notificationsCubit {
// React to invitation status changes
_singleInvitationStatusProcessor.follow(
stream, state, _invitationStatusListener);
stream,
state,
_invitationStatusListener,
);
// Follow the contact invitation list cubit
follow(contactInvitationListCubit);
@ -45,21 +66,25 @@ class WaitingInvitationsBlocMapCubit extends BlocMapCubit<RecordKey,
await super.close();
}
void _addWaitingInvitation(
{required proto.ContactInvitationRecord contactInvitationRecord}) =>
add(
contactInvitationRecord.contactRequestInbox.recordKey.toDart(),
() => WaitingInvitationCubit(
initialStateCreate: () => ContactRequestInboxCubit(
accountInfo: _accountInfo,
contactInvitationRecord: contactInvitationRecord),
accountInfo: _accountInfo,
accountRecordCubit: _accountRecordCubit,
contactInvitationRecord: contactInvitationRecord));
void _addWaitingInvitation({
required proto.ContactInvitationRecord contactInvitationRecord,
}) => add(
contactInvitationRecord.contactRequestInbox.recordKey.toDart(),
() => WaitingInvitationCubit(
initialStateCreate: () => ContactRequestInboxCubit(
accountInfo: _accountInfo,
contactInvitationRecord: contactInvitationRecord,
),
accountInfo: _accountInfo,
accountRecordCubit: _accountRecordCubit,
contactInvitationRecord: contactInvitationRecord,
),
);
// Process all accepted or rejected invitations
Future<void> _invitationStatusListener(
WaitingInvitationsBlocMapState newState) async {
WaitingInvitationsBlocMapState newState,
) async {
for (final entry in newState.entries) {
final contactRequestInboxRecordKey = entry.key;
@ -67,22 +92,27 @@ class WaitingInvitationsBlocMapCubit extends BlocMapCubit<RecordKey,
case WaitingInvitationStateInvalidSignature():
// Signature was invalid, display an error and treat like a rejection
await _contactInvitationListCubit.deleteInvitation(
accepted: false,
contactRequestInboxRecordKey: contactRequestInboxRecordKey);
accepted: false,
contactRequestInboxRecordKey: contactRequestInboxRecordKey,
);
// Notify about error state
_notificationsCubit.error(
text: translate('waiting_invitation.invalid'));
text: translate('waiting_invitation.invalid'),
);
case final WaitingInvitationStateInitFailed st:
// Initialization error, display an error and treat like a rejection
await _contactInvitationListCubit.deleteInvitation(
accepted: false,
contactRequestInboxRecordKey: contactRequestInboxRecordKey);
accepted: false,
contactRequestInboxRecordKey: contactRequestInboxRecordKey,
);
// Notify about error state
_notificationsCubit.error(
text: '${translate('waiting_invitation.init_failed')}\n'
'${st.exception}');
text:
'${translate('waiting_invitation.init_failed')}\n'
'${st.exception}',
);
case final WaitingInvitationStateInvitationStatus st:
final invStatus = st.status;
@ -91,8 +121,9 @@ class WaitingInvitationsBlocMapCubit extends BlocMapCubit<RecordKey,
final acceptedContact = invStatus.acceptedContact;
if (acceptedContact != null) {
await _contactInvitationListCubit.deleteInvitation(
accepted: true,
contactRequestInboxRecordKey: contactRequestInboxRecordKey);
accepted: true,
contactRequestInboxRecordKey: contactRequestInboxRecordKey,
);
// Accept
await _contactListCubit.createContact(
@ -106,19 +137,22 @@ class WaitingInvitationsBlocMapCubit extends BlocMapCubit<RecordKey,
// Notify about acceptance
_notificationsCubit.info(
text: translate('waiting_invitation.accepted',
args: {'name': acceptedContact.remoteProfile.name}));
text: translate(
'waiting_invitation.accepted',
args: {'name': acceptedContact.remoteProfile.name},
),
);
} else {
// Reject
await _contactInvitationListCubit.deleteInvitation(
accepted: false,
contactRequestInboxRecordKey: contactRequestInboxRecordKey);
accepted: false,
contactRequestInboxRecordKey: contactRequestInboxRecordKey,
);
// Notify about rejection
_notificationsCubit.info(
text: translate(
'waiting_invitation.rejected',
));
text: translate('waiting_invitation.rejected'),
);
}
case WaitingInvitationStateWaitForContactResponse():
// Do nothing, still waiting for contact response
@ -139,17 +173,11 @@ class WaitingInvitationsBlocMapCubit extends BlocMapCubit<RecordKey,
void removeFromState(RecordKey key) => remove(key);
@override
void updateState(RecordKey key, proto.ContactInvitationRecord? oldValue,
proto.ContactInvitationRecord newValue) {
void updateState(
RecordKey key,
proto.ContactInvitationRecord? oldValue,
proto.ContactInvitationRecord newValue,
) {
_addWaitingInvitation(contactInvitationRecord: newValue);
}
////
final AccountInfo _accountInfo;
final AccountRecordCubit _accountRecordCubit;
final ContactInvitationListCubit _contactInvitationListCubit;
final ContactListCubit _contactListCubit;
final NotificationsCubit _notificationsCubit;
final _singleInvitationStatusProcessor =
SingleStateProcessor<WaitingInvitationsBlocMapState>();
}

View file

@ -6,6 +6,14 @@ import '../../proto/proto.dart' as proto;
@immutable
class AcceptedContact extends Equatable {
final proto.Profile remoteProfile;
final SuperIdentity remoteIdentity;
final RecordKey remoteConversationRecordKey;
final RecordKey localConversationRecordKey;
const AcceptedContact({
required this.remoteProfile,
required this.remoteIdentity,
@ -13,16 +21,11 @@ class AcceptedContact extends Equatable {
required this.localConversationRecordKey,
});
final proto.Profile remoteProfile;
final SuperIdentity remoteIdentity;
final RecordKey remoteConversationRecordKey;
final RecordKey localConversationRecordKey;
@override
List<Object?> get props => [
remoteProfile,
remoteIdentity,
remoteConversationRecordKey,
localConversationRecordKey
];
remoteProfile,
remoteIdentity,
remoteConversationRecordKey,
localConversationRecordKey,
];
}

View file

@ -11,18 +11,29 @@ import 'models.dart';
///
class ValidContactInvitation {
//
final AccountInfo _accountInfo;
final RecordKey _contactRequestInboxKey;
final SuperIdentity _contactSuperIdentity;
final KeyPair _writer;
final proto.ContactRequestPrivate _contactRequestPrivate;
@internal
ValidContactInvitation(
{required AccountInfo accountInfo,
required RecordKey contactRequestInboxKey,
required proto.ContactRequestPrivate contactRequestPrivate,
required SuperIdentity contactSuperIdentity,
required KeyPair writer})
: _accountInfo = accountInfo,
_contactRequestInboxKey = contactRequestInboxKey,
_contactRequestPrivate = contactRequestPrivate,
_contactSuperIdentity = contactSuperIdentity,
_writer = writer;
ValidContactInvitation({
required AccountInfo accountInfo,
required RecordKey contactRequestInboxKey,
required proto.ContactRequestPrivate contactRequestPrivate,
required SuperIdentity contactSuperIdentity,
required KeyPair writer,
}) : _accountInfo = accountInfo,
_contactRequestInboxKey = contactRequestInboxKey,
_contactRequestPrivate = contactRequestPrivate,
_contactSuperIdentity = contactSuperIdentity,
_writer = writer;
proto.Profile get remoteProfile => _contactRequestPrivate.profile;
@ -31,48 +42,57 @@ class ValidContactInvitation {
try {
// Ensure we don't delete this if we're trying to chat to self
// The initiating side will delete the records in deleteInvitation()
final isSelf = _contactSuperIdentity.currentInstance.publicKey ==
final isSelf =
_contactSuperIdentity.currentInstance.publicKey ==
_accountInfo.identityPublicKey;
return (await pool.openRecordWrite(_contactRequestInboxKey, _writer,
debugName: 'ValidContactInvitation::accept::'
'ContactRequestInbox',
parent: await pool.getParentRecordKey(_contactRequestInboxKey) ??
_accountInfo.accountRecordKey))
.maybeDeleteScope(!isSelf, (contactRequestInbox) async {
return (await pool.openRecordWrite(
_contactRequestInboxKey,
_writer,
debugName:
'ValidContactInvitation::accept::'
'ContactRequestInbox',
parent:
await pool.getParentRecordKey(_contactRequestInboxKey) ??
_accountInfo.accountRecordKey,
)).maybeDeleteScope(!isSelf, (contactRequestInbox) async {
// Create local conversation key for this
// contact and send via contact response
final conversation = ConversationCubit(
accountInfo: _accountInfo,
remoteAuthor:
await _contactSuperIdentity.currentInstance.getAuthor());
final localConversationRecordKey =
await conversation.initLocalConversation(profile: profile);
accountInfo: _accountInfo,
remoteAuthor: await _contactSuperIdentity.currentInstance.getAuthor(),
);
final localConversationRecordKey = await conversation
.initLocalConversation(profile: profile);
final contactResponse = proto.ContactResponse()
..accept = true
..remoteConversationRecordKey = localConversationRecordKey.toProto()
..superIdentityRecordKey =
_accountInfo.superIdentityRecordKey.toProto();
..superIdentityRecordKey = _accountInfo.superIdentityRecordKey
.toProto();
final contactResponseBytes = contactResponse.writeToBuffer();
final cs = await _accountInfo.identityCryptoSystem;
final identitySignature = await cs.signWithKeyPair(
_accountInfo.identityWriter, contactResponseBytes);
_accountInfo.identityWriter,
contactResponseBytes,
);
final signedContactResponse = proto.SignedContactResponse()
..contactResponse = contactResponseBytes
..identitySignature = identitySignature.toProto();
// Write the acceptance to the inbox
await contactRequestInbox.eventualWriteProtobuf(signedContactResponse,
subkey: 1);
await contactRequestInbox.eventualWriteProtobuf(
signedContactResponse,
subkey: 1,
);
return AcceptedContact(
remoteProfile: _contactRequestPrivate.profile,
remoteIdentity: _contactSuperIdentity,
remoteConversationRecordKey:
_contactRequestPrivate.chatRecordKey.toDart(),
remoteConversationRecordKey: _contactRequestPrivate.chatRecordKey
.toDart(),
localConversationRecordKey: localConversationRecordKey,
);
});
@ -86,39 +106,40 @@ class ValidContactInvitation {
final pool = DHTRecordPool.instance;
// Ensure we don't delete this if we're trying to chat to self
final isSelf = _contactSuperIdentity.currentInstance.publicKey ==
final isSelf =
_contactSuperIdentity.currentInstance.publicKey ==
_accountInfo.identityPublicKey;
return (await pool.openRecordWrite(_contactRequestInboxKey, _writer,
debugName: 'ValidContactInvitation::reject::'
'ContactRequestInbox',
parent: _accountInfo.accountRecordKey))
.maybeDeleteScope(!isSelf, (contactRequestInbox) async {
return (await pool.openRecordWrite(
_contactRequestInboxKey,
_writer,
debugName:
'ValidContactInvitation::reject::'
'ContactRequestInbox',
parent: _accountInfo.accountRecordKey,
)).maybeDeleteScope(!isSelf, (contactRequestInbox) async {
final contactResponse = proto.ContactResponse()
..accept = false
..superIdentityRecordKey =
_accountInfo.superIdentityRecordKey.toProto();
..superIdentityRecordKey = _accountInfo.superIdentityRecordKey
.toProto();
final contactResponseBytes = contactResponse.writeToBuffer();
final cs = await _accountInfo.identityCryptoSystem;
final identitySignature = await cs.signWithKeyPair(
_accountInfo.identityWriter, contactResponseBytes);
_accountInfo.identityWriter,
contactResponseBytes,
);
final signedContactResponse = proto.SignedContactResponse()
..contactResponse = contactResponseBytes
..identitySignature = identitySignature.toProto();
// Write the rejection to the inbox
await contactRequestInbox.eventualWriteProtobuf(signedContactResponse,
subkey: 1);
await contactRequestInbox.eventualWriteProtobuf(
signedContactResponse,
subkey: 1,
);
return true;
});
}
//
final AccountInfo _accountInfo;
final RecordKey _contactRequestInboxKey;
final SuperIdentity _contactSuperIdentity;
final KeyPair _writer;
final proto.ContactRequestPrivate _contactRequestPrivate;
}

View file

@ -10,18 +10,15 @@ import 'package:zxing2/qrcode.dart';
import '../../theme/theme.dart';
enum _FrameState {
notFound,
formatError,
checksumError,
}
enum _FrameState { notFound, formatError, checksumError }
class _ScannerOverlay extends CustomPainter {
_ScannerOverlay(this.scanWindow, this.frameColor);
final Rect scanWindow;
final Color? frameColor;
_ScannerOverlay(this.scanWindow, this.frameColor);
@override
void paint(Canvas canvas, Size size) {
final backgroundPath = Path()..addRect(Rect.largest);
@ -46,63 +43,76 @@ class _ScannerOverlay extends CustomPainter {
/// Camera QR scanner
class CameraQRScanner<T> extends StatefulWidget {
const CameraQRScanner(
{required Widget Function(BuildContext) loadingBuilder,
required Widget Function(
BuildContext, Object error, StackTrace? stackTrace)
errorBuilder,
required Widget Function(BuildContext) bottomRowBuilder,
required void Function(String) showNotification,
required void Function(String, Object? error, StackTrace? stackTrace)
logError,
required void Function(T) onDone,
T? Function(Result)? onDetect,
T? Function(CameraImage)? onImageAvailable,
Size? scanSize,
Color? formatErrorFrameColor,
Color? checksumErrorFrameColor,
String? cameraErrorMessage,
String? deniedErrorMessage,
String? deniedWithoutPromptErrorMessage,
String? restrictedErrorMessage,
super.key})
: _loadingBuilder = loadingBuilder,
_errorBuilder = errorBuilder,
_bottomRowBuilder = bottomRowBuilder,
_showNotification = showNotification,
_logError = logError,
_scanSize = scanSize,
_onDetect = onDetect,
_onDone = onDone,
_onImageAvailable = onImageAvailable,
_formatErrorFrameColor = formatErrorFrameColor,
_checksumErrorFrameColor = checksumErrorFrameColor,
_cameraErrorMessage = cameraErrorMessage,
_deniedErrorMessage = deniedErrorMessage,
_deniedWithoutPromptErrorMessage = deniedWithoutPromptErrorMessage,
_restrictedErrorMessage = restrictedErrorMessage;
@override
State<CameraQRScanner<T>> createState() => _CameraQRScannerState();
////////////////////////////////////////////////////////////////////////////
final Widget Function(BuildContext) _loadingBuilder;
final Widget Function(BuildContext, Object error, StackTrace? stackTrace)
_errorBuilder;
_errorBuilder;
final Widget Function(BuildContext) _bottomRowBuilder;
final void Function(String) _showNotification;
final void Function(String, Object? error, StackTrace? stackTrace) _logError;
final T? Function(Result)? _onDetect;
final void Function(T) _onDone;
final T? Function(CameraImage)? _onImageAvailable;
final Size? _scanSize;
final Color? _formatErrorFrameColor;
final Color? _checksumErrorFrameColor;
final String? _cameraErrorMessage;
final String? _deniedErrorMessage;
final String? _deniedWithoutPromptErrorMessage;
final String? _restrictedErrorMessage;
const CameraQRScanner({
required Widget Function(BuildContext) loadingBuilder,
required Widget Function(BuildContext, Object error, StackTrace? stackTrace)
errorBuilder,
required Widget Function(BuildContext) bottomRowBuilder,
required void Function(String) showNotification,
required void Function(String, Object? error, StackTrace? stackTrace)
logError,
required void Function(T) onDone,
T? Function(Result)? onDetect,
T? Function(CameraImage)? onImageAvailable,
Size? scanSize,
Color? formatErrorFrameColor,
Color? checksumErrorFrameColor,
String? cameraErrorMessage,
String? deniedErrorMessage,
String? deniedWithoutPromptErrorMessage,
String? restrictedErrorMessage,
super.key,
}) : _loadingBuilder = loadingBuilder,
_errorBuilder = errorBuilder,
_bottomRowBuilder = bottomRowBuilder,
_showNotification = showNotification,
_logError = logError,
_scanSize = scanSize,
_onDetect = onDetect,
_onDone = onDone,
_onImageAvailable = onImageAvailable,
_formatErrorFrameColor = formatErrorFrameColor,
_checksumErrorFrameColor = checksumErrorFrameColor,
_cameraErrorMessage = cameraErrorMessage,
_deniedErrorMessage = deniedErrorMessage,
_deniedWithoutPromptErrorMessage = deniedWithoutPromptErrorMessage,
_restrictedErrorMessage = restrictedErrorMessage;
@override
State<CameraQRScanner<T>> createState() => _CameraQRScannerState();
}
class _CameraQRScannerState<T> extends State<CameraQRScanner<T>>
@ -157,49 +167,54 @@ class _CameraQRScannerState<T> extends State<CameraQRScanner<T>>
);
return Scaffold(
body: FutureBuilder(
future: _initWait(),
builder: (context, av) => av.when(
error: (e, st) => widget._errorBuilder(context, e, st),
loading: () => widget._loadingBuilder(context),
data: (data, isComplete) => Column(
children: <Widget>[
Expanded(
child: Padding(
padding: const EdgeInsets.all(1),
child: Center(
child: Stack(
alignment: AlignmentDirectional.center,
children: [
_cameraPreviewWidget(context),
if (scanWindow != null)
IgnorePointer(
child: CustomPaint(
foregroundPainter: _ScannerOverlay(
scanWindow,
switch (_frameState) {
_FrameState.notFound => null,
_FrameState.formatError =>
widget._formatErrorFrameColor,
_FrameState.checksumError =>
widget._checksumErrorFrameColor
}),
)),
]),
body: FutureBuilder(
future: _initWait(),
builder: (context, av) => av.when(
error: (e, st) => widget._errorBuilder(context, e, st),
loading: () => widget._loadingBuilder(context),
data: (data, isComplete) => Column(
children: <Widget>[
Expanded(
child: Padding(
padding: const EdgeInsets.all(1),
child: Center(
child: Stack(
alignment: AlignmentDirectional.center,
children: [
_cameraPreviewWidget(context),
if (scanWindow != null)
IgnorePointer(
child: CustomPaint(
foregroundPainter: _ScannerOverlay(
scanWindow,
switch (_frameState) {
_FrameState.notFound => null,
_FrameState.formatError =>
widget._formatErrorFrameColor,
_FrameState.checksumError =>
widget._checksumErrorFrameColor,
},
),
),
),
),
),
widget._bottomRowBuilder(context),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
_cameraToggleWidget(),
_torchToggleWidget(activeColor, inactiveColor)
],
),
],
],
),
),
)));
),
),
widget._bottomRowBuilder(context),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
_cameraToggleWidget(),
_torchToggleWidget(activeColor, inactiveColor),
],
),
],
),
),
),
);
}
/// Display the preview from the camera
@ -216,13 +231,13 @@ class _CameraQRScannerState<T> extends State<CameraQRScanner<T>>
child: CameraPreview(
cameraController,
child: LayoutBuilder(
builder: (context, constraints) => GestureDetector(
behavior: HitTestBehavior.opaque,
onScaleStart: _handleScaleStart,
onScaleUpdate: _handleScaleUpdate,
onTapDown: (details) =>
_onViewFinderTap(details, constraints),
)),
builder: (context, constraints) => GestureDetector(
behavior: HitTestBehavior.opaque,
onScaleStart: _handleScaleStart,
onScaleUpdate: _handleScaleUpdate,
onTapDown: (details) => _onViewFinderTap(details, constraints),
),
),
),
);
}
@ -238,8 +253,10 @@ class _CameraQRScannerState<T> extends State<CameraQRScanner<T>>
return;
}
_currentScale = (_baseScale * details.scale)
.clamp(_minAvailableZoom, _maxAvailableZoom);
_currentScale = (_baseScale * details.scale).clamp(
_minAvailableZoom,
_maxAvailableZoom,
);
await _controller!.setZoomLevel(_currentScale);
}
@ -254,23 +271,24 @@ class _CameraQRScannerState<T> extends State<CameraQRScanner<T>>
? () => _onSetFlashModeButtonPressed(
_controller?.value.flashMode == FlashMode.torch
? FlashMode.off
: FlashMode.torch)
: FlashMode.torch,
)
: null,
);
Widget _cameraToggleWidget() {
final currentCameraDescription = _controller?.description;
return IconButton(
icon:
Icon(isAndroid ? Icons.flip_camera_android : Icons.flip_camera_ios),
onPressed: (currentCameraDescription == null || _cameras.isEmpty)
? null
: () {
final nextCameraIndex =
(_cameras.indexOf(currentCameraDescription) + 1) %
_cameras.length;
unawaited(_onNewCameraSelected(_cameras[nextCameraIndex]));
});
icon: Icon(isAndroid ? Icons.flip_camera_android : Icons.flip_camera_ios),
onPressed: (currentCameraDescription == null || _cameras.isEmpty)
? null
: () {
final nextCameraIndex =
(_cameras.indexOf(currentCameraDescription) + 1) %
_cameras.length;
unawaited(_onNewCameraSelected(_cameras[nextCameraIndex]));
},
);
}
void _onViewFinderTap(TapDownDetails details, BoxConstraints constraints) {
@ -296,7 +314,8 @@ class _CameraQRScannerState<T> extends State<CameraQRScanner<T>>
}
Future<void> _initializeCameraController(
CameraDescription cameraDescription) async {
CameraDescription cameraDescription,
) async {
final cameraController = CameraController(
cameraDescription,
kIsWeb ? ResolutionPreset.max : ResolutionPreset.medium,
@ -314,8 +333,9 @@ class _CameraQRScannerState<T> extends State<CameraQRScanner<T>>
if (cameraController.value.hasError &&
(cameraController.value.errorDescription?.isNotEmpty ?? false)) {
widget._showNotification(
'${widget._cameraErrorMessage ?? 'Camera error'}: '
'${cameraController.value.errorDescription!}');
'${widget._cameraErrorMessage ?? 'Camera error'}: '
'${cameraController.value.errorDescription!}',
);
}
});
@ -331,8 +351,9 @@ class _CameraQRScannerState<T> extends State<CameraQRScanner<T>>
}
await cameraController.startImageStream((cameraImage) {
final out =
(widget._onImageAvailable ?? _onImageAvailable)(cameraImage);
final out = (widget._onImageAvailable ?? _onImageAvailable)(
cameraImage,
);
if (out != null) {
_controller = null;
unawaited(cameraController.dispose());
@ -343,15 +364,19 @@ class _CameraQRScannerState<T> extends State<CameraQRScanner<T>>
switch (e.code) {
case 'CameraAccessDenied':
widget._showNotification(
widget._deniedErrorMessage ?? 'You have denied camera access.');
widget._deniedErrorMessage ?? 'You have denied camera access.',
);
case 'CameraAccessDeniedWithoutPrompt':
// iOS only
widget._showNotification(widget._deniedWithoutPromptErrorMessage ??
'Please go to Settings app to enable camera access.');
widget._showNotification(
widget._deniedWithoutPromptErrorMessage ??
'Please go to Settings app to enable camera access.',
);
case 'CameraAccessRestricted':
// iOS only
widget._showNotification(
widget._restrictedErrorMessage ?? 'Camera access is restricted.');
widget._restrictedErrorMessage ?? 'Camera access is restricted.',
);
default:
_showCameraException(e, st);
}
@ -382,8 +407,11 @@ class _CameraQRScannerState<T> extends State<CameraQRScanner<T>>
final abgrImage = plane.bytes.buffer.asInt32List();
final source =
RGBLuminanceSource(cameraImage.width, cameraImage.height, abgrImage);
final source = RGBLuminanceSource(
cameraImage.width,
cameraImage.height,
abgrImage,
);
final bitmap = BinaryBitmap(HybridBinarizer(source));
@ -418,11 +446,13 @@ class _CameraQRScannerState<T> extends State<CameraQRScanner<T>>
}
void _onSetFlashModeButtonPressed(FlashMode mode) {
unawaited(_setFlashMode(mode).then((_) {
if (mounted) {
setState(() {});
}
}));
unawaited(
_setFlashMode(mode).then((_) {
if (mounted) {
setState(() {});
}
}),
);
}
Future<void> _setFlashMode(FlashMode mode) async {
@ -447,9 +477,10 @@ class _CameraQRScannerState<T> extends State<CameraQRScanner<T>>
final code = e.code;
final message = e.description;
widget._logError(
'CameraException: $code${message == null ? '' : '\nMessage: $message'}',
e,
st);
'CameraException: $code${message == null ? '' : '\nMessage: $message'}',
e,
st,
);
}
Future<void> _init(Completer<void> cancel) async {

View file

@ -19,6 +19,14 @@ import '../../theme/theme.dart';
import '../contact_invitation.dart';
class ContactInvitationDisplayDialog extends StatelessWidget {
final Locator locator;
final String recipient;
final String message;
final String fingerprint;
const ContactInvitationDisplayDialog._({
required this.locator,
required this.recipient,
@ -26,11 +34,6 @@ class ContactInvitationDisplayDialog extends StatelessWidget {
required this.fingerprint,
});
final Locator locator;
final String recipient;
final String message;
final String fingerprint;
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
@ -43,8 +46,11 @@ class ContactInvitationDisplayDialog extends StatelessWidget {
String makeTextInvite(String recipient, String message, Uint8List data) {
final invite = StringUtils.addCharAtPosition(
base64UrlNoPadEncode(data), '\n', 40,
repeat: true);
base64UrlNoPadEncode(data),
'\n',
40,
repeat: true,
);
final to = recipient.isNotEmpty
? '${translate('invitiation_dialog.to')}: $recipient\n'
: '';
@ -65,102 +71,135 @@ class ContactInvitationDisplayDialog extends StatelessWidget {
final generatorOutputV = context.watch<InvitationGeneratorCubit>().state;
final cardsize =
min<double>(MediaQuery.of(context).size.shortestSide - 48.0, 400);
final cardsize = min<double>(
MediaQuery.of(context).size.shortestSide - 48.0,
400,
);
final fingerprintText =
'${translate('create_invitation_dialog.fingerprint')}\n'
'$fingerprint';
return BlocListener<ContactInvitationListCubit,
ContactInvitiationListState>(
bloc: locator<ContactInvitationListCubit>(),
listener: (context, state) {
final listState = state.state.asData?.value;
final data = generatorOutputV.asData?.value;
return BlocListener<
ContactInvitationListCubit,
ContactInvitiationListState
>(
bloc: locator<ContactInvitationListCubit>(),
listener: (context, state) {
final listState = state.state.asData?.value;
final data = generatorOutputV.asData?.value;
if (listState != null && data != null) {
final idx = listState.indexWhere((x) =>
x.value.contactRequestInbox.recordKey.toDart() == data.$2);
if (idx == -1) {
// This invitation is gone, close it
Navigator.pop(context);
}
if (listState != null && data != null) {
final idx = listState.indexWhere(
(x) => x.value.contactRequestInbox.recordKey.toDart() == data.$2,
);
if (idx == -1) {
// This invitation is gone, close it
Navigator.pop(context);
}
},
child: PopControl(
dismissible: !generatorOutputV.isLoading,
child: Dialog(
shape: RoundedRectangleBorder(
side: const BorderSide(width: 2),
borderRadius: BorderRadius.circular(
16 * scaleConfig.borderRadiusScale)),
backgroundColor: Colors.white,
child: ConstrainedBox(
constraints: BoxConstraints(
minWidth: cardsize,
maxWidth: cardsize,
minHeight: cardsize,
maxHeight: cardsize),
child: generatorOutputV.when(
loading: buildProgressIndicator,
data: (data) => Column(children: [
FittedBox(
child: Text(
translate('create_invitation_dialog'
'.contact_invitation'),
style: textTheme.headlineSmall!
.copyWith(color: Colors.black)))
.paddingAll(8),
FittedBox(
child: QrImageView.withQr(
size: 300,
qr: QrCode.fromUint8List(
data: data.$1,
errorCorrectLevel:
QrErrorCorrectLevel.L)),
).expanded(),
if (recipient.isNotEmpty)
AutoSizeText(recipient,
softWrap: true,
maxLines: 2,
style: textTheme.labelLarge!
.copyWith(color: Colors.black))
.paddingAll(8),
if (message.isNotEmpty)
Text(message,
softWrap: true,
textAlign: TextAlign.center,
maxLines: 2,
style: textTheme.labelMedium!
.copyWith(color: Colors.black))
.paddingAll(8),
Text(fingerprintText,
softWrap: true,
textAlign: TextAlign.center,
style: textTheme.labelSmall!.copyWith(
color: Colors.black,
fontFamily: 'Source Code Pro'))
.paddingAll(2),
ElevatedButton.icon(
icon: const Icon(Icons.copy),
style: ElevatedButton.styleFrom(
foregroundColor: Colors.black,
backgroundColor: Colors.white,
side: const BorderSide()),
label: Text(translate(
'create_invitation_dialog.copy_invitation')),
onPressed: () async {
context.read<NotificationsCubit>().info(
text: translate('create_invitation_dialog'
'.invitation_copied'));
await Clipboard.setData(ClipboardData(
text: makeTextInvite(
recipient, message, data.$1)));
},
).paddingAll(16),
]),
error: errorPage)))));
}
},
child: PopControl(
dismissible: !generatorOutputV.isLoading,
child: Dialog(
shape: RoundedRectangleBorder(
side: const BorderSide(width: 2),
borderRadius: BorderRadius.circular(
16 * scaleConfig.borderRadiusScale,
),
),
backgroundColor: Colors.white,
child: ConstrainedBox(
constraints: BoxConstraints(
minWidth: cardsize,
maxWidth: cardsize,
minHeight: cardsize,
maxHeight: cardsize,
),
child: generatorOutputV.when(
loading: buildProgressIndicator,
data: (data) => Column(
children: [
FittedBox(
child: Text(
translate(
'create_invitation_dialog'
'.contact_invitation',
),
style: textTheme.headlineSmall!.copyWith(
color: Colors.black,
),
),
).paddingAll(8),
FittedBox(
child: QrImageView.withQr(
size: 300,
qr: QrCode.fromUint8List(
data: data.$1,
errorCorrectLevel: QrErrorCorrectLevel.L,
),
),
).expanded(),
if (recipient.isNotEmpty)
AutoSizeText(
recipient,
softWrap: true,
maxLines: 2,
style: textTheme.labelLarge!.copyWith(
color: Colors.black,
),
).paddingAll(8),
if (message.isNotEmpty)
Text(
message,
softWrap: true,
textAlign: TextAlign.center,
maxLines: 2,
style: textTheme.labelMedium!.copyWith(
color: Colors.black,
),
).paddingAll(8),
Text(
fingerprintText,
softWrap: true,
textAlign: TextAlign.center,
style: textTheme.labelSmall!.copyWith(
color: Colors.black,
fontFamily: 'Source Code Pro',
),
).paddingAll(2),
ElevatedButton.icon(
icon: const Icon(Icons.copy),
style: ElevatedButton.styleFrom(
foregroundColor: Colors.black,
backgroundColor: Colors.white,
side: const BorderSide(),
),
label: Text(
translate('create_invitation_dialog.copy_invitation'),
),
onPressed: () async {
context.read<NotificationsCubit>().info(
text: translate(
'create_invitation_dialog'
'.invitation_copied',
),
);
await Clipboard.setData(
ClipboardData(
text: makeTextInvite(recipient, message, data.$1),
),
);
},
).paddingAll(16),
],
),
error: errorPage,
),
),
),
),
);
}
static Future<void> show({
@ -170,18 +209,20 @@ class ContactInvitationDisplayDialog extends StatelessWidget {
required String recipient,
required String message,
}) async {
final fingerprint =
locator<AccountInfoCubit>().state.identityPublicKey.toString();
final fingerprint = locator<AccountInfoCubit>().state.identityPublicKey
.toString();
await showPopControlDialog<void>(
context: context,
builder: (context) => BlocProvider(
create: create,
child: ContactInvitationDisplayDialog._(
locator: locator,
recipient: recipient,
message: message,
fingerprint: fingerprint,
)));
context: context,
builder: (context) => BlocProvider(
create: create,
child: ContactInvitationDisplayDialog._(
locator: locator,
recipient: recipient,
message: message,
fingerprint: fingerprint,
),
),
);
}
}

View file

@ -7,25 +7,30 @@ import '../../theme/theme.dart';
import '../contact_invitation.dart';
class ContactInvitationItemWidget extends StatelessWidget {
const ContactInvitationItemWidget(
{required this.contactInvitationRecord,
required this.disabled,
super.key});
final proto.ContactInvitationRecord contactInvitationRecord;
final bool disabled;
const ContactInvitationItemWidget({
required this.contactInvitationRecord,
required this.disabled,
super.key,
});
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties
..add(DiagnosticsProperty<proto.ContactInvitationRecord>(
'contactInvitationRecord', contactInvitationRecord))
..add(
DiagnosticsProperty<proto.ContactInvitationRecord>(
'contactInvitationRecord',
contactInvitationRecord,
),
)
..add(DiagnosticsProperty<bool>('disabled', disabled));
}
@override
// ignore: prefer_expression_function_bodies
Widget build(BuildContext context) {
// final localConversationKey =
// contact.localConversationRecordKey.toDart();
@ -56,14 +61,15 @@ class ContactInvitationItemWidget extends StatelessWidget {
return;
}
await ContactInvitationDisplayDialog.show(
context: context,
locator: context.read,
recipient: contactInvitationRecord.recipient,
message: contactInvitationRecord.message,
create: (context) => InvitationGeneratorCubit.value((
Uint8List.fromList(contactInvitationRecord.invitation),
contactInvitationRecord.contactRequestInbox.recordKey.toDart()
)));
context: context,
locator: context.read,
recipient: contactInvitationRecord.recipient,
message: contactInvitationRecord.message,
create: (context) => InvitationGeneratorCubit.value((
Uint8List.fromList(contactInvitationRecord.invitation),
contactInvitationRecord.contactRequestInbox.recordKey.toDart(),
)),
);
},
endActions: [
SlideTileAction(
@ -71,15 +77,17 @@ class ContactInvitationItemWidget extends StatelessWidget {
label: translate('button.delete'),
actionScale: ScaleKind.tertiary,
onPressed: (context) async {
final contactInvitationListCubit =
context.read<ContactInvitationListCubit>();
final contactInvitationListCubit = context
.read<ContactInvitationListCubit>();
await contactInvitationListCubit.deleteInvitation(
accepted: false,
contactRequestInboxRecordKey: contactInvitationRecord
.contactRequestInbox.recordKey
.toDart());
accepted: false,
contactRequestInboxRecordKey: contactInvitationRecord
.contactRequestInbox
.recordKey
.toDart(),
);
},
)
),
],
);
}

View file

@ -9,24 +9,30 @@ import '../../theme/theme.dart';
import 'contact_invitation_item_widget.dart';
class ContactInvitationListWidget extends StatefulWidget {
final IList<proto.ContactInvitationRecord> contactInvitationRecordList;
final bool disabled;
const ContactInvitationListWidget({
required this.contactInvitationRecordList,
required this.disabled,
super.key,
});
final IList<proto.ContactInvitationRecord> contactInvitationRecordList;
final bool disabled;
@override
ContactInvitationListWidgetState createState() =>
ContactInvitationListWidgetState();
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties
..add(IterableProperty<proto.ContactInvitationRecord>(
'contactInvitationRecordList', contactInvitationRecordList))
..add(
IterableProperty<proto.ContactInvitationRecord>(
'contactInvitationRecordList',
contactInvitationRecordList,
),
)
..add(DiagnosticsProperty<bool>('disabled', disabled));
}
}
@ -35,13 +41,17 @@ class ContactInvitationListWidgetState
extends State<ContactInvitationListWidget>
with SingleTickerProviderStateMixin {
late final _controller = AnimationController(
vsync: this, duration: const Duration(milliseconds: 250), value: 1);
late final _animation =
CurvedAnimation(parent: _controller, curve: Curves.easeInOut);
bool _expanded = true;
vsync: this,
duration: const Duration(milliseconds: 250),
value: 1,
);
late final _animation = CurvedAnimation(
parent: _controller,
curve: Curves.easeInOut,
);
var _expanded = true;
@override
// ignore: prefer_expression_function_bodies
Widget build(BuildContext context) {
final theme = Theme.of(context);
// final textTheme = theme.textTheme;
@ -49,41 +59,41 @@ class ContactInvitationListWidgetState
final scaleConfig = theme.extension<ScaleConfig>()!;
return styledExpandingSliver(
context: context,
animation: _animation,
expanded: _expanded,
backgroundColor: scaleConfig.preferBorders
? scale.primaryScale.subtleBackground
: scale.primaryScale.subtleBorder,
onTap: () {
setState(() {
_expanded = !_expanded;
});
_controller.animateTo(_expanded ? 1 : 0);
context: context,
animation: _animation,
expanded: _expanded,
backgroundColor: scaleConfig.preferBorders
? scale.primaryScale.subtleBackground
: scale.primaryScale.subtleBorder,
onTap: () {
setState(() {
_expanded = !_expanded;
});
_controller.animateTo(_expanded ? 1 : 0);
},
title: translate('contacts_dialog.invitations'),
sliver: SliverList.builder(
itemCount: widget.contactInvitationRecordList.length,
itemBuilder: (context, index) {
if (index < 0 || index >= widget.contactInvitationRecordList.length) {
return null;
}
return ContactInvitationItemWidget(
contactInvitationRecord: widget.contactInvitationRecordList[index],
disabled: widget.disabled,
key: ObjectKey(widget.contactInvitationRecordList[index]),
).paddingLTRB(4, 2, 4, 2);
},
title: translate('contacts_dialog.invitations'),
sliver: SliverList.builder(
itemCount: widget.contactInvitationRecordList.length,
itemBuilder: (context, index) {
if (index < 0 ||
index >= widget.contactInvitationRecordList.length) {
return null;
}
return ContactInvitationItemWidget(
contactInvitationRecord:
widget.contactInvitationRecordList[index],
disabled: widget.disabled,
key: ObjectKey(widget.contactInvitationRecordList[index]))
.paddingLTRB(4, 2, 4, 2);
},
findChildIndexCallback: (key) {
final index = widget.contactInvitationRecordList.indexOf(
(key as ObjectKey).value! as proto.ContactInvitationRecord);
if (index == -1) {
return null;
}
return index;
},
));
findChildIndexCallback: (key) {
final index = widget.contactInvitationRecordList.indexOf(
(key as ObjectKey).value! as proto.ContactInvitationRecord,
);
if (index == -1) {
return null;
}
return index;
},
),
);
}
}

View file

@ -15,6 +15,8 @@ import '../../theme/theme.dart';
import '../contact_invitation.dart';
class CreateInvitationDialog extends StatefulWidget {
final Locator locator;
const CreateInvitationDialog._({required this.locator});
@override
@ -22,13 +24,12 @@ class CreateInvitationDialog extends StatefulWidget {
static Future<void> show(BuildContext context) async {
await StyledDialog.show<void>(
context: context,
title: translate('create_invitation_dialog.title'),
child: CreateInvitationDialog._(locator: context.read));
context: context,
title: translate('create_invitation_dialog.title'),
child: CreateInvitationDialog._(locator: context.read),
);
}
final Locator locator;
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
@ -47,11 +48,15 @@ class _CreateInvitationDialogState extends State<CreateInvitationDialog> {
@override
void initState() {
final accountInfo = widget.locator<AccountRecordCubit>().state;
final name = accountInfo.asData?.value.profile.name ??
final name =
accountInfo.asData?.value.profile.name ??
translate('create_invitation_dialog.me');
_messageTextController = TextEditingController(
text: translate('create_invitation_dialog.connect_with_me',
args: {'name': name}));
text: translate(
'create_invitation_dialog.connect_with_me',
args: {'name': name},
),
);
_recipientTextController = TextEditingController();
super.initState();
}
@ -67,9 +72,10 @@ class _CreateInvitationDialogState extends State<CreateInvitationDialog> {
Future<void> _onPinEncryptionSelected(bool selected) async {
final description = translate('create_invitation_dialog.pin_description');
final pin = await showDialog<String>(
context: context,
builder: (context) =>
EnterPinDialog(reenter: false, description: description));
context: context,
builder: (context) =>
EnterPinDialog(reenter: false, description: description),
);
if (pin == null) {
return;
}
@ -77,11 +83,10 @@ class _CreateInvitationDialogState extends State<CreateInvitationDialog> {
return;
}
final matchpin = await showDialog<String>(
context: context,
builder: (context) => EnterPinDialog(
reenter: true,
description: description,
));
context: context,
builder: (context) =>
EnterPinDialog(reenter: true, description: description),
);
if (matchpin == null) {
return;
} else if (pin == matchpin) {
@ -94,7 +99,8 @@ class _CreateInvitationDialogState extends State<CreateInvitationDialog> {
return;
}
context.read<NotificationsCubit>().error(
text: translate('create_invitation_dialog.pin_does_not_match'));
text: translate('create_invitation_dialog.pin_does_not_match'),
);
setState(() {
_encryptionKeyType = EncryptionKeyType.none;
_encryptionKey = '';
@ -103,11 +109,13 @@ class _CreateInvitationDialogState extends State<CreateInvitationDialog> {
}
Future<void> _onPasswordEncryptionSelected(bool selected) async {
final description =
translate('create_invitation_dialog.password_description');
final description = translate(
'create_invitation_dialog.password_description',
);
final password = await showDialog<String>(
context: context,
builder: (context) => EnterPasswordDialog(description: description));
context: context,
builder: (context) => EnterPasswordDialog(description: description),
);
if (password == null) {
return;
}
@ -115,11 +123,10 @@ class _CreateInvitationDialogState extends State<CreateInvitationDialog> {
return;
}
final matchpass = await showDialog<String>(
context: context,
builder: (context) => EnterPasswordDialog(
matchPass: password,
description: description,
));
context: context,
builder: (context) =>
EnterPasswordDialog(matchPass: password, description: description),
);
if (matchpass == null) {
return;
} else if (password == matchpass) {
@ -132,7 +139,8 @@ class _CreateInvitationDialogState extends State<CreateInvitationDialog> {
return;
}
context.read<NotificationsCubit>().error(
text: translate('create_invitation_dialog.password_does_not_match'));
text: translate('create_invitation_dialog.password_does_not_match'),
);
setState(() {
_encryptionKeyType = EncryptionKeyType.none;
_encryptionKey = '';
@ -144,30 +152,36 @@ class _CreateInvitationDialogState extends State<CreateInvitationDialog> {
final navigator = Navigator.of(context);
// Start generation
final contactInvitationListCubit =
widget.locator<ContactInvitationListCubit>();
final profile =
widget.locator<AccountRecordCubit>().state.asData?.value.profile;
final contactInvitationListCubit = widget
.locator<ContactInvitationListCubit>();
final profile = widget
.locator<AccountRecordCubit>()
.state
.asData
?.value
.profile;
if (profile == null) {
return;
}
final generator = contactInvitationListCubit.createInvitation(
profile: profile,
encryptionKeyType: _encryptionKeyType,
encryptionKey: _encryptionKey,
recipient: _recipientTextController.text,
message: _messageTextController.text,
expiration: _expiration);
profile: profile,
encryptionKeyType: _encryptionKeyType,
encryptionKey: _encryptionKey,
recipient: _recipientTextController.text,
message: _messageTextController.text,
expiration: _expiration,
);
navigator.pop();
await ContactInvitationDisplayDialog.show(
context: context,
locator: widget.locator,
recipient: _recipientTextController.text,
message: _messageTextController.text,
create: (context) => InvitationGeneratorCubit(generator));
context: context,
locator: widget.locator,
recipient: _recipientTextController.text,
message: _messageTextController.text,
create: (context) => InvitationGeneratorCubit(generator),
);
}
@override
@ -180,8 +194,10 @@ class _CreateInvitationDialogState extends State<CreateInvitationDialog> {
final textTheme = theme.textTheme;
return ConstrainedBox(
constraints:
BoxConstraints(maxHeight: maxDialogHeight, maxWidth: maxDialogWidth),
constraints: BoxConstraints(
maxHeight: maxDialogHeight,
maxWidth: maxDialogWidth,
),
child: SingleChildScrollView(
padding: const EdgeInsets.all(8).scaled(context),
child: Column(
@ -195,55 +211,55 @@ class _CreateInvitationDialogState extends State<CreateInvitationDialog> {
onChanged: (value) {
setState(() {});
},
inputFormatters: [
LengthLimitingTextInputFormatter(128),
],
inputFormatters: [LengthLimitingTextInputFormatter(128)],
decoration: InputDecoration(
contentPadding: const EdgeInsets.all(8).scaled(context),
hintText:
translate('create_invitation_dialog.recipient_hint'),
labelText:
translate('create_invitation_dialog.recipient_name'),
helperText:
translate('create_invitation_dialog.recipient_helper')),
contentPadding: const EdgeInsets.all(8).scaled(context),
hintText: translate('create_invitation_dialog.recipient_hint'),
labelText: translate('create_invitation_dialog.recipient_name'),
helperText: translate(
'create_invitation_dialog.recipient_helper',
),
),
),
TextField(
controller: _messageTextController,
inputFormatters: [
LengthLimitingTextInputFormatter(128),
],
inputFormatters: [LengthLimitingTextInputFormatter(128)],
decoration: InputDecoration(
contentPadding: const EdgeInsets.all(8).scaled(context),
hintText: translate('create_invitation_dialog.message_hint'),
labelText:
translate('create_invitation_dialog.message_label'),
helperText:
translate('create_invitation_dialog.message_helper')),
contentPadding: const EdgeInsets.all(8).scaled(context),
hintText: translate('create_invitation_dialog.message_hint'),
labelText: translate('create_invitation_dialog.message_label'),
helperText: translate(
'create_invitation_dialog.message_helper',
),
),
),
Text(
translate('create_invitation_dialog.protect_this_invitation'),
style: textTheme.labelLarge,
),
Text(translate('create_invitation_dialog.protect_this_invitation'),
style: textTheme.labelLarge),
Wrap(
alignment: WrapAlignment.center,
runAlignment: WrapAlignment.center,
runSpacing: 8,
spacing: 8,
children: [
ChoiceChip(
label: Text(translate('create_invitation_dialog.unlocked')),
selected: _encryptionKeyType == EncryptionKeyType.none,
onSelected: _onNoneEncryptionSelected,
),
ChoiceChip(
label: Text(translate('create_invitation_dialog.pin')),
selected: _encryptionKeyType == EncryptionKeyType.pin,
onSelected: _onPinEncryptionSelected,
),
ChoiceChip(
label: Text(translate('create_invitation_dialog.password')),
selected: _encryptionKeyType == EncryptionKeyType.password,
onSelected: _onPasswordEncryptionSelected,
)
]).toCenter(),
alignment: WrapAlignment.center,
runAlignment: WrapAlignment.center,
runSpacing: 8,
spacing: 8,
children: [
ChoiceChip(
label: Text(translate('create_invitation_dialog.unlocked')),
selected: _encryptionKeyType == EncryptionKeyType.none,
onSelected: _onNoneEncryptionSelected,
),
ChoiceChip(
label: Text(translate('create_invitation_dialog.pin')),
selected: _encryptionKeyType == EncryptionKeyType.pin,
onSelected: _onPinEncryptionSelected,
),
ChoiceChip(
label: Text(translate('create_invitation_dialog.password')),
selected: _encryptionKeyType == EncryptionKeyType.password,
onSelected: _onPasswordEncryptionSelected,
),
],
).toCenter(),
Container(
padding: const EdgeInsets.all(8).scaled(context),
child: ElevatedButton(

View file

@ -16,48 +16,74 @@ import '../../tools/tools.dart';
import '../contact_invitation.dart';
class InvitationDialog extends StatefulWidget {
const InvitationDialog(
{required Locator locator,
required this.onValidationCancelled,
required this.onValidationSuccess,
required this.onValidationFailed,
required this.inviteControlIsValid,
required this.buildInviteControl,
super.key})
: _locator = locator;
final void Function() onValidationCancelled;
final void Function() onValidationSuccess;
final void Function() onValidationFailed;
final bool Function() inviteControlIsValid;
final Widget Function(
BuildContext context,
InvitationDialogState dialogState,
Future<void> Function({required Uint8List inviteData})
validateInviteData) buildInviteControl;
BuildContext context,
InvitationDialogState dialogState,
Future<void> Function({required Uint8List inviteData}) validateInviteData,
)
buildInviteControl;
final Locator _locator;
const InvitationDialog({
required Locator locator,
required this.onValidationCancelled,
required this.onValidationSuccess,
required this.onValidationFailed,
required this.inviteControlIsValid,
required this.buildInviteControl,
super.key,
}) : _locator = locator;
@override
InvitationDialogState createState() => InvitationDialogState();
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties
..add(ObjectFlagProperty<void Function()>.has(
'onValidationCancelled', onValidationCancelled))
..add(ObjectFlagProperty<void Function()>.has(
'onValidationSuccess', onValidationSuccess))
..add(ObjectFlagProperty<void Function()>.has(
'onValidationFailed', onValidationFailed))
..add(ObjectFlagProperty<void Function()>.has(
'inviteControlIsValid', inviteControlIsValid))
..add(ObjectFlagProperty<
Widget Function(
BuildContext context,
InvitationDialogState dialogState,
Future<void> Function({required Uint8List inviteData})
validateInviteData)>.has(
'buildInviteControl', buildInviteControl));
..add(
ObjectFlagProperty<void Function()>.has(
'onValidationCancelled',
onValidationCancelled,
),
)
..add(
ObjectFlagProperty<void Function()>.has(
'onValidationSuccess',
onValidationSuccess,
),
)
..add(
ObjectFlagProperty<void Function()>.has(
'onValidationFailed',
onValidationFailed,
),
)
..add(
ObjectFlagProperty<void Function()>.has(
'inviteControlIsValid',
inviteControlIsValid,
),
)
..add(
ObjectFlagProperty<
Widget Function(
BuildContext context,
InvitationDialogState dialogState,
Future<void> Function({required Uint8List inviteData})
validateInviteData,
)
>.has('buildInviteControl', buildInviteControl),
);
}
}
@ -80,8 +106,12 @@ class InvitationDialogState extends State<InvitationDialog> {
final navigator = Navigator.of(context);
final accountInfo = widget._locator<AccountInfoCubit>().state;
final contactList = widget._locator<ContactListCubit>();
final profile =
widget._locator<AccountRecordCubit>().state.asData!.value.profile;
final profile = widget
._locator<AccountRecordCubit>()
.state
.asData!
.value
.profile;
setState(() {
_isAccepting = true;
@ -92,7 +122,8 @@ class InvitationDialogState extends State<InvitationDialog> {
if (acceptedContact != null) {
// initiator when accept is received will create
// contact in the case of a 'note to self'
final isSelf = accountInfo.identityPublicKey ==
final isSelf =
accountInfo.identityPublicKey ==
acceptedContact.remoteIdentity.currentInstance.publicKey;
if (!isSelf) {
await contactList.createContact(
@ -106,9 +137,9 @@ class InvitationDialogState extends State<InvitationDialog> {
}
} else {
if (mounted) {
context
.read<NotificationsCubit>()
.error(text: 'invitation_dialog.failed_to_accept');
context.read<NotificationsCubit>().error(
text: 'invitation_dialog.failed_to_accept',
);
}
}
}
@ -130,9 +161,9 @@ class InvitationDialogState extends State<InvitationDialog> {
// do nothing right now
} else {
if (mounted) {
context
.read<NotificationsCubit>()
.error(text: 'invitation_dialog.failed_to_reject');
context.read<NotificationsCubit>().error(
text: 'invitation_dialog.failed_to_reject',
);
}
}
}
@ -142,61 +173,67 @@ class InvitationDialogState extends State<InvitationDialog> {
navigator.pop();
}
Future<void> _validateInviteData({
required Uint8List inviteData,
}) async {
Future<void> _validateInviteData({required Uint8List inviteData}) async {
try {
final contactInvitationListCubit =
widget._locator<ContactInvitationListCubit>();
final contactInvitationListCubit = widget
._locator<ContactInvitationListCubit>();
setState(() {
_isValidating = true;
_validInvitation = null;
});
final validatedContactInvitation =
await contactInvitationListCubit.validateInvitation(
inviteData: inviteData,
cancelRequest: _cancelRequest,
getEncryptionKeyCallback:
(cs, encryptionKeyType, encryptedSecret) async {
String encryptionKey;
switch (encryptionKeyType) {
case EncryptionKeyType.none:
encryptionKey = '';
case EncryptionKeyType.pin:
final description =
translate('invitation_dialog.protected_with_pin');
if (!mounted) {
return null;
}
final pin = await showDialog<String>(
final validatedContactInvitation = await contactInvitationListCubit
.validateInvitation(
inviteData: inviteData,
cancelRequest: _cancelRequest,
getEncryptionKeyCallback:
(cs, encryptionKeyType, encryptedSecret) async {
String encryptionKey;
switch (encryptionKeyType) {
case EncryptionKeyType.none:
encryptionKey = '';
case EncryptionKeyType.pin:
final description = translate(
'invitation_dialog.protected_with_pin',
);
if (!mounted) {
return null;
}
final pin = await showDialog<String>(
context: context,
builder: (context) => EnterPinDialog(
reenter: false, description: description));
if (pin == null) {
return null;
}
encryptionKey = pin;
case EncryptionKeyType.password:
final description =
translate('invitation_dialog.protected_with_password');
if (!mounted) {
return null;
}
final password = await showDialog<String>(
reenter: false,
description: description,
),
);
if (pin == null) {
return null;
}
encryptionKey = pin;
case EncryptionKeyType.password:
final description = translate(
'invitation_dialog.protected_with_password',
);
if (!mounted) {
return null;
}
final password = await showDialog<String>(
context: context,
builder: (context) =>
EnterPasswordDialog(description: description));
if (password == null) {
return null;
}
encryptionKey = password;
}
return encryptionKeyType.decryptSecretFromBytes(
EnterPasswordDialog(description: description),
);
if (password == null) {
return null;
}
encryptionKey = password;
}
return encryptionKeyType.decryptSecretFromBytes(
secretBytes: encryptedSecret,
cryptoKind: cs.kind(),
encryptionKey: encryptionKey);
});
encryptionKey: encryptionKey,
);
},
);
// Check if validation was cancelled
if (validatedContactInvitation == null) {
@ -218,9 +255,9 @@ class InvitationDialogState extends State<InvitationDialog> {
});
} on ContactInviteInvalidIdentityException catch (_) {
if (mounted) {
context
.read<NotificationsCubit>()
.error(text: translate('invitation_dialog.invalid_identity'));
context.read<NotificationsCubit>().error(
text: translate('invitation_dialog.invalid_identity'),
);
}
setState(() {
_isValidating = false;
@ -281,54 +318,61 @@ class InvitationDialogState extends State<InvitationDialog> {
}
List<Widget> _buildPreAccept() => <Widget>[
if (!_isValidating && _validInvitation == null)
widget.buildInviteControl(context, this, _validateInviteData),
if (_isValidating)
Column(children: [
Text(translate('invitation_dialog.validating'))
.paddingLTRB(0, 0, 0, 16),
buildProgressIndicator().paddingAll(16),
ElevatedButton.icon(
icon: const Icon(Icons.cancel),
label: Text(translate('button.cancel')),
onPressed: _onCancel,
).paddingAll(16),
]).toCenter(),
if (_validInvitation == null &&
!_isValidating &&
widget.inviteControlIsValid())
Column(children: [
Text(translate('invitation_dialog.invalid_invitation')),
const Icon(Icons.error).paddingAll(16)
]).toCenter(),
if (_validInvitation != null && !_isValidating)
Column(crossAxisAlignment: CrossAxisAlignment.stretch, children: [
ProfileWidget(
profile: _validInvitation!.remoteProfile,
byline: _validInvitation!.remoteProfile.pronouns.isEmpty
? null
: _validInvitation!.remoteProfile.pronouns,
).paddingLTRB(0, 0, 0, 16),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton.icon(
icon: const Icon(Icons.check_circle),
label: Text(translate('button.accept')),
onPressed: _onAccept,
).paddingLTRB(0, 0, 8, 0),
ElevatedButton.icon(
icon: const Icon(Icons.cancel),
label: Text(translate('button.reject')),
onPressed: _onReject,
).paddingLTRB(8, 0, 0, 0)
],
),
])
];
if (!_isValidating && _validInvitation == null)
widget.buildInviteControl(context, this, _validateInviteData),
if (_isValidating)
Column(
children: [
Text(
translate('invitation_dialog.validating'),
).paddingLTRB(0, 0, 0, 16),
buildProgressIndicator().paddingAll(16),
ElevatedButton.icon(
icon: const Icon(Icons.cancel),
label: Text(translate('button.cancel')),
onPressed: _onCancel,
).paddingAll(16),
],
).toCenter(),
if (_validInvitation == null &&
!_isValidating &&
widget.inviteControlIsValid())
Column(
children: [
Text(translate('invitation_dialog.invalid_invitation')),
const Icon(Icons.error).paddingAll(16),
],
).toCenter(),
if (_validInvitation != null && !_isValidating)
Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
ProfileWidget(
profile: _validInvitation!.remoteProfile,
byline: _validInvitation!.remoteProfile.pronouns.isEmpty
? null
: _validInvitation!.remoteProfile.pronouns,
).paddingLTRB(0, 0, 0, 16),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton.icon(
icon: const Icon(Icons.check_circle),
label: Text(translate('button.accept')),
onPressed: _onAccept,
).paddingLTRB(0, 0, 8, 0),
ElevatedButton.icon(
icon: const Icon(Icons.cancel),
label: Text(translate('button.reject')),
onPressed: _onReject,
).paddingLTRB(8, 0, 0, 0),
],
),
],
),
];
@override
// ignore: prefer_expression_function_bodies
Widget build(BuildContext context) {
// final theme = Theme.of(context);
// final scale = theme.extension<ScaleScheme>()!;
@ -341,12 +385,11 @@ class InvitationDialogState extends State<InvitationDialog> {
child: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
mainAxisSize: MainAxisSize.min,
children: _isAccepting
? [
buildProgressIndicator().paddingAll(16),
]
: _buildPreAccept()),
mainAxisSize: MainAxisSize.min,
children: _isAccepting
? [buildProgressIndicator().paddingAll(16)]
: _buildPreAccept(),
),
),
);
return PopControl(dismissible: dismissible, child: dialog);
@ -355,8 +398,8 @@ class InvitationDialogState extends State<InvitationDialog> {
////////////////////////////////////////////////////////////////////////////
ValidContactInvitation? _validInvitation;
bool _isValidating = false;
bool _isAccepting = false;
var _isValidating = false;
var _isAccepting = false;
final _cancelRequest = CancelRequest();
bool get isValidating => _isValidating;

View file

@ -11,8 +11,10 @@ import '../../theme/theme.dart';
import 'invitation_dialog.dart';
class PasteInvitationDialog extends StatefulWidget {
final Locator _locator;
const PasteInvitationDialog({required Locator locator, super.key})
: _locator = locator;
: _locator = locator;
@override
PasteInvitationDialogState createState() => PasteInvitationDialogState();
@ -21,13 +23,13 @@ class PasteInvitationDialog extends StatefulWidget {
final locator = context.read;
await showPopControlDialog<void>(
context: context,
builder: (context) => StyledDialog(
title: translate('paste_invitation_dialog.title'),
child: PasteInvitationDialog(locator: locator)));
context: context,
builder: (context) => StyledDialog(
title: translate('paste_invitation_dialog.title'),
child: PasteInvitationDialog(locator: locator),
),
);
}
final Locator _locator;
}
class PasteInvitationDialogState extends State<PasteInvitationDialog> {
@ -39,21 +41,22 @@ class PasteInvitationDialogState extends State<PasteInvitationDialog> {
}
Future<void> _onPasteChanged(
String text,
Future<void> Function({
required Uint8List inviteData,
}) validateInviteData) async {
String text,
Future<void> Function({required Uint8List inviteData}) validateInviteData,
) async {
final lines = text.split('\n');
if (lines.isEmpty) {
return;
}
var firstline =
lines.indexWhere((element) => element.contains('BEGIN VEILIDCHAT'));
var firstline = lines.indexWhere(
(element) => element.contains('BEGIN VEILIDCHAT'),
);
firstline += 1;
var lastline =
lines.indexWhere((element) => element.contains('END VEILIDCHAT'));
var lastline = lines.indexWhere(
(element) => element.contains('END VEILIDCHAT'),
);
if (lastline == -1) {
lastline = lines.length;
}
@ -89,10 +92,10 @@ class PasteInvitationDialogState extends State<PasteInvitationDialog> {
bool inviteControlIsValid() => _pasteTextController.text.isNotEmpty;
Widget buildInviteControl(
BuildContext context,
InvitationDialogState dialogState,
Future<void> Function({required Uint8List inviteData})
validateInviteData) {
BuildContext context,
InvitationDialogState dialogState,
Future<void> Function({required Uint8List inviteData}) validateInviteData,
) {
final theme = Theme.of(context);
final scale = theme.extension<ScaleScheme>()!;
//final textTheme = theme.textTheme;
@ -104,11 +107,13 @@ class PasteInvitationDialogState extends State<PasteInvitationDialog> {
color: scale.primaryScale.appText,
);
return Column(mainAxisSize: MainAxisSize.min, children: [
Text(
translate('paste_invitation_dialog.paste_invite_here'),
).paddingLTRB(0, 0, 0, 16),
Container(
return Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
translate('paste_invitation_dialog.paste_invite_here'),
).paddingLTRB(0, 0, 0, 16),
Container(
constraints: const BoxConstraints(maxHeight: 200),
child: TextField(
enabled: !dialogState.isValidating,
@ -119,21 +124,25 @@ class PasteInvitationDialogState extends State<PasteInvitationDialog> {
maxLines: null,
controller: _pasteTextController,
decoration: const InputDecoration(
hintText: '--- BEGIN VEILIDCHAT CONTACT INVITE ----\n'
hintText:
'--- BEGIN VEILIDCHAT CONTACT INVITE ----\n'
'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\n'
'---- END VEILIDCHAT CONTACT INVITE -----\n',
//labelText: translate('paste_invitation_dialog.paste')
),
)).paddingLTRB(0, 0, 0, 8)
]);
),
).paddingLTRB(0, 0, 0, 8),
],
);
}
@override
Widget build(BuildContext context) => InvitationDialog(
locator: widget._locator,
onValidationCancelled: onValidationCancelled,
onValidationSuccess: onValidationSuccess,
onValidationFailed: onValidationFailed,
inviteControlIsValid: inviteControlIsValid,
buildInviteControl: buildInviteControl);
locator: widget._locator,
onValidationCancelled: onValidationCancelled,
onValidationSuccess: onValidationSuccess,
onValidationFailed: onValidationFailed,
inviteControlIsValid: inviteControlIsValid,
buildInviteControl: buildInviteControl,
);
}

View file

@ -16,8 +16,10 @@ import 'camera_qr_scanner.dart';
import 'invitation_dialog.dart';
class ScanInvitationDialog extends StatefulWidget {
final Locator _locator;
const ScanInvitationDialog({required Locator locator, super.key})
: _locator = locator;
: _locator = locator;
@override
ScanInvitationDialogState createState() => ScanInvitationDialogState();
@ -25,13 +27,13 @@ class ScanInvitationDialog extends StatefulWidget {
static Future<void> show(BuildContext context) async {
final locator = context.read;
await showPopControlDialog<void>(
context: context,
builder: (context) => StyledDialog(
title: translate('scan_invitation_dialog.title'),
child: ScanInvitationDialog(locator: locator)));
context: context,
builder: (context) => StyledDialog(
title: translate('scan_invitation_dialog.title'),
child: ScanInvitationDialog(locator: locator),
),
);
}
final Locator _locator;
}
class ScanInvitationDialogState extends State<ScanInvitationDialog> {
@ -63,62 +65,69 @@ class ScanInvitationDialogState extends State<ScanInvitationDialog> {
try {
return showDialog(
context: context,
builder: (context) => Stack(
fit: StackFit.expand,
children: [
CameraQRScanner(
scanSize: const Size(200, 200),
loadingBuilder: (context) => waitingPage(),
errorBuilder: (coRntext, e, st) => errorPage(e, st),
bottomRowBuilder: (context) => FittedBox(
fit: BoxFit.scaleDown,
child: Text(
translate(
'scan_invitation_dialog.instructions'),
overflow: TextOverflow.ellipsis,
style: theme.textTheme.labelLarge),
),
showNotification: (s) {},
logError: log.error,
cameraErrorMessage:
translate('scan_invitation_dialog.camera_error'),
deniedErrorMessage:
translate('scan_invitation_dialog.permission_error'),
deniedWithoutPromptErrorMessage:
translate('scan_invitation_dialog.permission_error'),
restrictedErrorMessage:
translate('scan_invitation_dialog.permission_error'),
onDetect: (result) {
final byteSegments = result
.resultMetadata[ResultMetadataType.byteSegments];
if (byteSegments != null) {
final segs = byteSegments as List<Int8List>;
context: context,
builder: (context) => Stack(
fit: StackFit.expand,
children: [
CameraQRScanner(
scanSize: const Size(200, 200),
loadingBuilder: (context) => waitingPage(),
errorBuilder: (coRntext, e, st) => errorPage(e, st),
bottomRowBuilder: (context) => FittedBox(
fit: BoxFit.scaleDown,
child: Text(
translate('scan_invitation_dialog.instructions'),
overflow: TextOverflow.ellipsis,
style: theme.textTheme.labelLarge,
),
),
showNotification: (s) {},
logError: log.error,
cameraErrorMessage: translate(
'scan_invitation_dialog.camera_error',
),
deniedErrorMessage: translate(
'scan_invitation_dialog.permission_error',
),
deniedWithoutPromptErrorMessage: translate(
'scan_invitation_dialog.permission_error',
),
restrictedErrorMessage: translate(
'scan_invitation_dialog.permission_error',
),
onDetect: (result) {
final byteSegments =
result.resultMetadata[ResultMetadataType.byteSegments];
if (byteSegments != null) {
final segs = byteSegments as List<Int8List>;
final byteData = Uint8List.fromList(segs[0].toList());
return byteData;
}
return null;
},
onDone: (result) {
Navigator.of(context).pop(result);
}),
Align(
alignment: Alignment.topRight,
child: IconButton(
color: Colors.white,
icon: Icon(Icons.close,
color: scale.primaryScale.appText),
iconSize: 32.scaled(context),
onPressed: () {
Navigator.of(context).pop();
})),
],
));
final byteData = Uint8List.fromList(segs[0].toList());
return byteData;
}
return null;
},
onDone: (result) {
Navigator.of(context).pop(result);
},
),
Align(
alignment: Alignment.topRight,
child: IconButton(
color: Colors.white,
icon: Icon(Icons.close, color: scale.primaryScale.appText),
iconSize: 32.scaled(context),
onPressed: () {
Navigator.of(context).pop();
},
),
),
],
),
);
} on Exception catch (_) {
context
.read<NotificationsCubit>()
.error(text: translate('scan_invitation_dialog.error'));
context.read<NotificationsCubit>().error(
text: translate('scan_invitation_dialog.error'),
);
}
return null;
@ -128,9 +137,9 @@ class ScanInvitationDialogState extends State<ScanInvitationDialog> {
final imageBytes = await Pasteboard.image;
if (imageBytes == null) {
if (context.mounted) {
context
.read<NotificationsCubit>()
.error(text: translate('scan_invitation_dialog.not_an_image'));
context.read<NotificationsCubit>().error(
text: translate('scan_invitation_dialog.not_an_image'),
);
}
return null;
}
@ -139,42 +148,46 @@ class ScanInvitationDialogState extends State<ScanInvitationDialog> {
if (image == null) {
if (context.mounted) {
context.read<NotificationsCubit>().error(
text: translate('scan_invitation_dialog.could_not_decode_image'));
text: translate('scan_invitation_dialog.could_not_decode_image'),
);
}
return null;
}
try {
final source = RGBLuminanceSource(
image.width,
image.height,
image
.convert(numChannels: 4)
.getBytes(order: img.ChannelOrder.abgr)
.buffer
.asInt32List());
image.width,
image.height,
image
.convert(numChannels: 4)
.getBytes(order: img.ChannelOrder.abgr)
.buffer
.asInt32List(),
);
final bitmap = BinaryBitmap(HybridBinarizer(source));
final reader = QRCodeReader();
final result = reader.decode(bitmap);
final segs = result.resultMetadata[ResultMetadataType.byteSegments]!
as List<Int8List>;
final segs =
result.resultMetadata[ResultMetadataType.byteSegments]!
as List<Int8List>;
return Uint8List.fromList(segs[0].toList());
} on Exception catch (_) {
if (context.mounted) {
context.read<NotificationsCubit>().error(
text: translate('scan_invitation_dialog.not_a_valid_qr_code'));
text: translate('scan_invitation_dialog.not_a_valid_qr_code'),
);
}
return null;
}
}
Widget buildInviteControl(
BuildContext context,
InvitationDialogState dialogState,
Future<void> Function({required Uint8List inviteData})
validateInviteData) {
BuildContext context,
InvitationDialogState dialogState,
Future<void> Function({required Uint8List inviteData}) validateInviteData,
) {
if (_scanned) {
return const SizedBox.shrink();
}
@ -188,19 +201,20 @@ class ScanInvitationDialogState extends State<ScanInvitationDialog> {
Container(
constraints: const BoxConstraints(maxHeight: 200),
child: ElevatedButton(
onPressed: dialogState.isValidating
? null
: () async {
final inviteData = await scanQRImage(context);
if (inviteData != null) {
setState(() {
_scanned = true;
});
await validateInviteData(inviteData: inviteData);
}
},
child: Text(translate('scan_invitation_dialog.scan'))),
).paddingLTRB(0, 0, 0, 8)
onPressed: dialogState.isValidating
? null
: () async {
final inviteData = await scanQRImage(context);
if (inviteData != null) {
setState(() {
_scanned = true;
});
await validateInviteData(inviteData: inviteData);
}
},
child: Text(translate('scan_invitation_dialog.scan')),
),
).paddingLTRB(0, 0, 0, 8),
]);
}
@ -211,19 +225,20 @@ class ScanInvitationDialogState extends State<ScanInvitationDialog> {
Container(
constraints: const BoxConstraints(maxHeight: 200),
child: ElevatedButton(
onPressed: dialogState.isValidating
? null
: () async {
final inviteData = await pasteQRImage(context);
if (inviteData != null) {
await validateInviteData(inviteData: inviteData);
setState(() {
_scanned = true;
});
}
},
child: Text(translate('scan_invitation_dialog.paste'))),
).paddingLTRB(0, 0, 0, 8)
onPressed: dialogState.isValidating
? null
: () async {
final inviteData = await pasteQRImage(context);
if (inviteData != null) {
await validateInviteData(inviteData: inviteData);
setState(() {
_scanned = true;
});
}
},
child: Text(translate('scan_invitation_dialog.paste')),
),
).paddingLTRB(0, 0, 0, 8),
]);
return Column(mainAxisSize: MainAxisSize.min, children: children);
@ -231,10 +246,11 @@ class ScanInvitationDialogState extends State<ScanInvitationDialog> {
@override
Widget build(BuildContext context) => InvitationDialog(
locator: widget._locator,
onValidationCancelled: onValidationCancelled,
onValidationSuccess: onValidationSuccess,
onValidationFailed: onValidationFailed,
inviteControlIsValid: inviteControlIsValid,
buildInviteControl: buildInviteControl);
locator: widget._locator,
onValidationCancelled: onValidationCancelled,
onValidationSuccess: onValidationSuccess,
onValidationFailed: onValidationFailed,
inviteControlIsValid: inviteControlIsValid,
buildInviteControl: buildInviteControl,
);
}

View file

@ -15,19 +15,27 @@ import '../models/models.dart';
// Mutable state for per-account contacts
class ContactListCubit extends DHTShortArrayCubit<proto.Contact> {
final _contactProfileUpdateMap =
SingleStateProcessorMap<RecordKey, proto.Profile?>();
ContactListCubit({
required AccountInfo accountInfo,
required OwnedDHTRecordPointer contactListRecordPointer,
}) : super(
open: () =>
_open(accountInfo.accountRecordKey, contactListRecordPointer),
decodeElement: proto.Contact.fromBuffer);
open: () =>
_open(accountInfo.accountRecordKey, contactListRecordPointer),
decodeElement: proto.Contact.fromBuffer,
);
static Future<DHTShortArray> _open(RecordKey accountRecordKey,
OwnedDHTRecordPointer contactListRecordPointer) async {
final dhtRecord = await DHTShortArray.openOwned(contactListRecordPointer,
debugName: 'ContactListCubit::_open::ContactList',
parent: accountRecordKey);
static Future<DHTShortArray> _open(
RecordKey accountRecordKey,
OwnedDHTRecordPointer contactListRecordPointer,
) async {
final dhtRecord = await DHTShortArray.openOwned(
contactListRecordPointer,
debugName: 'ContactListCubit::_open::ContactList',
parent: accountRecordKey,
);
return dhtRecord;
}
@ -37,21 +45,29 @@ class ContactListCubit extends DHTShortArrayCubit<proto.Contact> {
await _contactProfileUpdateMap.close();
await super.close();
}
////////////////////////////////////////////////////////////////////////////
// Public Interface
void followContactProfileChanges(RecordKey localConversationRecordKey,
Stream<proto.Profile?> profileStream, proto.Profile? profileState) {
_contactProfileUpdateMap
.follow(localConversationRecordKey, profileStream, profileState,
(remoteProfile) async {
if (remoteProfile == null) {
return;
}
return updateContactProfile(
void followContactProfileChanges(
RecordKey localConversationRecordKey,
Stream<proto.Profile?> profileStream,
proto.Profile? profileState,
) {
_contactProfileUpdateMap.follow(
localConversationRecordKey,
profileStream,
profileState,
(remoteProfile) async {
if (remoteProfile == null) {
return;
}
return updateContactProfile(
localConversationRecordKey: localConversationRecordKey,
profile: remoteProfile);
});
profile: remoteProfile,
);
},
);
}
Future<void> updateContactProfile({
@ -71,7 +87,10 @@ class ContactListCubit extends DHTShortArrayCubit<proto.Contact> {
}
final newContact = c.deepCopy()..profile = profile;
final updated = await writer.tryWriteItemProtobuf(
proto.Contact.fromBuffer, pos, newContact);
proto.Contact.fromBuffer,
pos,
newContact,
);
if (!updated) {
throw const DHTExceptionOutdated();
}
@ -95,7 +114,10 @@ class ContactListCubit extends DHTShortArrayCubit<proto.Contact> {
final newContact = await updatedContactSpec.updateProto(c);
final updated = await writer.tryWriteItemProtobuf(
proto.Contact.fromBuffer, pos, newContact);
proto.Contact.fromBuffer,
pos,
newContact,
);
if (!updated) {
throw const DHTExceptionOutdated();
}
@ -115,8 +137,8 @@ class ContactListCubit extends DHTShortArrayCubit<proto.Contact> {
final contact = proto.Contact()
..profile = profile
..superIdentityJson = jsonEncode(remoteSuperIdentity.toJson())
..author =
(await remoteSuperIdentity.currentInstance.getAuthor()).toProto()
..author = (await remoteSuperIdentity.currentInstance.getAuthor())
.toProto()
..localConversationRecordKey = localConversationRecordKey.toProto()
..remoteConversationRecordKey = remoteConversationRecordKey.toProto()
..showAvailability = false;
@ -127,8 +149,9 @@ class ContactListCubit extends DHTShortArrayCubit<proto.Contact> {
});
}
Future<void> deleteContact(
{required RecordKey localConversationRecordKey}) async {
Future<void> deleteContact({
required RecordKey localConversationRecordKey,
}) async {
// Remove Contact from account's list
final deletedItem = await operateWriteEventual((writer) async {
for (var i = 0; i < writer.length; i++) {
@ -148,20 +171,19 @@ class ContactListCubit extends DHTShortArrayCubit<proto.Contact> {
if (deletedItem != null) {
try {
// Mark the conversation records for deletion
await DHTRecordPool.instance
.deleteRecord(deletedItem.localConversationRecordKey.toDart());
await DHTRecordPool.instance.deleteRecord(
deletedItem.localConversationRecordKey.toDart(),
);
} on Exception catch (e) {
log.debug('error deleting local conversation record: $e', e);
}
try {
await DHTRecordPool.instance
.deleteRecord(deletedItem.remoteConversationRecordKey.toDart());
await DHTRecordPool.instance.deleteRecord(
deletedItem.remoteConversationRecordKey.toDart(),
);
} on Exception catch (e) {
log.debug('error deleting remote conversation record: $e', e);
}
}
}
final _contactProfileUpdateMap =
SingleStateProcessorMap<RecordKey, proto.Profile?>();
}

View file

@ -6,6 +6,14 @@ import '../../proto/proto.dart' as proto;
@immutable
class ContactSpec extends Equatable {
////////////////////////////////////////////////////////////////////////////
final String nickname;
final String notes;
final bool showAvailability;
const ContactSpec({
required this.nickname,
required this.notes,
@ -13,9 +21,9 @@ class ContactSpec extends Equatable {
});
ContactSpec.fromProto(proto.Contact p)
: nickname = p.nickname,
notes = p.notes,
showAvailability = p.showAvailability;
: nickname = p.nickname,
notes = p.notes,
showAvailability = p.showAvailability;
Future<proto.Contact> updateProto(proto.Contact old) async {
final newProto = old.deepCopy()
@ -26,12 +34,6 @@ class ContactSpec extends Equatable {
return newProto;
}
////////////////////////////////////////////////////////////////////////////
final String nickname;
final String notes;
final bool showAvailability;
@override
List<Object?> get props => [nickname, notes, showAvailability];
}

View file

@ -7,11 +7,20 @@ import '../../proto/proto.dart' as proto;
import '../../theme/theme.dart';
class AvailabilityWidget extends StatelessWidget {
const AvailabilityWidget(
{required this.availability,
required this.color,
this.vertical = true,
super.key});
////////////////////////////////////////////////////////////////////////////
final proto.Availability availability;
final Color color;
final bool vertical;
const AvailabilityWidget({
required this.availability,
required this.color,
this.vertical = true,
super.key,
});
static Widget availabilityIcon(
BuildContext context,
@ -21,10 +30,12 @@ class AvailabilityWidget extends StatelessWidget {
late final Widget icon;
switch (availability) {
case proto.Availability.AVAILABILITY_AWAY:
icon = SvgPicture.asset('assets/images/toilet.svg',
width: 24.scaled(context),
height: 24.scaled(context),
colorFilter: ColorFilter.mode(color, BlendMode.srcATop));
icon = SvgPicture.asset(
'assets/images/toilet.svg',
width: 24.scaled(context),
height: 24.scaled(context),
colorFilter: ColorFilter.mode(color, BlendMode.srcATop),
);
case proto.Availability.AVAILABILITY_BUSY:
icon = Icon(size: 24.scaled(context), Icons.event_busy);
case proto.Availability.AVAILABILITY_FREE:
@ -63,28 +74,32 @@ class AvailabilityWidget extends StatelessWidget {
final icon = availabilityIcon(context, availability, color);
return vertical
? Column(mainAxisSize: MainAxisSize.min, children: [
icon,
Text(name, style: textTheme.labelSmall!.copyWith(color: color))
])
: Row(mainAxisSize: MainAxisSize.min, children: [
icon,
Text(' $name', style: textTheme.labelLarge!.copyWith(color: color))
]);
? Column(
mainAxisSize: MainAxisSize.min,
children: [
icon,
Text(name, style: textTheme.labelSmall!.copyWith(color: color)),
],
)
: Row(
mainAxisSize: MainAxisSize.min,
children: [
icon,
Text(
' $name',
style: textTheme.labelLarge!.copyWith(color: color),
),
],
);
}
////////////////////////////////////////////////////////////////////////////
final proto.Availability availability;
final Color color;
final bool vertical;
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties
..add(
DiagnosticsProperty<proto.Availability>('availability', availability))
DiagnosticsProperty<proto.Availability>('availability', availability),
)
..add(DiagnosticsProperty<bool>('vertical', vertical))
..add(ColorProperty('color', color));
}

View file

@ -8,8 +8,15 @@ import '../../tools/tools.dart';
import '../contacts.dart';
class ContactDetailsWidget extends StatefulWidget {
const ContactDetailsWidget(
{required this.contact, this.onModifiedState, super.key});
final proto.Contact contact;
final void Function(bool)? onModifiedState;
const ContactDetailsWidget({
required this.contact,
this.onModifiedState,
super.key,
});
@override
State<ContactDetailsWidget> createState() => _ContactDetailsWidgetState();
@ -19,33 +26,39 @@ class ContactDetailsWidget extends StatefulWidget {
super.debugFillProperties(properties);
properties
..add(DiagnosticsProperty<proto.Contact>('contact', contact))
..add(ObjectFlagProperty<void Function(bool p1)?>.has(
'onModifiedState', onModifiedState));
..add(
ObjectFlagProperty<void Function(bool p1)?>.has(
'onModifiedState',
onModifiedState,
),
);
}
final proto.Contact contact;
final void Function(bool)? onModifiedState;
}
class _ContactDetailsWidgetState extends State<ContactDetailsWidget> {
@override
Widget build(BuildContext context) => SingleChildScrollView(
child: EditContactForm(
contact: widget.contact,
submitText: translate('button.update'),
submitDisabledText: translate('button.waiting_for_network'),
onModifiedState: widget.onModifiedState,
onSubmit: (updatedContactSpec) async {
final contactList = context.read<ContactListCubit>();
try {
await contactList.updateContactFields(
localConversationRecordKey:
widget.contact.localConversationRecordKey.toDart(),
updatedContactSpec: updatedContactSpec);
} on Exception catch (e) {
log.debug('error updating contact: $e', e);
return false;
}
return true;
}));
child: EditContactForm(
contact: widget.contact,
submitText: translate('button.update'),
submitDisabledText: translate('button.waiting_for_network'),
onModifiedState: widget.onModifiedState,
onSubmit: (updatedContactSpec) async {
final contactList = context.read<ContactListCubit>();
try {
await contactList.updateContactFields(
localConversationRecordKey: widget
.contact
.localConversationRecordKey
.toDart(),
updatedContactSpec: updatedContactSpec,
);
} on Exception catch (e) {
log.debug('error updating contact: $e', e);
return false;
}
return true;
},
),
);
}

View file

@ -7,33 +7,42 @@ import '../../theme/theme.dart';
const _kOnTap = 'onTap';
class ContactItemWidget extends StatelessWidget {
const ContactItemWidget(
{required proto.Contact contact,
required bool disabled,
required bool selected,
Future<void> Function(proto.Contact)? onTap,
Future<void> Function(proto.Contact)? onDoubleTap,
Future<void> Function(proto.Contact)? onDelete,
super.key})
: _disabled = disabled,
_selected = selected,
_contact = contact,
_onTap = onTap,
_onDoubleTap = onDoubleTap,
_onDelete = onDelete;
////////////////////////////////////////////////////////////////////////////
final proto.Contact _contact;
final bool _disabled;
final bool _selected;
final Future<void> Function(proto.Contact contact)? _onTap;
final Future<void> Function(proto.Contact contact)? _onDoubleTap;
final Future<void> Function(proto.Contact contact)? _onDelete;
const ContactItemWidget({
required proto.Contact contact,
required bool disabled,
required bool selected,
Future<void> Function(proto.Contact)? onTap,
Future<void> Function(proto.Contact)? onDoubleTap,
Future<void> Function(proto.Contact)? onDelete,
super.key,
}) : _disabled = disabled,
_selected = selected,
_contact = contact,
_onTap = onTap,
_onDoubleTap = onDoubleTap,
_onDelete = onDelete;
@override
Widget build(
BuildContext context,
) {
Widget build(BuildContext context) {
final name = _contact.nameOrNickname;
final title = _contact.displayName;
final subtitle = _contact.profile.status;
final avatar = StyledAvatar(
name: name,
size: 34.scaled(context),
);
final avatar = StyledAvatar(name: name, size: 34.scaled(context));
return StyledSlideTile(
key: ObjectKey(_contact),
@ -46,23 +55,23 @@ class ContactItemWidget extends StatelessWidget {
onDoubleTap: _onDoubleTap == null
? null
: () => singleFuture<void>((this, _kOnTap), () async {
await _onDoubleTap(_contact);
}),
await _onDoubleTap(_contact);
}),
onTap: _onTap == null
? null
: () => singleFuture<void>((this, _kOnTap), () async {
await _onTap(_contact);
}),
await _onTap(_contact);
}),
startActions: [
if (_onDoubleTap != null)
SlideTileAction(
//icon: Icons.edit,
label: translate('button.chat'),
actionScale: ScaleKind.secondary,
onPressed: (_context) =>
onPressed: (context) =>
singleFuture<void>((this, _kOnTap), () async {
await _onDoubleTap(_contact);
}),
await _onDoubleTap(_contact);
}),
),
],
endActions: [
@ -71,31 +80,22 @@ class ContactItemWidget extends StatelessWidget {
//icon: Icons.edit,
label: translate('button.edit'),
actionScale: ScaleKind.secondary,
onPressed: (_context) =>
onPressed: (context) =>
singleFuture<void>((this, _kOnTap), () async {
await _onTap(_contact);
}),
await _onTap(_contact);
}),
),
if (_onDelete != null)
SlideTileAction(
//icon: Icons.delete,
label: translate('button.delete'),
actionScale: ScaleKind.tertiary,
onPressed: (_context) =>
onPressed: (context) =>
singleFuture<void>((this, _kOnTap), () async {
await _onDelete(_contact);
}),
await _onDelete(_contact);
}),
),
],
);
}
////////////////////////////////////////////////////////////////////////////
final proto.Contact _contact;
final bool _disabled;
final bool _selected;
final Future<void> Function(proto.Contact contact)? _onTap;
final Future<void> Function(proto.Contact contact)? _onDoubleTap;
final Future<void> Function(proto.Contact contact)? _onDelete;
}

View file

@ -14,62 +14,80 @@ import '../cubits/cubits.dart';
import 'contact_item_widget.dart';
import 'empty_contact_list_widget.dart';
enum ContactsBrowserElementKind {
contact,
invitation,
}
enum ContactsBrowserElementKind { contact, invitation }
class ContactsBrowserElement {
final ContactsBrowserElementKind kind;
final proto.ContactInvitationRecord? invitation;
final proto.Contact? contact;
ContactsBrowserElement.contact(proto.Contact c)
: kind = ContactsBrowserElementKind.contact,
invitation = null,
contact = c;
: kind = ContactsBrowserElementKind.contact,
invitation = null,
contact = c;
ContactsBrowserElement.invitation(proto.ContactInvitationRecord i)
: kind = ContactsBrowserElementKind.invitation,
contact = null,
invitation = i;
: kind = ContactsBrowserElementKind.invitation,
contact = null,
invitation = i;
String get sortKey => switch (kind) {
ContactsBrowserElementKind.contact => contact!.displayName,
ContactsBrowserElementKind.invitation =>
invitation!.recipient + invitation!.message
};
final ContactsBrowserElementKind kind;
final proto.ContactInvitationRecord? invitation;
final proto.Contact? contact;
ContactsBrowserElementKind.contact => contact!.displayName,
ContactsBrowserElementKind.invitation =>
invitation!.recipient + invitation!.message,
};
}
class ContactsBrowser extends StatefulWidget {
const ContactsBrowser(
{required this.onContactSelected,
required this.onContactDeleted,
required this.onStartChat,
this.selectedContactRecordKey,
super.key});
final Future<void> Function(proto.Contact? contact) onContactSelected;
final Future<void> Function(proto.Contact contact) onContactDeleted;
final Future<void> Function(proto.Contact contact) onStartChat;
final RecordKey? selectedContactRecordKey;
const ContactsBrowser({
required this.onContactSelected,
required this.onContactDeleted,
required this.onStartChat,
this.selectedContactRecordKey,
super.key,
});
@override
State<ContactsBrowser> createState() => _ContactsBrowserState();
final Future<void> Function(proto.Contact? contact) onContactSelected;
final Future<void> Function(proto.Contact contact) onContactDeleted;
final Future<void> Function(proto.Contact contact) onStartChat;
final RecordKey? selectedContactRecordKey;
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties
..add(DiagnosticsProperty<RecordKey?>(
'selectedContactRecordKey', selectedContactRecordKey))
..add(
ObjectFlagProperty<Future<void> Function(proto.Contact? contact)>.has(
'onContactSelected', onContactSelected))
DiagnosticsProperty<RecordKey?>(
'selectedContactRecordKey',
selectedContactRecordKey,
),
)
..add(
ObjectFlagProperty<Future<void> Function(proto.Contact contact)>.has(
'onStartChat', onStartChat))
ObjectFlagProperty<Future<void> Function(proto.Contact? contact)>.has(
'onContactSelected',
onContactSelected,
),
)
..add(
ObjectFlagProperty<Future<void> Function(proto.Contact contact)>.has(
'onContactDeleted', onContactDeleted));
ObjectFlagProperty<Future<void> Function(proto.Contact contact)>.has(
'onStartChat',
onStartChat,
),
)
..add(
ObjectFlagProperty<Future<void> Function(proto.Contact contact)>.has(
'onContactDeleted',
onContactDeleted,
),
);
}
}
@ -89,51 +107,60 @@ class _ContactsBrowserState extends State<ContactsBrowser>
// final menuBorderColor = scaleScheme.primaryScale.hoverBorder;
PopupMenuEntry<void> makeMenuButton(
{required IconData iconData,
required String text,
void Function()? onTap}) =>
PopupMenuItem(
onTap: onTap,
child: Row(
spacing: 8.scaled(context),
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Icon(iconData, size: 32.scaled(context)),
Text(
text,
textScaler: MediaQuery.of(context).textScaler,
maxLines: 2,
textAlign: TextAlign.center,
)
]));
PopupMenuEntry<void> makeMenuButton({
required IconData iconData,
required String text,
void Function()? onTap,
}) => PopupMenuItem(
onTap: onTap,
child: Row(
spacing: 8.scaled(context),
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Icon(iconData, size: 32.scaled(context)),
Text(
text,
textScaler: MediaQuery.of(context).textScaler,
maxLines: 2,
textAlign: TextAlign.center,
),
],
),
);
final inviteMenuItems = [
makeMenuButton(
iconData: Icons.contact_page,
text: translate('add_contact_sheet.create_invite'),
onTap: () async {
await CreateInvitationDialog.show(context);
}),
iconData: Icons.contact_page,
text: translate('add_contact_sheet.create_invite'),
onTap: () async {
await CreateInvitationDialog.show(context);
},
),
makeMenuButton(
iconData: Icons.qr_code_scanner,
text: translate('add_contact_sheet.scan_invite'),
onTap: () async {
await ScanInvitationDialog.show(context);
}),
iconData: Icons.qr_code_scanner,
text: translate('add_contact_sheet.scan_invite'),
onTap: () async {
await ScanInvitationDialog.show(context);
},
),
makeMenuButton(
iconData: Icons.paste,
text: translate('add_contact_sheet.paste_invite'),
onTap: () async {
await PasteInvitationDialog.show(context);
}),
iconData: Icons.paste,
text: translate('add_contact_sheet.paste_invite'),
onTap: () async {
await PasteInvitationDialog.show(context);
},
),
];
return PopupMenuButton(
itemBuilder: (_) => inviteMenuItems,
menuPadding: const EdgeInsets.symmetric(vertical: 8).scaled(context),
tooltip: translate('add_contact_sheet.add_contact'),
child: Icon(
size: 32.scaled(context), Icons.person_add, color: menuIconColor));
itemBuilder: (_) => inviteMenuItems,
menuPadding: const EdgeInsets.symmetric(vertical: 8).scaled(context),
tooltip: translate('add_contact_sheet.add_contact'),
child: Icon(
size: 32.scaled(context),
Icons.person_add,
color: menuIconColor,
),
);
}
@override
@ -144,97 +171,107 @@ class _ContactsBrowserState extends State<ContactsBrowser>
final cilState = context.watch<ContactInvitationListCubit>().state;
final contactInvitationRecordList =
cilState.state.asData?.value.map((x) => x.value).toIList() ??
const IListConst([]);
const IListConst([]);
final ciState = context.watch<ContactListCubit>().state;
final contactList =
ciState.state.asData?.value.map((x) => x.value).toIList();
final contactList = ciState.state.asData?.value
.map((x) => x.value)
.toIList();
final initialList = <ContactsBrowserElement>[];
if (contactList != null) {
initialList
.addAll(contactList.toList().map(ContactsBrowserElement.contact));
initialList.addAll(
contactList.toList().map(ContactsBrowserElement.contact),
);
}
if (contactInvitationRecordList.isNotEmpty) {
initialList.addAll(contactInvitationRecordList
.toList()
.map(ContactsBrowserElement.invitation));
initialList.addAll(
contactInvitationRecordList.toList().map(
ContactsBrowserElement.invitation,
),
);
}
initialList.sort((a, b) => a.sortKey.compareTo(b.sortKey));
return Column(children: [
SearchableList<ContactsBrowserElement>(
initialList: initialList,
itemBuilder: (element) {
switch (element.kind) {
case ContactsBrowserElementKind.contact:
final contact = element.contact!;
return ContactItemWidget(
contact: contact,
selected: widget.selectedContactRecordKey ==
contact.localConversationRecordKey.toDart(),
disabled: false,
onDoubleTap: _onStartChat,
onTap: onContactSelected,
onDelete: _onContactDeleted)
.paddingLTRB(0, 4.scaled(context), 0, 0);
case ContactsBrowserElementKind.invitation:
final invitation = element.invitation!;
return ContactInvitationItemWidget(
contactInvitationRecord: invitation,
disabled: false)
.paddingLTRB(0, 4.scaled(context), 0, 0);
}
},
filter: (value) {
final lowerValue = value.toLowerCase();
return Column(
children: [
SearchableList<ContactsBrowserElement>(
initialList: initialList,
itemBuilder: (element) {
switch (element.kind) {
case ContactsBrowserElementKind.contact:
final contact = element.contact!;
return ContactItemWidget(
contact: contact,
selected:
widget.selectedContactRecordKey ==
contact.localConversationRecordKey.toDart(),
disabled: false,
onDoubleTap: _onStartChat,
onTap: onContactSelected,
onDelete: _onContactDeleted,
).paddingLTRB(0, 4.scaled(context), 0, 0);
case ContactsBrowserElementKind.invitation:
final invitation = element.invitation!;
return ContactInvitationItemWidget(
contactInvitationRecord: invitation,
disabled: false,
).paddingLTRB(0, 4.scaled(context), 0, 0);
}
},
filter: (value) {
final lowerValue = value.toLowerCase();
final filtered = <ContactsBrowserElement>[];
for (final element in initialList) {
switch (element.kind) {
case ContactsBrowserElementKind.contact:
final contact = element.contact!;
if (contact.nickname.toLowerCase().contains(lowerValue) ||
contact.profile.name
.toLowerCase()
.contains(lowerValue) ||
contact.profile.pronouns
.toLowerCase()
.contains(lowerValue)) {
filtered.add(element);
}
case ContactsBrowserElementKind.invitation:
final invitation = element.invitation!;
if (invitation.message
.toLowerCase()
.contains(lowerValue) ||
invitation.recipient
.toLowerCase()
.contains(lowerValue)) {
filtered.add(element);
}
final filtered = <ContactsBrowserElement>[];
for (final element in initialList) {
switch (element.kind) {
case ContactsBrowserElementKind.contact:
final contact = element.contact!;
if (contact.nickname.toLowerCase().contains(lowerValue) ||
contact.profile.name.toLowerCase().contains(lowerValue) ||
contact.profile.pronouns.toLowerCase().contains(
lowerValue,
)) {
filtered.add(element);
}
}
return filtered;
},
searchFieldHeight: 40.scaled(context),
listViewPadding:
const EdgeInsets.fromLTRB(4, 0, 4, 4).scaled(context),
searchFieldPadding:
const EdgeInsets.fromLTRB(4, 8, 4, 4).scaled(context),
emptyWidget: contactList == null
? waitingPage(
text: translate('contact_list.loading_contacts'))
: const EmptyContactListWidget(),
defaultSuffixIconColor: scale.primaryScale.border,
searchFieldEnabled: contactList != null,
inputDecoration:
InputDecoration(labelText: translate('contact_list.search')),
secondaryWidget: buildInvitationButton(context)
.paddingLTRB(8.scaled(context), 0, 0, 0))
.expanded()
]);
case ContactsBrowserElementKind.invitation:
final invitation = element.invitation!;
if (invitation.message.toLowerCase().contains(lowerValue) ||
invitation.recipient.toLowerCase().contains(lowerValue)) {
filtered.add(element);
}
}
}
return filtered;
},
searchFieldHeight: 40.scaled(context),
listViewPadding: const EdgeInsets.fromLTRB(
4,
0,
4,
4,
).scaled(context),
searchFieldPadding: const EdgeInsets.fromLTRB(
4,
8,
4,
4,
).scaled(context),
emptyWidget: contactList == null
? waitingPage(text: translate('contact_list.loading_contacts'))
: const EmptyContactListWidget(),
defaultSuffixIconColor: scale.primaryScale.border,
searchFieldEnabled: contactList != null,
inputDecoration: InputDecoration(
labelText: translate('contact_list.search'),
),
secondaryWidget: buildInvitationButton(
context,
).paddingLTRB(8.scaled(context), 0, 0, 0),
).expanded(),
],
);
}
Future<void> onContactSelected(proto.Contact contact) async {

View file

@ -15,6 +15,22 @@ import 'availability_widget.dart';
const _kDoSubmitEditContact = 'doSubmitEditContact';
class EditContactForm extends StatefulWidget {
final proto.Contact contact;
final String submitText;
final String submitDisabledText;
final Future<bool> Function(ContactSpec) onSubmit;
final void Function(bool)? onModifiedState;
static const formFieldNickname = 'nickname';
static const formFieldNotes = 'notes';
static const formFieldShowAvailability = 'show_availability';
const EditContactForm({
required this.contact,
required this.onSubmit,
@ -27,28 +43,26 @@ class EditContactForm extends StatefulWidget {
@override
State createState() => _EditContactFormState();
final proto.Contact contact;
final String submitText;
final String submitDisabledText;
final Future<bool> Function(ContactSpec) onSubmit;
final void Function(bool)? onModifiedState;
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties
..add(ObjectFlagProperty<Future<bool> Function(ContactSpec p1)>.has(
'onSubmit', onSubmit))
..add(ObjectFlagProperty<void Function(bool p1)?>.has(
'onModifiedState', onModifiedState))
..add(
ObjectFlagProperty<Future<bool> Function(ContactSpec p1)>.has(
'onSubmit',
onSubmit,
),
)
..add(
ObjectFlagProperty<void Function(bool p1)?>.has(
'onModifiedState',
onModifiedState,
),
)
..add(DiagnosticsProperty<proto.Contact>('contact', contact))
..add(StringProperty('submitText', submitText))
..add(StringProperty('submitDisabledText', submitDisabledText));
}
static const formFieldNickname = 'nickname';
static const formFieldNotes = 'notes';
static const formFieldShowAvailability = 'show_availability';
}
class _EditContactFormState extends State<EditContactForm> {
@ -63,15 +77,24 @@ class _EditContactFormState extends State<EditContactForm> {
}
ContactSpec _makeContactSpec() {
final nickname = _formKey.currentState!
.fields[EditContactForm.formFieldNickname]!.value as String;
final notes = _formKey
.currentState!.fields[EditContactForm.formFieldNotes]!.value as String;
final showAvailability = _formKey.currentState!
.fields[EditContactForm.formFieldShowAvailability]!.value as bool;
final nickname =
_formKey.currentState!.fields[EditContactForm.formFieldNickname]!.value
as String;
final notes =
_formKey.currentState!.fields[EditContactForm.formFieldNotes]!.value
as String;
final showAvailability =
_formKey
.currentState!
.fields[EditContactForm.formFieldShowAvailability]!
.value
as bool;
return ContactSpec(
nickname: nickname, notes: notes, showAvailability: showAvailability);
nickname: nickname,
notes: notes,
showAvailability: showAvailability,
);
}
// Check if everything is the same and update state
@ -104,66 +127,83 @@ class _EditContactFormState extends State<EditContactForm> {
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
styledCard(
context: context,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
context: context,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Row(
children: [
const Spacer(),
StyledAvatar(
name: _currentValueNickname.isNotEmpty
? _currentValueNickname
: widget.contact.profile.name,
size: 128,
).paddingLTRB(0, 0, 0, 16),
const Spacer(),
],
),
SelectableText(
widget.contact.profile.name,
style: textTheme.bodyLarge,
)
.noEditDecoratorLabel(
context,
translate('contact_form.form_name'),
)
.paddingSymmetric(vertical: 4),
SelectableText(
widget.contact.profile.pronouns,
style: textTheme.bodyLarge,
)
.noEditDecoratorLabel(
context,
translate('contact_form.form_pronouns'),
)
.paddingSymmetric(vertical: 4),
Row(
mainAxisSize: MainAxisSize.min,
children: [
Row(children: [
const Spacer(),
StyledAvatar(
name: _currentValueNickname.isNotEmpty
? _currentValueNickname
: widget.contact.profile.name,
size: 128)
.paddingLTRB(0, 0, 0, 16),
const Spacer()
]),
SelectableText(widget.contact.profile.name,
style: textTheme.bodyLarge)
.noEditDecoratorLabel(
context,
translate('contact_form.form_name'),
)
.paddingSymmetric(vertical: 4),
SelectableText(widget.contact.profile.pronouns,
style: textTheme.bodyLarge)
.noEditDecoratorLabel(
context,
translate('contact_form.form_pronouns'),
)
.paddingSymmetric(vertical: 4),
Row(mainAxisSize: MainAxisSize.min, children: [
_availabilityWidget(
widget.contact.profile.availability,
scale.primaryScale.appText),
SelectableText(widget.contact.profile.status,
style: textTheme.bodyMedium)
.paddingSymmetric(horizontal: 8)
])
.noEditDecoratorLabel(
context,
translate('contact_form.form_status'),
)
.paddingSymmetric(vertical: 4),
SelectableText(widget.contact.profile.about,
minLines: 1,
maxLines: 8,
style: textTheme.bodyMedium)
.noEditDecoratorLabel(
context,
translate('contact_form.form_about'),
)
.paddingSymmetric(vertical: 4),
SelectableText(widget.contact.fingerprint,
style: textTheme.bodyMedium!
.copyWith(fontFamily: 'Source Code Pro'))
.noEditDecoratorLabel(
context,
translate('contact_form.form_fingerprint'),
)
.paddingSymmetric(vertical: 4),
]).paddingAll(16))
.paddingLTRB(0, 0, 0, 16),
_availabilityWidget(
widget.contact.profile.availability,
scale.primaryScale.appText,
),
SelectableText(
widget.contact.profile.status,
style: textTheme.bodyMedium,
).paddingSymmetric(horizontal: 8),
],
)
.noEditDecoratorLabel(
context,
translate('contact_form.form_status'),
)
.paddingSymmetric(vertical: 4),
SelectableText(
widget.contact.profile.about,
minLines: 1,
maxLines: 8,
style: textTheme.bodyMedium,
)
.noEditDecoratorLabel(
context,
translate('contact_form.form_about'),
)
.paddingSymmetric(vertical: 4),
SelectableText(
widget.contact.fingerprint,
style: textTheme.bodyMedium!.copyWith(
fontFamily: 'Source Code Pro',
),
)
.noEditDecoratorLabel(
context,
translate('contact_form.form_fingerprint'),
)
.paddingSymmetric(vertical: 4),
],
).paddingAll(16),
).paddingLTRB(0, 0, 0, 16),
FormBuilderTextField(
name: EditContactForm.formFieldNickname,
initialValue: _currentValueNickname,
@ -173,15 +213,18 @@ class _EditContactFormState extends State<EditContactForm> {
});
},
decoration: InputDecoration(
labelText: translate('contact_form.form_nickname')),
labelText: translate('contact_form.form_nickname'),
),
maxLength: 64,
textInputAction: TextInputAction.next,
),
FormBuilderCheckbox(
name: EditContactForm.formFieldShowAvailability,
initialValue: _savedValue.showAvailability,
title: Text(translate('contact_form.form_show_availability'),
style: textTheme.labelMedium),
title: Text(
translate('contact_form.form_show_availability'),
style: textTheme.labelMedium,
),
),
FormBuilderTextField(
name: EditContactForm.formFieldNotes,
@ -190,16 +233,22 @@ class _EditContactFormState extends State<EditContactForm> {
maxLines: 8,
maxLength: 1024,
decoration: InputDecoration(
labelText: translate('contact_form.form_notes')),
labelText: translate('contact_form.form_notes'),
),
textInputAction: TextInputAction.newline,
),
ElevatedButton(
onPressed: _isModified ? _doSubmit : null,
child: Row(mainAxisSize: MainAxisSize.min, children: [
Icon(Icons.check, size: 24.scaled(context))
.paddingLTRB(0, 0, 4, 0),
Text(widget.submitText).paddingLTRB(0, 0, 4.scaled(context), 0)
]).paddingAll(4.scaled(context)),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.check,
size: 24.scaled(context),
).paddingLTRB(0, 0, 4, 0),
Text(widget.submitText).paddingLTRB(0, 0, 4.scaled(context), 0),
],
).paddingAll(4.scaled(context)),
).paddingSymmetric(vertical: 4.scaled(context)).alignAtCenter(),
],
),

View file

@ -1 +1,2 @@
export 'cubits/cubits.dart';
export 'models/models.dart';

View file

@ -12,6 +12,16 @@ import '../conversation.dart';
@immutable
class ActiveConversationState extends Equatable {
final Author remoteAuthor;
final RecordKey localConversationRecordKey;
final RecordKey remoteConversationRecordKey;
final proto.Conversation localConversation;
final proto.Conversation? remoteConversation;
const ActiveConversationState({
required this.remoteAuthor,
required this.localConversationRecordKey,
@ -20,29 +30,25 @@ class ActiveConversationState extends Equatable {
required this.remoteConversation,
});
final Author remoteAuthor;
final RecordKey localConversationRecordKey;
final RecordKey remoteConversationRecordKey;
final proto.Conversation localConversation;
final proto.Conversation? remoteConversation;
@override
List<Object?> get props => [
remoteAuthor,
localConversationRecordKey,
remoteConversationRecordKey,
localConversation,
remoteConversation
];
remoteAuthor,
localConversationRecordKey,
remoteConversationRecordKey,
localConversation,
remoteConversation,
];
}
typedef ActiveConversationCubit = TransformerCubit<
AsyncValue<ActiveConversationState>,
AsyncValue<ConversationState>,
ConversationCubit>;
typedef ActiveConversationCubit =
TransformerCubit<
AsyncValue<ActiveConversationState>,
AsyncValue<ConversationState>,
ConversationCubit
>;
typedef ActiveConversationsBlocMapState
= BlocMapState<RecordKey, AsyncValue<ActiveConversationState>>;
typedef ActiveConversationsBlocMapState =
BlocMapState<RecordKey, AsyncValue<ActiveConversationState>>;
// Map of localConversationRecordKey to ActiveConversationCubit
// Wraps a conversation cubit to only expose completely built conversations
@ -52,17 +58,30 @@ typedef ActiveConversationsBlocMapState
//
// TODO(crioux): Polling contacts for new inactive chats is yet to be done
//
class ActiveConversationsBlocMapCubit extends BlocMapCubit<RecordKey,
AsyncValue<ActiveConversationState>, ActiveConversationCubit>
class ActiveConversationsBlocMapCubit
extends
BlocMapCubit<
RecordKey,
AsyncValue<ActiveConversationState>,
ActiveConversationCubit
>
with StateMapFollower<ChatListCubitState, RecordKey, proto.Chat> {
////
final AccountInfo _accountInfo;
final AccountRecordCubit _accountRecordCubit;
final ContactListCubit _contactListCubit;
ActiveConversationsBlocMapCubit({
required AccountInfo accountInfo,
required AccountRecordCubit accountRecordCubit,
required ChatListCubit chatListCubit,
required ContactListCubit contactListCubit,
}) : _accountInfo = accountInfo,
_accountRecordCubit = accountRecordCubit,
_contactListCubit = contactListCubit {
}) : _accountInfo = accountInfo,
_accountRecordCubit = accountRecordCubit,
_contactListCubit = contactListCubit {
// Follow the chat list cubit
follow(chatListCubit);
}
@ -74,57 +93,70 @@ class ActiveConversationsBlocMapCubit extends BlocMapCubit<RecordKey,
// Private Implementation
// Add an active conversation to be tracked for changes
void _addDirectConversation(
{required Author remoteAuthor,
required RecordKey localConversationRecordKey,
required RecordKey remoteConversationRecordKey}) =>
add(localConversationRecordKey, () {
// Conversation cubit the tracks the state between the local
// and remote halves of a contact's relationship with this account
final conversationCubit = ConversationCubit(
accountInfo: _accountInfo,
remoteAuthor: remoteAuthor,
localConversationRecordKey: localConversationRecordKey,
remoteConversationRecordKey: remoteConversationRecordKey,
void _addDirectConversation({
required Author remoteAuthor,
required RecordKey localConversationRecordKey,
required RecordKey remoteConversationRecordKey,
}) => add(localConversationRecordKey, () {
// Conversation cubit the tracks the state between the local
// and remote halves of a contact's relationship with this account
final conversationCubit = ConversationCubit(
accountInfo: _accountInfo,
remoteAuthor: remoteAuthor,
localConversationRecordKey: localConversationRecordKey,
remoteConversationRecordKey: remoteConversationRecordKey,
);
// When remote conversation changes its profile,
// update our local contact
_contactListCubit.followContactProfileChanges(
localConversationRecordKey,
conversationCubit.stream.map(
(x) => x.map(
data: (d) => d.value.remoteConversation?.profile,
loading: (_) => null,
error: (_) => null,
),
),
conversationCubit.state.asData?.value.remoteConversation?.profile,
);
// When our local account profile changes, send it to the conversation
conversationCubit.watchAccountChanges(
_accountRecordCubit.stream,
_accountRecordCubit.state,
);
// Transformer that only passes through conversations where the local
// portion is not loading
// along with the contact that corresponds to the completed
// conversation
final transformedCubit =
TransformerCubit<
AsyncValue<ActiveConversationState>,
AsyncValue<ConversationState>,
ConversationCubit
>(
conversationCubit,
transform: (avstate) => avstate.when(
data: (data) => (data.localConversation == null)
? const AsyncValue.loading()
: AsyncValue.data(
ActiveConversationState(
localConversation: data.localConversation!,
remoteConversation: data.remoteConversation,
remoteAuthor: remoteAuthor,
localConversationRecordKey: localConversationRecordKey,
remoteConversationRecordKey: remoteConversationRecordKey,
),
),
loading: AsyncValue.loading,
error: AsyncValue.error,
),
);
// When remote conversation changes its profile,
// update our local contact
_contactListCubit.followContactProfileChanges(
localConversationRecordKey,
conversationCubit.stream.map((x) => x.map(
data: (d) => d.value.remoteConversation?.profile,
loading: (_) => null,
error: (_) => null)),
conversationCubit.state.asData?.value.remoteConversation?.profile);
// When our local account profile changes, send it to the conversation
conversationCubit.watchAccountChanges(
_accountRecordCubit.stream, _accountRecordCubit.state);
// Transformer that only passes through conversations where the local
// portion is not loading
// along with the contact that corresponds to the completed
// conversation
final transformedCubit = TransformerCubit<
AsyncValue<ActiveConversationState>,
AsyncValue<ConversationState>,
ConversationCubit>(conversationCubit,
transform: (avstate) => avstate.when(
data: (data) => (data.localConversation == null)
? const AsyncValue.loading()
: AsyncValue.data(ActiveConversationState(
localConversation: data.localConversation!,
remoteConversation: data.remoteConversation,
remoteAuthor: remoteAuthor,
localConversationRecordKey: localConversationRecordKey,
remoteConversationRecordKey:
remoteConversationRecordKey)),
loading: AsyncValue.loading,
error: AsyncValue.error));
return transformedCubit;
});
return transformedCubit;
});
/// StateFollower /////////////////////////
@ -137,18 +169,28 @@ class ActiveConversationsBlocMapCubit extends BlocMapCubit<RecordKey,
case proto.Chat_Kind.notSet:
throw StateError('unknown chat kind');
case proto.Chat_Kind.direct:
final localConversationRecordKey =
newValue.direct.localConversationRecordKey.toDart();
final localConversationRecordKey = newValue
.direct
.localConversationRecordKey
.toDart();
final remoteAuthor = newValue.direct.remoteMember.author.toDart();
final remoteConversationRecordKey =
newValue.direct.remoteMember.remoteConversationRecordKey.toDart();
final remoteConversationRecordKey = newValue
.direct
.remoteMember
.remoteConversationRecordKey
.toDart();
if (oldValue != null) {
final oldLocalConversationRecordKey =
oldValue.direct.localConversationRecordKey.toDart();
final oldLocalConversationRecordKey = oldValue
.direct
.localConversationRecordKey
.toDart();
final oldRemoteAuthor = oldValue.direct.remoteMember.author.toDart();
final oldRemoteConversationRecordKey =
oldValue.direct.remoteMember.remoteConversationRecordKey.toDart();
final oldRemoteConversationRecordKey = oldValue
.direct
.remoteMember
.remoteConversationRecordKey
.toDart();
if (oldLocalConversationRecordKey == localConversationRecordKey &&
oldRemoteAuthor.id == remoteAuthor.id &&
@ -158,18 +200,13 @@ class ActiveConversationsBlocMapCubit extends BlocMapCubit<RecordKey,
}
_addDirectConversation(
remoteAuthor: remoteAuthor,
localConversationRecordKey: localConversationRecordKey,
remoteConversationRecordKey: remoteConversationRecordKey);
remoteAuthor: remoteAuthor,
localConversationRecordKey: localConversationRecordKey,
remoteConversationRecordKey: remoteConversationRecordKey,
);
case proto.Chat_Kind.group:
throw StateError('unsupported chat kind');
}
}
////
final AccountInfo _accountInfo;
final AccountRecordCubit _accountRecordCubit;
final ContactListCubit _contactListCubit;
}

View file

@ -11,37 +11,53 @@ import '../conversation.dart';
@immutable
class _SingleContactChatState extends Equatable {
const _SingleContactChatState(
{required this.remoteAuthor,
required this.localConversationRecordKey,
required this.remoteConversationRecordKey,
required this.localMessagesRecordKey,
required this.remoteMessagesRecordKey});
final Author remoteAuthor;
final RecordKey localConversationRecordKey;
final RecordKey remoteConversationRecordKey;
final RecordKey localMessagesRecordKey;
final RecordKey? remoteMessagesRecordKey;
const _SingleContactChatState({
required this.remoteAuthor,
required this.localConversationRecordKey,
required this.remoteConversationRecordKey,
required this.localMessagesRecordKey,
required this.remoteMessagesRecordKey,
});
@override
List<Object?> get props => [
remoteAuthor,
localConversationRecordKey,
remoteConversationRecordKey,
localMessagesRecordKey,
remoteMessagesRecordKey
];
remoteAuthor,
localConversationRecordKey,
remoteConversationRecordKey,
localMessagesRecordKey,
remoteMessagesRecordKey,
];
}
// Map of localConversationRecordKey to SingleContactMessagesCubit
// Wraps a SingleContactMessagesCubit to stream the latest messages to the state
// Automatically follows the state of a ActiveConversationsBlocMapCubit.
class ActiveSingleContactChatBlocMapCubit extends BlocMapCubit<RecordKey,
SingleContactMessagesState, SingleContactMessagesCubit>
class ActiveSingleContactChatBlocMapCubit
extends
BlocMapCubit<
RecordKey,
SingleContactMessagesState,
SingleContactMessagesCubit
>
with
StateMapFollower<ActiveConversationsBlocMapState, RecordKey,
AsyncValue<ActiveConversationState>> {
StateMapFollower<
ActiveConversationsBlocMapState,
RecordKey,
AsyncValue<ActiveConversationState>
> {
////
final AccountInfo _accountInfo;
ActiveSingleContactChatBlocMapCubit({
required AccountInfo accountInfo,
required ActiveConversationsBlocMapCubit activeConversationsBlocMapCubit,
@ -51,32 +67,35 @@ class ActiveSingleContactChatBlocMapCubit extends BlocMapCubit<RecordKey,
}
void _addConversationMessages(_SingleContactChatState state) {
update(state.localConversationRecordKey,
onUpdate: (cubit) =>
cubit.updateRemoteMessagesRecordKey(state.remoteMessagesRecordKey),
onCreate: () => SingleContactMessagesCubit(
accountInfo: _accountInfo,
remoteAuthor: state.remoteAuthor,
localConversationRecordKey: state.localConversationRecordKey,
remoteConversationRecordKey: state.remoteConversationRecordKey,
localMessagesRecordKey: state.localMessagesRecordKey,
remoteMessagesRecordKey: state.remoteMessagesRecordKey,
));
update(
state.localConversationRecordKey,
onUpdate: (cubit) =>
cubit.updateRemoteMessagesRecordKey(state.remoteMessagesRecordKey),
onCreate: () => SingleContactMessagesCubit(
accountInfo: _accountInfo,
remoteAuthor: state.remoteAuthor,
localConversationRecordKey: state.localConversationRecordKey,
remoteConversationRecordKey: state.remoteConversationRecordKey,
localMessagesRecordKey: state.localMessagesRecordKey,
remoteMessagesRecordKey: state.remoteMessagesRecordKey,
),
);
}
_SingleContactChatState? _mapStateValue(
AsyncValue<ActiveConversationState> avInputState) {
AsyncValue<ActiveConversationState> avInputState,
) {
final inputState = avInputState.asData?.value;
if (inputState == null) {
return null;
}
return _SingleContactChatState(
remoteAuthor: inputState.remoteAuthor,
localConversationRecordKey: inputState.localConversationRecordKey,
remoteConversationRecordKey: inputState.remoteConversationRecordKey,
localMessagesRecordKey: inputState.localConversation.messages.toDart(),
remoteMessagesRecordKey:
inputState.remoteConversation?.messages.toDart());
remoteAuthor: inputState.remoteAuthor,
localConversationRecordKey: inputState.localConversationRecordKey,
remoteConversationRecordKey: inputState.remoteConversationRecordKey,
localMessagesRecordKey: inputState.localConversation.messages.toDart(),
remoteMessagesRecordKey: inputState.remoteConversation?.messages.toDart(),
);
}
/// StateFollower /////////////////////////
@ -85,8 +104,11 @@ class ActiveSingleContactChatBlocMapCubit extends BlocMapCubit<RecordKey,
void removeFromState(RecordKey key) => remove(key);
@override
void updateState(RecordKey key, AsyncValue<ActiveConversationState>? oldValue,
AsyncValue<ActiveConversationState> newValue) {
void updateState(
RecordKey key,
AsyncValue<ActiveConversationState>? oldValue,
AsyncValue<ActiveConversationState> newValue,
) {
final newState = _mapStateValue(newValue);
if (oldValue != null) {
final oldState = _mapStateValue(oldValue);
@ -99,13 +121,12 @@ class ActiveSingleContactChatBlocMapCubit extends BlocMapCubit<RecordKey,
} else if (newValue.isLoading) {
addState(key, const AsyncValue.loading());
} else {
final (error, stackTrace) =
(newValue.asError!.error, newValue.asError!.stackTrace);
final (error, stackTrace) = (
newValue.asError!.error,
newValue.asError!.stackTrace,
);
addError(error, stackTrace);
addState(key, AsyncValue.error(error, stackTrace));
}
}
////
final AccountInfo _accountInfo;
}

View file

@ -20,17 +20,21 @@ const _sfUpdateAccountChange = 'updateAccountChange';
@immutable
class ConversationState extends Equatable {
const ConversationState(
{required this.localConversation, required this.remoteConversation});
final proto.Conversation? localConversation;
final proto.Conversation? remoteConversation;
const ConversationState({
required this.localConversation,
required this.remoteConversation,
});
@override
List<Object?> get props => [localConversation, remoteConversation];
@override
String toString() => 'ConversationState('
String toString() =>
'ConversationState('
'localConversation: ${DynamicDebug.toDebug(localConversation)}, '
'remoteConversation: ${DynamicDebug.toDebug(remoteConversation)}'
')';
@ -40,14 +44,39 @@ class ConversationState extends Equatable {
/// Used to pass profile, identity and status changes, and the messages key for
/// 1-1 chats
class ConversationCubit extends Cubit<AsyncValue<ConversationState>> {
ConversationCubit(
{required AccountInfo accountInfo,
required Author remoteAuthor,
RecordKey? localConversationRecordKey,
RecordKey? remoteConversationRecordKey})
: _accountInfo = accountInfo,
_remoteAuthor = remoteAuthor,
super(const AsyncValue.loading()) {
final AccountInfo _accountInfo;
late final KeyPair _identityWriter;
final Author _remoteAuthor;
DefaultDHTRecordCubit<proto.Conversation>? _localConversationCubit;
DefaultDHTRecordCubit<proto.Conversation>? _remoteConversationCubit;
StreamSubscription<AsyncValue<proto.Conversation>>? _localSubscription;
StreamSubscription<AsyncValue<proto.Conversation>>? _remoteSubscription;
StreamSubscription<AsyncValue<proto.Account>>? _accountSubscription;
var _incrementalState = const ConversationState(
localConversation: null,
remoteConversation: null,
);
VeilidCrypto? _conversationCrypto;
final WaitSet<void, void> _initWait = WaitSet();
ConversationCubit({
required AccountInfo accountInfo,
required Author remoteAuthor,
RecordKey? localConversationRecordKey,
RecordKey? remoteConversationRecordKey,
}) : _accountInfo = accountInfo,
_remoteAuthor = remoteAuthor,
super(const AsyncValue.loading()) {
_identityWriter = _accountInfo.identityWriter;
if (localConversationRecordKey != null) {
@ -59,10 +88,12 @@ class ConversationCubit extends Cubit<AsyncValue<ConversationState>> {
final writer = _identityWriter;
final record = await pool.openRecordWrite(
localConversationRecordKey, writer,
debugName: 'ConversationCubit::LocalConversation',
parent: accountInfo.accountRecordKey,
crypto: crypto);
localConversationRecordKey,
writer,
debugName: 'ConversationCubit::LocalConversation',
parent: accountInfo.accountRecordKey,
crypto: crypto,
);
return record;
});
@ -76,12 +107,14 @@ class ConversationCubit extends Cubit<AsyncValue<ConversationState>> {
final pool = DHTRecordPool.instance;
final crypto = await _cachedConversationCrypto();
final record = await pool.openRecordRead(remoteConversationRecordKey,
debugName: 'ConversationCubit::RemoteConversation',
parent:
await pool.getParentRecordKey(remoteConversationRecordKey) ??
accountInfo.accountRecordKey,
crypto: crypto);
final record = await pool.openRecordRead(
remoteConversationRecordKey,
debugName: 'ConversationCubit::RemoteConversation',
parent:
await pool.getParentRecordKey(remoteConversationRecordKey) ??
accountInfo.accountRecordKey,
crypto: crypto,
);
return record;
});
@ -110,11 +143,14 @@ class ConversationCubit extends Cubit<AsyncValue<ConversationState>> {
/// in now that we have the remote identity key
/// The ConversationCubit must not already have a local conversation
/// Returns the local conversation record key that was initialized
Future<RecordKey> initLocalConversation(
{required proto.Profile profile,
RecordKey? existingConversationRecordKey}) async {
assert(_localConversationCubit == null,
'must not have a local conversation yet');
Future<RecordKey> initLocalConversation({
required proto.Profile profile,
RecordKey? existingConversationRecordKey,
}) async {
assert(
_localConversationCubit == null,
'must not have a local conversation yet',
);
final pool = DHTRecordPool.instance;
@ -126,45 +162,55 @@ class ConversationCubit extends Cubit<AsyncValue<ConversationState>> {
late final DHTRecord localConversationRecord;
if (existingConversationRecordKey != null) {
localConversationRecord = await pool.openRecordWrite(
existingConversationRecordKey, writer,
debugName:
'ConversationCubit::initLocalConversation::LocalConversation',
parent: accountRecordKey,
crypto: crypto);
existingConversationRecordKey,
writer,
debugName:
'ConversationCubit::initLocalConversation::LocalConversation',
parent: accountRecordKey,
crypto: crypto,
);
} else {
localConversationRecord = await pool.createRecord(
debugName:
'ConversationCubit::initLocalConversation::LocalConversation',
parent: accountRecordKey,
crypto: crypto,
writer: writer,
schema: DHTSchema.smpl(oCnt: 0, members: [
await DHTSchemaMember.fromPublicKey(Veilid.instance, writer.key, 1)
]));
debugName:
'ConversationCubit::initLocalConversation::LocalConversation',
parent: accountRecordKey,
crypto: crypto,
writer: writer,
schema: DHTSchema.smpl(
oCnt: 0,
members: [
await DHTSchemaMember.fromPublicKey(Veilid.instance, writer.key, 1),
],
),
);
}
await localConversationRecord.deleteScope((localConversation) async {
await _initLocalMessages(
localConversationKey: localConversation.key,
callback: (messages) async {
// Create initial local conversation key contents
final conversation = proto.Conversation()
..profile = profile
..superIdentityJson =
jsonEncode(_accountInfo.localAccount.superIdentity.toJson())
..messages = messages.recordKey.toProto();
localConversationKey: localConversation.key,
callback: (messages) async {
// Create initial local conversation key contents
final conversation = proto.Conversation()
..profile = profile
..superIdentityJson = jsonEncode(
_accountInfo.localAccount.superIdentity.toJson(),
)
..messages = messages.recordKey.toProto();
// Write initial conversation to record
final update = await localConversation.tryWriteProtobuf(
proto.Conversation.fromBuffer, conversation);
if (update != null) {
throw Exception('Failed to write local conversation');
}
// Write initial conversation to record
final update = await localConversation.tryWriteProtobuf(
proto.Conversation.fromBuffer,
conversation,
);
if (update != null) {
throw Exception('Failed to write local conversation');
}
// If success, save the new local conversation
// record key in this object
localConversation.ref();
await _setLocalConversation(() async => localConversation);
});
// If success, save the new local conversation
// record key in this object
localConversation.ref();
await _setLocalConversation(() async => localConversation);
},
);
});
return localConversationRecord.key;
@ -186,8 +232,10 @@ class ConversationCubit extends Cubit<AsyncValue<ConversationState>> {
// }
/// Watch for account record changes and update the conversation
void watchAccountChanges(Stream<AsyncValue<proto.Account>> accountStream,
AsyncValue<proto.Account> currentState) {
void watchAccountChanges(
Stream<AsyncValue<proto.Account>> accountStream,
AsyncValue<proto.Account> currentState,
) {
assert(_accountSubscription == null, 'only watch account once');
_accountSubscription = accountStream.listen(_updateAccountChange);
_updateAccountChange(currentState);
@ -206,13 +254,15 @@ class ConversationCubit extends Cubit<AsyncValue<ConversationState>> {
return;
}
serialFuture((this, _sfUpdateAccountChange), () async {
await cubit.record?.eventualUpdateProtobuf(proto.Conversation.fromBuffer,
(old) async {
if (old == null || old.profile == account.profile) {
return null;
}
return old.deepCopy()..profile = account.profile;
});
await cubit.record?.eventualUpdateProtobuf(
proto.Conversation.fromBuffer,
(old) async {
if (old == null || old.profile == account.profile) {
return null;
}
return old.deepCopy()..profile = account.profile;
},
);
});
}
@ -220,8 +270,9 @@ class ConversationCubit extends Cubit<AsyncValue<ConversationState>> {
final newState = avconv.when(
data: (conv) {
_incrementalState = ConversationState(
localConversation: conv,
remoteConversation: _incrementalState.remoteConversation);
localConversation: conv,
remoteConversation: _incrementalState.remoteConversation,
);
return AsyncValue.data(_incrementalState);
},
loading: AsyncValue<ConversationState>.loading,
@ -234,8 +285,9 @@ class ConversationCubit extends Cubit<AsyncValue<ConversationState>> {
final newState = avconv.when(
data: (conv) {
_incrementalState = ConversationState(
localConversation: _incrementalState.localConversation,
remoteConversation: conv);
localConversation: _incrementalState.localConversation,
remoteConversation: conv,
);
return AsyncValue.data(_incrementalState);
},
loading: AsyncValue<ConversationState>.loading,
@ -246,29 +298,39 @@ class ConversationCubit extends Cubit<AsyncValue<ConversationState>> {
// Open local converation key
Future<void> _setLocalConversation(Future<DHTRecord> Function() open) async {
assert(_localConversationCubit == null,
'should not set local conversation twice');
assert(
_localConversationCubit == null,
'should not set local conversation twice',
);
_localConversationCubit = DefaultDHTRecordCubit(
open: open, decodeState: proto.Conversation.fromBuffer);
open: open,
decodeState: proto.Conversation.fromBuffer,
);
await _localConversationCubit!.ready();
_localSubscription =
_localConversationCubit!.stream.listen(_updateLocalConversationState);
_localSubscription = _localConversationCubit!.stream.listen(
_updateLocalConversationState,
);
_updateLocalConversationState(_localConversationCubit!.state);
}
// Open remote converation key
Future<void> _setRemoteConversation(Future<DHTRecord> Function() open) async {
assert(_remoteConversationCubit == null,
'should not set remote conversation twice');
assert(
_remoteConversationCubit == null,
'should not set remote conversation twice',
);
_remoteConversationCubit = DefaultDHTRecordCubit(
open: open, decodeState: proto.Conversation.fromBuffer);
open: open,
decodeState: proto.Conversation.fromBuffer,
);
await _remoteConversationCubit!.ready();
_remoteSubscription =
_remoteConversationCubit!.stream.listen(_updateRemoteConversationState);
_remoteSubscription = _remoteConversationCubit!.stream.listen(
_updateRemoteConversationState,
);
_updateRemoteConversationState(_remoteConversationCubit!.state);
}
@ -281,11 +343,11 @@ class ConversationCubit extends Cubit<AsyncValue<ConversationState>> {
final writer = _identityWriter;
return (await DHTLog.create(
debugName: 'ConversationCubit::initLocalMessages::LocalMessages',
parent: localConversationKey,
crypto: crypto,
writer: writer))
.deleteScope((messages) async => await callback(messages));
debugName: 'ConversationCubit::initLocalMessages::LocalMessages',
parent: localConversationKey,
crypto: crypto,
writer: writer,
)).deleteScope((messages) async => await callback(messages));
}
Future<VeilidCrypto> _cachedConversationCrypto() async {
@ -293,8 +355,9 @@ class ConversationCubit extends Cubit<AsyncValue<ConversationState>> {
if (conversationCrypto != null) {
return conversationCrypto;
}
conversationCrypto =
await _accountInfo.makeConversationCrypto(_remoteAuthor);
conversationCrypto = await _accountInfo.makeConversationCrypto(
_remoteAuthor,
);
_conversationCrypto = conversationCrypto;
return conversationCrypto;
}
@ -302,17 +365,4 @@ class ConversationCubit extends Cubit<AsyncValue<ConversationState>> {
////////////////////////////////////////////////////////////////////////////
// Fields
Author get remoteAuthor => _remoteAuthor;
final AccountInfo _accountInfo;
late final KeyPair _identityWriter;
final Author _remoteAuthor;
DefaultDHTRecordCubit<proto.Conversation>? _localConversationCubit;
DefaultDHTRecordCubit<proto.Conversation>? _remoteConversationCubit;
StreamSubscription<AsyncValue<proto.Conversation>>? _localSubscription;
StreamSubscription<AsyncValue<proto.Conversation>>? _remoteSubscription;
StreamSubscription<AsyncValue<proto.Account>>? _accountSubscription;
var _incrementalState = const ConversationState(
localConversation: null, remoteConversation: null);
VeilidCrypto? _conversationCrypto;
final WaitSet<void, void> _initWait = WaitSet();
}

View file

@ -6,21 +6,15 @@ import '../../proto/proto.dart' as vcproto;
@immutable
class Author extends Equatable implements ToDebugMap {
const Author({
required this.id,
});
final MemberId id;
@override
List<Object?> get props => [
id,
];
const Author({required this.id});
@override
Map<String, dynamic> toDebugMap() => {
'id': id,
};
List<Object?> get props => [id];
@override
Map<String, dynamic> toDebugMap() => {'id': id};
}
extension AuthorToDart on vcproto.Author {

View file

@ -41,6 +41,10 @@ class DisplayScaleDownIntent extends Intent {
}
class KeyboardShortcuts extends StatelessWidget {
/////////////////////////////////////////////////////////
final Widget child;
const KeyboardShortcuts({required this.child, super.key});
void reloadTheme(BuildContext context) {
@ -49,18 +53,19 @@ class KeyboardShortcuts extends StatelessWidget {
await VeilidChatGlobalInit.loadAssetManifest();
final theme =
PreferencesRepository.instance.value.themePreference.themeData();
final theme = PreferencesRepository.instance.value.themePreference
.themeData();
if (context.mounted) {
ThemeSwitcher.of(context).changeTheme(theme: theme);
// Hack to reload translations
final localizationDelegate = LocalizedApp.of(context).delegate;
await LocalizationDelegate.create(
fallbackLocale: localizationDelegate.fallbackLocale.toString(),
supportedLocales: localizationDelegate.supportedLocales
.map((x) => x.toString())
.toList());
fallbackLocale: localizationDelegate.fallbackLocale.toString(),
supportedLocales: localizationDelegate.supportedLocales
.map((x) => x.toString())
.toList(),
);
}
});
}
@ -70,19 +75,23 @@ class KeyboardShortcuts extends StatelessWidget {
final prefs = PreferencesRepository.instance.value;
final oldBrightness = prefs.themePreference.brightnessPreference;
final newBrightness = BrightnessPreference.values[
(oldBrightness.index + 1) % BrightnessPreference.values.length];
final newBrightness =
BrightnessPreference.values[(oldBrightness.index + 1) %
BrightnessPreference.values.length];
log.info('Changing brightness to $newBrightness');
final newPrefs = prefs.copyWith(
themePreference: prefs.themePreference
.copyWith(brightnessPreference: newBrightness));
themePreference: prefs.themePreference.copyWith(
brightnessPreference: newBrightness,
),
);
await PreferencesRepository.instance.set(newPrefs);
if (context.mounted) {
ThemeSwitcher.of(context)
.changeTheme(theme: newPrefs.themePreference.themeData());
ThemeSwitcher.of(
context,
).changeTheme(theme: newPrefs.themePreference.themeData());
}
});
}
@ -97,13 +106,16 @@ class KeyboardShortcuts extends StatelessWidget {
log.info('Changing color to $newColor');
final newPrefs = prefs.copyWith(
themePreference:
prefs.themePreference.copyWith(colorPreference: newColor));
themePreference: prefs.themePreference.copyWith(
colorPreference: newColor,
),
);
await PreferencesRepository.instance.set(newPrefs);
if (context.mounted) {
ThemeSwitcher.of(context)
.changeTheme(theme: newPrefs.themePreference.themeData());
ThemeSwitcher.of(
context,
).changeTheme(theme: newPrefs.themePreference.themeData());
}
});
}
@ -121,13 +133,16 @@ class KeyboardShortcuts extends StatelessWidget {
log.info('Changing display scale to $newDisplayScaleName');
final newPrefs = prefs.copyWith(
themePreference: prefs.themePreference
.copyWith(displayScale: indexToDisplayScale(newIndex)));
themePreference: prefs.themePreference.copyWith(
displayScale: indexToDisplayScale(newIndex),
),
);
await PreferencesRepository.instance.set(newPrefs);
if (context.mounted) {
ThemeSwitcher.of(context)
.changeTheme(theme: newPrefs.themePreference.themeData());
ThemeSwitcher.of(
context,
).changeTheme(theme: newPrefs.themePreference.themeData());
}
});
}
@ -145,13 +160,16 @@ class KeyboardShortcuts extends StatelessWidget {
log.info('Changing display scale to $newDisplayScaleName');
final newPrefs = prefs.copyWith(
themePreference: prefs.themePreference
.copyWith(displayScale: indexToDisplayScale(newIndex)));
themePreference: prefs.themePreference.copyWith(
displayScale: indexToDisplayScale(newIndex),
),
);
await PreferencesRepository.instance.set(newPrefs);
if (context.mounted) {
ThemeSwitcher.of(context)
.changeTheme(theme: newPrefs.themePreference.themeData());
ThemeSwitcher.of(
context,
).changeTheme(theme: newPrefs.themePreference.themeData());
}
});
}
@ -162,7 +180,9 @@ class KeyboardShortcuts extends StatelessWidget {
log.info('Detaching');
await Veilid.instance.detach();
} else if (ProcessorRepository
.instance.processorConnectionState.isDetached) {
.instance
.processorConnectionState
.isDetached) {
log.info('Attaching');
await Veilid.instance.attach();
}
@ -180,92 +200,100 @@ class KeyboardShortcuts extends StatelessWidget {
@override
Widget build(BuildContext context) => ThemeSwitcher(
builder: (context) => Shortcuts(
shortcuts: <ShortcutActivator, Intent>{
////////////////////////// Reload Theme
const SingleActivator(
LogicalKeyboardKey.keyR,
control: true,
alt: true,
): const ReloadThemeIntent(),
////////////////////////// Switch Brightness
const SingleActivator(
LogicalKeyboardKey.keyB,
control: true,
alt: true,
): const ChangeBrightnessIntent(),
////////////////////////// Change Color
const SingleActivator(
LogicalKeyboardKey.keyC,
control: true,
alt: true,
): const ChangeColorIntent(),
////////////////////////// Attach/Detach
if (kIsDebugMode)
const SingleActivator(
LogicalKeyboardKey.keyA,
control: true,
alt: true,
): const AttachDetachIntent(),
////////////////////////// Show Developer Page
const SingleActivator(
LogicalKeyboardKey.keyD,
control: true,
alt: true,
): const DeveloperPageIntent(),
////////////////////////// Display Scale Up
SingleActivator(
LogicalKeyboardKey.equal,
meta: isMac || isiOS,
control: !(isMac || isiOS),
): const DisplayScaleUpIntent(),
SingleActivator(
LogicalKeyboardKey.equal,
shift: true,
meta: isMac || isiOS,
control: !(isMac || isiOS),
): const DisplayScaleUpIntent(),
SingleActivator(
LogicalKeyboardKey.add,
shift: true,
meta: isMac || isiOS,
control: !(isMac || isiOS),
): const DisplayScaleUpIntent(),
SingleActivator(
LogicalKeyboardKey.numpadAdd,
meta: isMac || isiOS,
control: !(isMac || isiOS),
): const DisplayScaleUpIntent(),
////////////////////////// Display Scale Down
SingleActivator(
LogicalKeyboardKey.minus,
meta: isMac || isiOS,
control: !(isMac || isiOS),
): const DisplayScaleDownIntent(),
SingleActivator(
LogicalKeyboardKey.numpadSubtract,
meta: isMac || isiOS,
control: !(isMac || isiOS),
): const DisplayScaleDownIntent(),
},
child: Actions(actions: <Type, Action<Intent>>{
ReloadThemeIntent: CallbackAction<ReloadThemeIntent>(
onInvoke: (intent) => reloadTheme(context)),
ChangeBrightnessIntent: CallbackAction<ChangeBrightnessIntent>(
onInvoke: (intent) => _changeBrightness(context)),
ChangeColorIntent: CallbackAction<ChangeColorIntent>(
onInvoke: (intent) => _changeColor(context)),
AttachDetachIntent: CallbackAction<AttachDetachIntent>(
onInvoke: (intent) => _attachDetach(context)),
DeveloperPageIntent: CallbackAction<DeveloperPageIntent>(
onInvoke: (intent) => _developerPage(context)),
DisplayScaleUpIntent: CallbackAction<DisplayScaleUpIntent>(
onInvoke: (intent) => _displayScaleUp(context)),
DisplayScaleDownIntent: CallbackAction<DisplayScaleDownIntent>(
onInvoke: (intent) => _displayScaleDown(context)),
}, child: Focus(autofocus: true, child: child))));
/////////////////////////////////////////////////////////
final Widget child;
builder: (context) => Shortcuts(
shortcuts: <ShortcutActivator, Intent>{
////////////////////////// Reload Theme
const SingleActivator(
LogicalKeyboardKey.keyR,
control: true,
alt: true,
): const ReloadThemeIntent(),
////////////////////////// Switch Brightness
const SingleActivator(
LogicalKeyboardKey.keyB,
control: true,
alt: true,
): const ChangeBrightnessIntent(),
////////////////////////// Change Color
const SingleActivator(
LogicalKeyboardKey.keyC,
control: true,
alt: true,
): const ChangeColorIntent(),
////////////////////////// Attach/Detach
if (kIsDebugMode)
const SingleActivator(
LogicalKeyboardKey.keyA,
control: true,
alt: true,
): const AttachDetachIntent(),
////////////////////////// Show Developer Page
const SingleActivator(
LogicalKeyboardKey.keyD,
control: true,
alt: true,
): const DeveloperPageIntent(),
////////////////////////// Display Scale Up
SingleActivator(
LogicalKeyboardKey.equal,
meta: isMac || isiOS,
control: !(isMac || isiOS),
): const DisplayScaleUpIntent(),
SingleActivator(
LogicalKeyboardKey.equal,
shift: true,
meta: isMac || isiOS,
control: !(isMac || isiOS),
): const DisplayScaleUpIntent(),
SingleActivator(
LogicalKeyboardKey.add,
shift: true,
meta: isMac || isiOS,
control: !(isMac || isiOS),
): const DisplayScaleUpIntent(),
SingleActivator(
LogicalKeyboardKey.numpadAdd,
meta: isMac || isiOS,
control: !(isMac || isiOS),
): const DisplayScaleUpIntent(),
////////////////////////// Display Scale Down
SingleActivator(
LogicalKeyboardKey.minus,
meta: isMac || isiOS,
control: !(isMac || isiOS),
): const DisplayScaleDownIntent(),
SingleActivator(
LogicalKeyboardKey.numpadSubtract,
meta: isMac || isiOS,
control: !(isMac || isiOS),
): const DisplayScaleDownIntent(),
},
child: Actions(
actions: <Type, Action<Intent>>{
ReloadThemeIntent: CallbackAction<ReloadThemeIntent>(
onInvoke: (intent) => reloadTheme(context),
),
ChangeBrightnessIntent: CallbackAction<ChangeBrightnessIntent>(
onInvoke: (intent) => _changeBrightness(context),
),
ChangeColorIntent: CallbackAction<ChangeColorIntent>(
onInvoke: (intent) => _changeColor(context),
),
AttachDetachIntent: CallbackAction<AttachDetachIntent>(
onInvoke: (intent) => _attachDetach(context),
),
DeveloperPageIntent: CallbackAction<DeveloperPageIntent>(
onInvoke: (intent) => _developerPage(context),
),
DisplayScaleUpIntent: CallbackAction<DisplayScaleUpIntent>(
onInvoke: (intent) => _displayScaleUp(context),
),
DisplayScaleDownIntent: CallbackAction<DisplayScaleDownIntent>(
onInvoke: (intent) => _displayScaleDown(context),
),
},
child: Focus(autofocus: true, child: child),
),
),
);
}

View file

@ -5,6 +5,46 @@ import 'package:flutter/material.dart';
import '../../../theme/views/preferences/preferences.dart';
class MenuItemWidget extends StatelessWidget {
////////////////////////////////////////////////////////////////////////////
final String title;
final Widget? headerWidget;
final Widget? widthBox;
final TextStyle titleStyle;
final Color foregroundColor;
final void Function()? callback;
final IconData? footerButtonIcon;
final void Function()? footerCallback;
final Color? backgroundColor;
final Color? backgroundHoverColor;
final Color? backgroundFocusColor;
final Color? borderColor;
final double? borderRadius;
final Color? borderHoverColor;
final Color? borderFocusColor;
final Color? footerButtonIconColor;
final Color? footerButtonIconHoverColor;
final Color? footerButtonIconFocusColor;
final double minHeight;
const MenuItemWidget({
required this.title,
required this.titleStyle,
@ -30,65 +70,67 @@ class MenuItemWidget extends StatelessWidget {
@override
Widget build(BuildContext context) => TextButton(
onPressed: callback,
style: TextButton.styleFrom(foregroundColor: foregroundColor).copyWith(
backgroundColor: WidgetStateProperty.resolveWith((states) {
if (states.contains(WidgetState.hovered)) {
return backgroundHoverColor;
}
if (states.contains(WidgetState.focused)) {
return backgroundFocusColor;
}
return backgroundColor;
}),
overlayColor:
WidgetStateProperty.resolveWith((states) => backgroundHoverColor),
side: WidgetStateBorderSide.resolveWith((states) {
if (states.contains(WidgetState.hovered)) {
return borderColor != null
? BorderSide(width: 2, color: borderHoverColor!)
: null;
}
if (states.contains(WidgetState.focused)) {
return borderColor != null
? BorderSide(width: 2, color: borderFocusColor!)
: null;
}
return borderColor != null
? BorderSide(width: 2, color: borderColor!)
: null;
}),
shape: WidgetStateProperty.all(RoundedRectangleBorder(
borderRadius: BorderRadius.circular(borderRadius ?? 0)))),
child: ConstrainedBox(
constraints: BoxConstraints(minHeight: minHeight),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
if (headerWidget != null) headerWidget!,
if (widthBox != null) widthBox!,
Expanded(
child: FittedBox(
alignment: Alignment.centerLeft,
fit: BoxFit.scaleDown,
child: Text(
title,
style: titleStyle,
).paddingAll(8)),
onPressed: callback,
style: TextButton.styleFrom(foregroundColor: foregroundColor).copyWith(
backgroundColor: WidgetStateProperty.resolveWith((states) {
if (states.contains(WidgetState.hovered)) {
return backgroundHoverColor;
}
if (states.contains(WidgetState.focused)) {
return backgroundFocusColor;
}
return backgroundColor;
}),
overlayColor: WidgetStateProperty.resolveWith(
(states) => backgroundHoverColor,
),
side: WidgetStateBorderSide.resolveWith((states) {
if (states.contains(WidgetState.hovered)) {
return borderColor != null
? BorderSide(width: 2, color: borderHoverColor!)
: null;
}
if (states.contains(WidgetState.focused)) {
return borderColor != null
? BorderSide(width: 2, color: borderFocusColor!)
: null;
}
return borderColor != null
? BorderSide(width: 2, color: borderColor!)
: null;
}),
shape: WidgetStateProperty.all(
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(borderRadius ?? 0),
),
),
),
child: ConstrainedBox(
constraints: BoxConstraints(minHeight: minHeight),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
if (headerWidget != null) headerWidget!,
if (widthBox != null) widthBox!,
Expanded(
child: FittedBox(
alignment: Alignment.centerLeft,
fit: BoxFit.scaleDown,
child: Text(title, style: titleStyle).paddingAll(8),
),
if (footerButtonIcon != null)
IconButton(
color: footerButtonIconColor,
focusColor: footerButtonIconFocusColor,
hoverColor: footerButtonIconHoverColor,
icon: Icon(
footerButtonIcon,
size: 24.scaled(context),
),
onPressed: footerCallback),
],
).paddingAll(2),
));
),
if (footerButtonIcon != null)
IconButton(
color: footerButtonIconColor,
focusColor: footerButtonIconFocusColor,
hoverColor: footerButtonIconHoverColor,
icon: Icon(footerButtonIcon, size: 24.scaled(context)),
onPressed: footerCallback,
),
],
).paddingAll(2),
),
);
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
@ -99,14 +141,21 @@ class MenuItemWidget extends StatelessWidget {
..add(DiagnosticsProperty<Color>('foregroundColor', foregroundColor))
..add(StringProperty('title', title))
..add(
DiagnosticsProperty<IconData?>('footerButtonIcon', footerButtonIcon))
..add(ObjectFlagProperty<void Function()?>.has(
'footerCallback', footerCallback))
DiagnosticsProperty<IconData?>('footerButtonIcon', footerButtonIcon),
)
..add(
ObjectFlagProperty<void Function()?>.has(
'footerCallback',
footerCallback,
),
)
..add(ColorProperty('footerButtonIconColor', footerButtonIconColor))
..add(ColorProperty(
'footerButtonIconHoverColor', footerButtonIconHoverColor))
..add(ColorProperty(
'footerButtonIconFocusColor', footerButtonIconFocusColor))
..add(
ColorProperty('footerButtonIconHoverColor', footerButtonIconHoverColor),
)
..add(
ColorProperty('footerButtonIconFocusColor', footerButtonIconFocusColor),
)
..add(ColorProperty('backgroundColor', backgroundColor))
..add(ColorProperty('backgroundHoverColor', backgroundHoverColor))
..add(ColorProperty('backgroundFocusColor', backgroundFocusColor))
@ -116,26 +165,4 @@ class MenuItemWidget extends StatelessWidget {
..add(ColorProperty('borderFocusColor', borderFocusColor))
..add(DoubleProperty('minHeight', minHeight));
}
////////////////////////////////////////////////////////////////////////////
final String title;
final Widget? headerWidget;
final Widget? widthBox;
final TextStyle titleStyle;
final Color foregroundColor;
final void Function()? callback;
final IconData? footerButtonIcon;
final void Function()? footerCallback;
final Color? backgroundColor;
final Color? backgroundHoverColor;
final Color? backgroundFocusColor;
final Color? borderColor;
final double? borderRadius;
final Color? borderHoverColor;
final Color? borderFocusColor;
final Color? footerButtonIconColor;
final Color? footerButtonIconHoverColor;
final Color? footerButtonIconFocusColor;
final double minHeight;
}

View file

@ -6,6 +6,8 @@ part 'notifications_preference.g.dart';
@freezed
sealed class NotificationsPreference with _$NotificationsPreference {
static const defaults = NotificationsPreference();
const factory NotificationsPreference({
@Default(true) bool displayBetaWarning,
@Default(true) bool enableBadge,
@ -23,8 +25,6 @@ sealed class NotificationsPreference with _$NotificationsPreference {
factory NotificationsPreference.fromJson(dynamic json) =>
_$NotificationsPreferenceFromJson(json as Map<String, dynamic>);
static const NotificationsPreference defaults = NotificationsPreference();
}
enum NotificationMode {

View file

@ -1,6 +1,5 @@
// dart format width=80
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// coverage:ignore-file
// 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
@ -15,94 +14,47 @@ T _$identity<T>(T value) => value;
/// @nodoc
mixin _$NotificationsPreference {
bool get displayBetaWarning;
bool get enableBadge;
bool get enableNotifications;
MessageNotificationContent get messageNotificationContent;
NotificationMode get onInvitationAcceptedMode;
SoundEffect get onInvitationAcceptedSound;
NotificationMode get onMessageReceivedMode;
SoundEffect get onMessageReceivedSound;
SoundEffect get onMessageSentSound;
/// Create a copy of NotificationsPreference
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$NotificationsPreferenceCopyWith<NotificationsPreference> get copyWith =>
_$NotificationsPreferenceCopyWithImpl<NotificationsPreference>(
this as NotificationsPreference, _$identity);
bool get displayBetaWarning; bool get enableBadge; bool get enableNotifications; MessageNotificationContent get messageNotificationContent; NotificationMode get onInvitationAcceptedMode; SoundEffect get onInvitationAcceptedSound; NotificationMode get onMessageReceivedMode; SoundEffect get onMessageReceivedSound; SoundEffect get onMessageSentSound;
/// Create a copy of NotificationsPreference
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$NotificationsPreferenceCopyWith<NotificationsPreference> get copyWith => _$NotificationsPreferenceCopyWithImpl<NotificationsPreference>(this as NotificationsPreference, _$identity);
/// Serializes this NotificationsPreference to a JSON map.
Map<String, dynamic> toJson();
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is NotificationsPreference &&
(identical(other.displayBetaWarning, displayBetaWarning) ||
other.displayBetaWarning == displayBetaWarning) &&
(identical(other.enableBadge, enableBadge) ||
other.enableBadge == enableBadge) &&
(identical(other.enableNotifications, enableNotifications) ||
other.enableNotifications == enableNotifications) &&
(identical(other.messageNotificationContent,
messageNotificationContent) ||
other.messageNotificationContent ==
messageNotificationContent) &&
(identical(
other.onInvitationAcceptedMode, onInvitationAcceptedMode) ||
other.onInvitationAcceptedMode == onInvitationAcceptedMode) &&
(identical(other.onInvitationAcceptedSound,
onInvitationAcceptedSound) ||
other.onInvitationAcceptedSound == onInvitationAcceptedSound) &&
(identical(other.onMessageReceivedMode, onMessageReceivedMode) ||
other.onMessageReceivedMode == onMessageReceivedMode) &&
(identical(other.onMessageReceivedSound, onMessageReceivedSound) ||
other.onMessageReceivedSound == onMessageReceivedSound) &&
(identical(other.onMessageSentSound, onMessageSentSound) ||
other.onMessageSentSound == onMessageSentSound));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(
runtimeType,
displayBetaWarning,
enableBadge,
enableNotifications,
messageNotificationContent,
onInvitationAcceptedMode,
onInvitationAcceptedSound,
onMessageReceivedMode,
onMessageReceivedSound,
onMessageSentSound);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is NotificationsPreference&&(identical(other.displayBetaWarning, displayBetaWarning) || other.displayBetaWarning == displayBetaWarning)&&(identical(other.enableBadge, enableBadge) || other.enableBadge == enableBadge)&&(identical(other.enableNotifications, enableNotifications) || other.enableNotifications == enableNotifications)&&(identical(other.messageNotificationContent, messageNotificationContent) || other.messageNotificationContent == messageNotificationContent)&&(identical(other.onInvitationAcceptedMode, onInvitationAcceptedMode) || other.onInvitationAcceptedMode == onInvitationAcceptedMode)&&(identical(other.onInvitationAcceptedSound, onInvitationAcceptedSound) || other.onInvitationAcceptedSound == onInvitationAcceptedSound)&&(identical(other.onMessageReceivedMode, onMessageReceivedMode) || other.onMessageReceivedMode == onMessageReceivedMode)&&(identical(other.onMessageReceivedSound, onMessageReceivedSound) || other.onMessageReceivedSound == onMessageReceivedSound)&&(identical(other.onMessageSentSound, onMessageSentSound) || other.onMessageSentSound == onMessageSentSound));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,displayBetaWarning,enableBadge,enableNotifications,messageNotificationContent,onInvitationAcceptedMode,onInvitationAcceptedSound,onMessageReceivedMode,onMessageReceivedSound,onMessageSentSound);
@override
String toString() {
return 'NotificationsPreference(displayBetaWarning: $displayBetaWarning, enableBadge: $enableBadge, enableNotifications: $enableNotifications, messageNotificationContent: $messageNotificationContent, onInvitationAcceptedMode: $onInvitationAcceptedMode, onInvitationAcceptedSound: $onInvitationAcceptedSound, onMessageReceivedMode: $onMessageReceivedMode, onMessageReceivedSound: $onMessageReceivedSound, onMessageSentSound: $onMessageSentSound)';
}
@override
String toString() {
return 'NotificationsPreference(displayBetaWarning: $displayBetaWarning, enableBadge: $enableBadge, enableNotifications: $enableNotifications, messageNotificationContent: $messageNotificationContent, onInvitationAcceptedMode: $onInvitationAcceptedMode, onInvitationAcceptedSound: $onInvitationAcceptedSound, onMessageReceivedMode: $onMessageReceivedMode, onMessageReceivedSound: $onMessageReceivedSound, onMessageSentSound: $onMessageSentSound)';
}
}
/// @nodoc
abstract mixin class $NotificationsPreferenceCopyWith<$Res> {
factory $NotificationsPreferenceCopyWith(NotificationsPreference value,
$Res Function(NotificationsPreference) _then) =
_$NotificationsPreferenceCopyWithImpl;
@useResult
$Res call(
{bool displayBetaWarning,
bool enableBadge,
bool enableNotifications,
MessageNotificationContent messageNotificationContent,
NotificationMode onInvitationAcceptedMode,
SoundEffect onInvitationAcceptedSound,
NotificationMode onMessageReceivedMode,
SoundEffect onMessageReceivedSound,
SoundEffect onMessageSentSound});
}
abstract mixin class $NotificationsPreferenceCopyWith<$Res> {
factory $NotificationsPreferenceCopyWith(NotificationsPreference value, $Res Function(NotificationsPreference) _then) = _$NotificationsPreferenceCopyWithImpl;
@useResult
$Res call({
bool displayBetaWarning, bool enableBadge, bool enableNotifications, MessageNotificationContent messageNotificationContent, NotificationMode onInvitationAcceptedMode, SoundEffect onInvitationAcceptedSound, NotificationMode onMessageReceivedMode, SoundEffect onMessageReceivedSound, SoundEffect onMessageSentSound
});
}
/// @nodoc
class _$NotificationsPreferenceCopyWithImpl<$Res>
implements $NotificationsPreferenceCopyWith<$Res> {
@ -111,192 +63,207 @@ class _$NotificationsPreferenceCopyWithImpl<$Res>
final NotificationsPreference _self;
final $Res Function(NotificationsPreference) _then;
/// Create a copy of NotificationsPreference
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? displayBetaWarning = null,
Object? enableBadge = null,
Object? enableNotifications = null,
Object? messageNotificationContent = null,
Object? onInvitationAcceptedMode = null,
Object? onInvitationAcceptedSound = null,
Object? onMessageReceivedMode = null,
Object? onMessageReceivedSound = null,
Object? onMessageSentSound = null,
}) {
return _then(_self.copyWith(
displayBetaWarning: null == displayBetaWarning
? _self.displayBetaWarning
: displayBetaWarning // ignore: cast_nullable_to_non_nullable
as bool,
enableBadge: null == enableBadge
? _self.enableBadge
: enableBadge // ignore: cast_nullable_to_non_nullable
as bool,
enableNotifications: null == enableNotifications
? _self.enableNotifications
: enableNotifications // ignore: cast_nullable_to_non_nullable
as bool,
messageNotificationContent: null == messageNotificationContent
? _self.messageNotificationContent
: messageNotificationContent // ignore: cast_nullable_to_non_nullable
as MessageNotificationContent,
onInvitationAcceptedMode: null == onInvitationAcceptedMode
? _self.onInvitationAcceptedMode
: onInvitationAcceptedMode // ignore: cast_nullable_to_non_nullable
as NotificationMode,
onInvitationAcceptedSound: null == onInvitationAcceptedSound
? _self.onInvitationAcceptedSound
: onInvitationAcceptedSound // ignore: cast_nullable_to_non_nullable
as SoundEffect,
onMessageReceivedMode: null == onMessageReceivedMode
? _self.onMessageReceivedMode
: onMessageReceivedMode // ignore: cast_nullable_to_non_nullable
as NotificationMode,
onMessageReceivedSound: null == onMessageReceivedSound
? _self.onMessageReceivedSound
: onMessageReceivedSound // ignore: cast_nullable_to_non_nullable
as SoundEffect,
onMessageSentSound: null == onMessageSentSound
? _self.onMessageSentSound
: onMessageSentSound // ignore: cast_nullable_to_non_nullable
as SoundEffect,
));
}
/// Create a copy of NotificationsPreference
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? displayBetaWarning = null,Object? enableBadge = null,Object? enableNotifications = null,Object? messageNotificationContent = null,Object? onInvitationAcceptedMode = null,Object? onInvitationAcceptedSound = null,Object? onMessageReceivedMode = null,Object? onMessageReceivedSound = null,Object? onMessageSentSound = null,}) {
return _then(_self.copyWith(
displayBetaWarning: null == displayBetaWarning ? _self.displayBetaWarning : displayBetaWarning // ignore: cast_nullable_to_non_nullable
as bool,enableBadge: null == enableBadge ? _self.enableBadge : enableBadge // ignore: cast_nullable_to_non_nullable
as bool,enableNotifications: null == enableNotifications ? _self.enableNotifications : enableNotifications // ignore: cast_nullable_to_non_nullable
as bool,messageNotificationContent: null == messageNotificationContent ? _self.messageNotificationContent : messageNotificationContent // ignore: cast_nullable_to_non_nullable
as MessageNotificationContent,onInvitationAcceptedMode: null == onInvitationAcceptedMode ? _self.onInvitationAcceptedMode : onInvitationAcceptedMode // ignore: cast_nullable_to_non_nullable
as NotificationMode,onInvitationAcceptedSound: null == onInvitationAcceptedSound ? _self.onInvitationAcceptedSound : onInvitationAcceptedSound // ignore: cast_nullable_to_non_nullable
as SoundEffect,onMessageReceivedMode: null == onMessageReceivedMode ? _self.onMessageReceivedMode : onMessageReceivedMode // ignore: cast_nullable_to_non_nullable
as NotificationMode,onMessageReceivedSound: null == onMessageReceivedSound ? _self.onMessageReceivedSound : onMessageReceivedSound // ignore: cast_nullable_to_non_nullable
as SoundEffect,onMessageSentSound: null == onMessageSentSound ? _self.onMessageSentSound : onMessageSentSound // ignore: cast_nullable_to_non_nullable
as SoundEffect,
));
}
}
/// Adds pattern-matching-related methods to [NotificationsPreference].
extension NotificationsPreferencePatterns on NotificationsPreference {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _NotificationsPreference value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _NotificationsPreference() when $default != null:
return $default(_that);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _NotificationsPreference value) $default,){
final _that = this;
switch (_that) {
case _NotificationsPreference():
return $default(_that);}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _NotificationsPreference value)? $default,){
final _that = this;
switch (_that) {
case _NotificationsPreference() when $default != null:
return $default(_that);case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( bool displayBetaWarning, bool enableBadge, bool enableNotifications, MessageNotificationContent messageNotificationContent, NotificationMode onInvitationAcceptedMode, SoundEffect onInvitationAcceptedSound, NotificationMode onMessageReceivedMode, SoundEffect onMessageReceivedSound, SoundEffect onMessageSentSound)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _NotificationsPreference() when $default != null:
return $default(_that.displayBetaWarning,_that.enableBadge,_that.enableNotifications,_that.messageNotificationContent,_that.onInvitationAcceptedMode,_that.onInvitationAcceptedSound,_that.onMessageReceivedMode,_that.onMessageReceivedSound,_that.onMessageSentSound);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( bool displayBetaWarning, bool enableBadge, bool enableNotifications, MessageNotificationContent messageNotificationContent, NotificationMode onInvitationAcceptedMode, SoundEffect onInvitationAcceptedSound, NotificationMode onMessageReceivedMode, SoundEffect onMessageReceivedSound, SoundEffect onMessageSentSound) $default,) {final _that = this;
switch (_that) {
case _NotificationsPreference():
return $default(_that.displayBetaWarning,_that.enableBadge,_that.enableNotifications,_that.messageNotificationContent,_that.onInvitationAcceptedMode,_that.onInvitationAcceptedSound,_that.onMessageReceivedMode,_that.onMessageReceivedSound,_that.onMessageSentSound);}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( bool displayBetaWarning, bool enableBadge, bool enableNotifications, MessageNotificationContent messageNotificationContent, NotificationMode onInvitationAcceptedMode, SoundEffect onInvitationAcceptedSound, NotificationMode onMessageReceivedMode, SoundEffect onMessageReceivedSound, SoundEffect onMessageSentSound)? $default,) {final _that = this;
switch (_that) {
case _NotificationsPreference() when $default != null:
return $default(_that.displayBetaWarning,_that.enableBadge,_that.enableNotifications,_that.messageNotificationContent,_that.onInvitationAcceptedMode,_that.onInvitationAcceptedSound,_that.onMessageReceivedMode,_that.onMessageReceivedSound,_that.onMessageSentSound);case _:
return null;
}
}
}
/// @nodoc
@JsonSerializable()
class _NotificationsPreference implements NotificationsPreference {
const _NotificationsPreference(
{this.displayBetaWarning = true,
this.enableBadge = true,
this.enableNotifications = true,
this.messageNotificationContent =
MessageNotificationContent.nameAndContent,
this.onInvitationAcceptedMode = NotificationMode.inAppOrPush,
this.onInvitationAcceptedSound = SoundEffect.beepBaDeep,
this.onMessageReceivedMode = NotificationMode.inAppOrPush,
this.onMessageReceivedSound = SoundEffect.boop,
this.onMessageSentSound = SoundEffect.bonk});
factory _NotificationsPreference.fromJson(Map<String, dynamic> json) =>
_$NotificationsPreferenceFromJson(json);
const _NotificationsPreference({this.displayBetaWarning = true, this.enableBadge = true, this.enableNotifications = true, this.messageNotificationContent = MessageNotificationContent.nameAndContent, this.onInvitationAcceptedMode = NotificationMode.inAppOrPush, this.onInvitationAcceptedSound = SoundEffect.beepBaDeep, this.onMessageReceivedMode = NotificationMode.inAppOrPush, this.onMessageReceivedSound = SoundEffect.boop, this.onMessageSentSound = SoundEffect.bonk});
factory _NotificationsPreference.fromJson(Map<String, dynamic> json) => _$NotificationsPreferenceFromJson(json);
@override
@JsonKey()
final bool displayBetaWarning;
@override
@JsonKey()
final bool enableBadge;
@override
@JsonKey()
final bool enableNotifications;
@override
@JsonKey()
final MessageNotificationContent messageNotificationContent;
@override
@JsonKey()
final NotificationMode onInvitationAcceptedMode;
@override
@JsonKey()
final SoundEffect onInvitationAcceptedSound;
@override
@JsonKey()
final NotificationMode onMessageReceivedMode;
@override
@JsonKey()
final SoundEffect onMessageReceivedSound;
@override
@JsonKey()
final SoundEffect onMessageSentSound;
@override@JsonKey() final bool displayBetaWarning;
@override@JsonKey() final bool enableBadge;
@override@JsonKey() final bool enableNotifications;
@override@JsonKey() final MessageNotificationContent messageNotificationContent;
@override@JsonKey() final NotificationMode onInvitationAcceptedMode;
@override@JsonKey() final SoundEffect onInvitationAcceptedSound;
@override@JsonKey() final NotificationMode onMessageReceivedMode;
@override@JsonKey() final SoundEffect onMessageReceivedSound;
@override@JsonKey() final SoundEffect onMessageSentSound;
/// Create a copy of NotificationsPreference
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$NotificationsPreferenceCopyWith<_NotificationsPreference> get copyWith =>
__$NotificationsPreferenceCopyWithImpl<_NotificationsPreference>(
this, _$identity);
/// Create a copy of NotificationsPreference
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$NotificationsPreferenceCopyWith<_NotificationsPreference> get copyWith => __$NotificationsPreferenceCopyWithImpl<_NotificationsPreference>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$NotificationsPreferenceToJson(
this,
);
}
@override
Map<String, dynamic> toJson() {
return _$NotificationsPreferenceToJson(this, );
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _NotificationsPreference &&
(identical(other.displayBetaWarning, displayBetaWarning) ||
other.displayBetaWarning == displayBetaWarning) &&
(identical(other.enableBadge, enableBadge) ||
other.enableBadge == enableBadge) &&
(identical(other.enableNotifications, enableNotifications) ||
other.enableNotifications == enableNotifications) &&
(identical(other.messageNotificationContent,
messageNotificationContent) ||
other.messageNotificationContent ==
messageNotificationContent) &&
(identical(
other.onInvitationAcceptedMode, onInvitationAcceptedMode) ||
other.onInvitationAcceptedMode == onInvitationAcceptedMode) &&
(identical(other.onInvitationAcceptedSound,
onInvitationAcceptedSound) ||
other.onInvitationAcceptedSound == onInvitationAcceptedSound) &&
(identical(other.onMessageReceivedMode, onMessageReceivedMode) ||
other.onMessageReceivedMode == onMessageReceivedMode) &&
(identical(other.onMessageReceivedSound, onMessageReceivedSound) ||
other.onMessageReceivedSound == onMessageReceivedSound) &&
(identical(other.onMessageSentSound, onMessageSentSound) ||
other.onMessageSentSound == onMessageSentSound));
}
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _NotificationsPreference&&(identical(other.displayBetaWarning, displayBetaWarning) || other.displayBetaWarning == displayBetaWarning)&&(identical(other.enableBadge, enableBadge) || other.enableBadge == enableBadge)&&(identical(other.enableNotifications, enableNotifications) || other.enableNotifications == enableNotifications)&&(identical(other.messageNotificationContent, messageNotificationContent) || other.messageNotificationContent == messageNotificationContent)&&(identical(other.onInvitationAcceptedMode, onInvitationAcceptedMode) || other.onInvitationAcceptedMode == onInvitationAcceptedMode)&&(identical(other.onInvitationAcceptedSound, onInvitationAcceptedSound) || other.onInvitationAcceptedSound == onInvitationAcceptedSound)&&(identical(other.onMessageReceivedMode, onMessageReceivedMode) || other.onMessageReceivedMode == onMessageReceivedMode)&&(identical(other.onMessageReceivedSound, onMessageReceivedSound) || other.onMessageReceivedSound == onMessageReceivedSound)&&(identical(other.onMessageSentSound, onMessageSentSound) || other.onMessageSentSound == onMessageSentSound));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,displayBetaWarning,enableBadge,enableNotifications,messageNotificationContent,onInvitationAcceptedMode,onInvitationAcceptedSound,onMessageReceivedMode,onMessageReceivedSound,onMessageSentSound);
@override
String toString() {
return 'NotificationsPreference(displayBetaWarning: $displayBetaWarning, enableBadge: $enableBadge, enableNotifications: $enableNotifications, messageNotificationContent: $messageNotificationContent, onInvitationAcceptedMode: $onInvitationAcceptedMode, onInvitationAcceptedSound: $onInvitationAcceptedSound, onMessageReceivedMode: $onMessageReceivedMode, onMessageReceivedSound: $onMessageReceivedSound, onMessageSentSound: $onMessageSentSound)';
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(
runtimeType,
displayBetaWarning,
enableBadge,
enableNotifications,
messageNotificationContent,
onInvitationAcceptedMode,
onInvitationAcceptedSound,
onMessageReceivedMode,
onMessageReceivedSound,
onMessageSentSound);
@override
String toString() {
return 'NotificationsPreference(displayBetaWarning: $displayBetaWarning, enableBadge: $enableBadge, enableNotifications: $enableNotifications, messageNotificationContent: $messageNotificationContent, onInvitationAcceptedMode: $onInvitationAcceptedMode, onInvitationAcceptedSound: $onInvitationAcceptedSound, onMessageReceivedMode: $onMessageReceivedMode, onMessageReceivedSound: $onMessageReceivedSound, onMessageSentSound: $onMessageSentSound)';
}
}
/// @nodoc
abstract mixin class _$NotificationsPreferenceCopyWith<$Res>
implements $NotificationsPreferenceCopyWith<$Res> {
factory _$NotificationsPreferenceCopyWith(_NotificationsPreference value,
$Res Function(_NotificationsPreference) _then) =
__$NotificationsPreferenceCopyWithImpl;
@override
@useResult
$Res call(
{bool displayBetaWarning,
bool enableBadge,
bool enableNotifications,
MessageNotificationContent messageNotificationContent,
NotificationMode onInvitationAcceptedMode,
SoundEffect onInvitationAcceptedSound,
NotificationMode onMessageReceivedMode,
SoundEffect onMessageReceivedSound,
SoundEffect onMessageSentSound});
}
abstract mixin class _$NotificationsPreferenceCopyWith<$Res> implements $NotificationsPreferenceCopyWith<$Res> {
factory _$NotificationsPreferenceCopyWith(_NotificationsPreference value, $Res Function(_NotificationsPreference) _then) = __$NotificationsPreferenceCopyWithImpl;
@override @useResult
$Res call({
bool displayBetaWarning, bool enableBadge, bool enableNotifications, MessageNotificationContent messageNotificationContent, NotificationMode onInvitationAcceptedMode, SoundEffect onInvitationAcceptedSound, NotificationMode onMessageReceivedMode, SoundEffect onMessageReceivedSound, SoundEffect onMessageSentSound
});
}
/// @nodoc
class __$NotificationsPreferenceCopyWithImpl<$Res>
implements _$NotificationsPreferenceCopyWith<$Res> {
@ -305,60 +272,24 @@ class __$NotificationsPreferenceCopyWithImpl<$Res>
final _NotificationsPreference _self;
final $Res Function(_NotificationsPreference) _then;
/// Create a copy of NotificationsPreference
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$Res call({
Object? displayBetaWarning = null,
Object? enableBadge = null,
Object? enableNotifications = null,
Object? messageNotificationContent = null,
Object? onInvitationAcceptedMode = null,
Object? onInvitationAcceptedSound = null,
Object? onMessageReceivedMode = null,
Object? onMessageReceivedSound = null,
Object? onMessageSentSound = null,
}) {
return _then(_NotificationsPreference(
displayBetaWarning: null == displayBetaWarning
? _self.displayBetaWarning
: displayBetaWarning // ignore: cast_nullable_to_non_nullable
as bool,
enableBadge: null == enableBadge
? _self.enableBadge
: enableBadge // ignore: cast_nullable_to_non_nullable
as bool,
enableNotifications: null == enableNotifications
? _self.enableNotifications
: enableNotifications // ignore: cast_nullable_to_non_nullable
as bool,
messageNotificationContent: null == messageNotificationContent
? _self.messageNotificationContent
: messageNotificationContent // ignore: cast_nullable_to_non_nullable
as MessageNotificationContent,
onInvitationAcceptedMode: null == onInvitationAcceptedMode
? _self.onInvitationAcceptedMode
: onInvitationAcceptedMode // ignore: cast_nullable_to_non_nullable
as NotificationMode,
onInvitationAcceptedSound: null == onInvitationAcceptedSound
? _self.onInvitationAcceptedSound
: onInvitationAcceptedSound // ignore: cast_nullable_to_non_nullable
as SoundEffect,
onMessageReceivedMode: null == onMessageReceivedMode
? _self.onMessageReceivedMode
: onMessageReceivedMode // ignore: cast_nullable_to_non_nullable
as NotificationMode,
onMessageReceivedSound: null == onMessageReceivedSound
? _self.onMessageReceivedSound
: onMessageReceivedSound // ignore: cast_nullable_to_non_nullable
as SoundEffect,
onMessageSentSound: null == onMessageSentSound
? _self.onMessageSentSound
: onMessageSentSound // ignore: cast_nullable_to_non_nullable
as SoundEffect,
));
}
/// Create a copy of NotificationsPreference
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? displayBetaWarning = null,Object? enableBadge = null,Object? enableNotifications = null,Object? messageNotificationContent = null,Object? onInvitationAcceptedMode = null,Object? onInvitationAcceptedSound = null,Object? onMessageReceivedMode = null,Object? onMessageReceivedSound = null,Object? onMessageSentSound = null,}) {
return _then(_NotificationsPreference(
displayBetaWarning: null == displayBetaWarning ? _self.displayBetaWarning : displayBetaWarning // ignore: cast_nullable_to_non_nullable
as bool,enableBadge: null == enableBadge ? _self.enableBadge : enableBadge // ignore: cast_nullable_to_non_nullable
as bool,enableNotifications: null == enableNotifications ? _self.enableNotifications : enableNotifications // ignore: cast_nullable_to_non_nullable
as bool,messageNotificationContent: null == messageNotificationContent ? _self.messageNotificationContent : messageNotificationContent // ignore: cast_nullable_to_non_nullable
as MessageNotificationContent,onInvitationAcceptedMode: null == onInvitationAcceptedMode ? _self.onInvitationAcceptedMode : onInvitationAcceptedMode // ignore: cast_nullable_to_non_nullable
as NotificationMode,onInvitationAcceptedSound: null == onInvitationAcceptedSound ? _self.onInvitationAcceptedSound : onInvitationAcceptedSound // ignore: cast_nullable_to_non_nullable
as SoundEffect,onMessageReceivedMode: null == onMessageReceivedMode ? _self.onMessageReceivedMode : onMessageReceivedMode // ignore: cast_nullable_to_non_nullable
as NotificationMode,onMessageReceivedSound: null == onMessageReceivedSound ? _self.onMessageReceivedSound : onMessageReceivedSound // ignore: cast_nullable_to_non_nullable
as SoundEffect,onMessageSentSound: null == onMessageSentSound ? _self.onMessageSentSound : onMessageSentSound // ignore: cast_nullable_to_non_nullable
as SoundEffect,
));
}
}
// dart format on

View file

@ -7,44 +7,43 @@ part of 'notifications_preference.dart';
// **************************************************************************
_NotificationsPreference _$NotificationsPreferenceFromJson(
Map<String, dynamic> json) =>
_NotificationsPreference(
displayBetaWarning: json['display_beta_warning'] as bool? ?? true,
enableBadge: json['enable_badge'] as bool? ?? true,
enableNotifications: json['enable_notifications'] as bool? ?? true,
messageNotificationContent: json['message_notification_content'] == null
? MessageNotificationContent.nameAndContent
: MessageNotificationContent.fromJson(
json['message_notification_content']),
onInvitationAcceptedMode: json['on_invitation_accepted_mode'] == null
? NotificationMode.inAppOrPush
: NotificationMode.fromJson(json['on_invitation_accepted_mode']),
onInvitationAcceptedSound: json['on_invitation_accepted_sound'] == null
? SoundEffect.beepBaDeep
: SoundEffect.fromJson(json['on_invitation_accepted_sound']),
onMessageReceivedMode: json['on_message_received_mode'] == null
? NotificationMode.inAppOrPush
: NotificationMode.fromJson(json['on_message_received_mode']),
onMessageReceivedSound: json['on_message_received_sound'] == null
? SoundEffect.boop
: SoundEffect.fromJson(json['on_message_received_sound']),
onMessageSentSound: json['on_message_sent_sound'] == null
? SoundEffect.bonk
: SoundEffect.fromJson(json['on_message_sent_sound']),
);
Map<String, dynamic> json,
) => _NotificationsPreference(
displayBetaWarning: json['display_beta_warning'] as bool? ?? true,
enableBadge: json['enable_badge'] as bool? ?? true,
enableNotifications: json['enable_notifications'] as bool? ?? true,
messageNotificationContent: json['message_notification_content'] == null
? MessageNotificationContent.nameAndContent
: MessageNotificationContent.fromJson(
json['message_notification_content'],
),
onInvitationAcceptedMode: json['on_invitation_accepted_mode'] == null
? NotificationMode.inAppOrPush
: NotificationMode.fromJson(json['on_invitation_accepted_mode']),
onInvitationAcceptedSound: json['on_invitation_accepted_sound'] == null
? SoundEffect.beepBaDeep
: SoundEffect.fromJson(json['on_invitation_accepted_sound']),
onMessageReceivedMode: json['on_message_received_mode'] == null
? NotificationMode.inAppOrPush
: NotificationMode.fromJson(json['on_message_received_mode']),
onMessageReceivedSound: json['on_message_received_sound'] == null
? SoundEffect.boop
: SoundEffect.fromJson(json['on_message_received_sound']),
onMessageSentSound: json['on_message_sent_sound'] == null
? SoundEffect.bonk
: SoundEffect.fromJson(json['on_message_sent_sound']),
);
Map<String, dynamic> _$NotificationsPreferenceToJson(
_NotificationsPreference instance) =>
<String, dynamic>{
'display_beta_warning': instance.displayBetaWarning,
'enable_badge': instance.enableBadge,
'enable_notifications': instance.enableNotifications,
'message_notification_content':
instance.messageNotificationContent.toJson(),
'on_invitation_accepted_mode': instance.onInvitationAcceptedMode.toJson(),
'on_invitation_accepted_sound':
instance.onInvitationAcceptedSound.toJson(),
'on_message_received_mode': instance.onMessageReceivedMode.toJson(),
'on_message_received_sound': instance.onMessageReceivedSound.toJson(),
'on_message_sent_sound': instance.onMessageSentSound.toJson(),
};
_NotificationsPreference instance,
) => <String, dynamic>{
'display_beta_warning': instance.displayBetaWarning,
'enable_badge': instance.enableBadge,
'enable_notifications': instance.enableNotifications,
'message_notification_content': instance.messageNotificationContent.toJson(),
'on_invitation_accepted_mode': instance.onInvitationAcceptedMode.toJson(),
'on_invitation_accepted_sound': instance.onInvitationAcceptedSound.toJson(),
'on_message_received_mode': instance.onMessageReceivedMode.toJson(),
'on_message_received_sound': instance.onMessageReceivedSound.toJson(),
'on_message_sent_sound': instance.onMessageSentSound.toJson(),
};

View file

@ -1,6 +1,5 @@
// dart format width=80
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// coverage:ignore-file
// 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
@ -12,49 +11,47 @@ part of 'notifications_state.dart';
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$NotificationItem {
NotificationType get type;
String get text;
String? get title;
/// Create a copy of NotificationItem
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$NotificationItemCopyWith<NotificationItem> get copyWith =>
_$NotificationItemCopyWithImpl<NotificationItem>(
this as NotificationItem, _$identity);
NotificationType get type; String get text; String? get title;
/// Create a copy of NotificationItem
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$NotificationItemCopyWith<NotificationItem> get copyWith => _$NotificationItemCopyWithImpl<NotificationItem>(this as NotificationItem, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is NotificationItem &&
(identical(other.type, type) || other.type == type) &&
(identical(other.text, text) || other.text == text) &&
(identical(other.title, title) || other.title == title));
}
@override
int get hashCode => Object.hash(runtimeType, type, text, title);
@override
String toString() {
return 'NotificationItem(type: $type, text: $text, title: $title)';
}
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is NotificationItem&&(identical(other.type, type) || other.type == type)&&(identical(other.text, text) || other.text == text)&&(identical(other.title, title) || other.title == title));
}
@override
int get hashCode => Object.hash(runtimeType,type,text,title);
@override
String toString() {
return 'NotificationItem(type: $type, text: $text, title: $title)';
}
}
/// @nodoc
abstract mixin class $NotificationItemCopyWith<$Res> {
factory $NotificationItemCopyWith(
NotificationItem value, $Res Function(NotificationItem) _then) =
_$NotificationItemCopyWithImpl;
@useResult
$Res call({NotificationType type, String text, String? title});
}
abstract mixin class $NotificationItemCopyWith<$Res> {
factory $NotificationItemCopyWith(NotificationItem value, $Res Function(NotificationItem) _then) = _$NotificationItemCopyWithImpl;
@useResult
$Res call({
NotificationType type, String text, String? title
});
}
/// @nodoc
class _$NotificationItemCopyWithImpl<$Res>
implements $NotificationItemCopyWith<$Res> {
@ -63,82 +60,192 @@ class _$NotificationItemCopyWithImpl<$Res>
final NotificationItem _self;
final $Res Function(NotificationItem) _then;
/// Create a copy of NotificationItem
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? type = null,
Object? text = null,
Object? title = freezed,
}) {
return _then(_self.copyWith(
type: null == type
? _self.type
: type // ignore: cast_nullable_to_non_nullable
as NotificationType,
text: null == text
? _self.text
: text // ignore: cast_nullable_to_non_nullable
as String,
title: freezed == title
? _self.title
: title // ignore: cast_nullable_to_non_nullable
as String?,
));
}
/// Create a copy of NotificationItem
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? type = null,Object? text = null,Object? title = freezed,}) {
return _then(_self.copyWith(
type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
as NotificationType,text: null == text ? _self.text : text // ignore: cast_nullable_to_non_nullable
as String,title: freezed == title ? _self.title : title // ignore: cast_nullable_to_non_nullable
as String?,
));
}
}
/// Adds pattern-matching-related methods to [NotificationItem].
extension NotificationItemPatterns on NotificationItem {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _NotificationItem value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _NotificationItem() when $default != null:
return $default(_that);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _NotificationItem value) $default,){
final _that = this;
switch (_that) {
case _NotificationItem():
return $default(_that);}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _NotificationItem value)? $default,){
final _that = this;
switch (_that) {
case _NotificationItem() when $default != null:
return $default(_that);case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( NotificationType type, String text, String? title)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _NotificationItem() when $default != null:
return $default(_that.type,_that.text,_that.title);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( NotificationType type, String text, String? title) $default,) {final _that = this;
switch (_that) {
case _NotificationItem():
return $default(_that.type,_that.text,_that.title);}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( NotificationType type, String text, String? title)? $default,) {final _that = this;
switch (_that) {
case _NotificationItem() when $default != null:
return $default(_that.type,_that.text,_that.title);case _:
return null;
}
}
}
/// @nodoc
class _NotificationItem implements NotificationItem {
const _NotificationItem({required this.type, required this.text, this.title});
@override
final NotificationType type;
@override
final String text;
@override
final String? title;
@override final NotificationType type;
@override final String text;
@override final String? title;
/// Create a copy of NotificationItem
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$NotificationItemCopyWith<_NotificationItem> get copyWith =>
__$NotificationItemCopyWithImpl<_NotificationItem>(this, _$identity);
/// Create a copy of NotificationItem
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$NotificationItemCopyWith<_NotificationItem> get copyWith => __$NotificationItemCopyWithImpl<_NotificationItem>(this, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _NotificationItem &&
(identical(other.type, type) || other.type == type) &&
(identical(other.text, text) || other.text == text) &&
(identical(other.title, title) || other.title == title));
}
@override
int get hashCode => Object.hash(runtimeType, type, text, title);
@override
String toString() {
return 'NotificationItem(type: $type, text: $text, title: $title)';
}
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _NotificationItem&&(identical(other.type, type) || other.type == type)&&(identical(other.text, text) || other.text == text)&&(identical(other.title, title) || other.title == title));
}
@override
int get hashCode => Object.hash(runtimeType,type,text,title);
@override
String toString() {
return 'NotificationItem(type: $type, text: $text, title: $title)';
}
}
/// @nodoc
abstract mixin class _$NotificationItemCopyWith<$Res>
implements $NotificationItemCopyWith<$Res> {
factory _$NotificationItemCopyWith(
_NotificationItem value, $Res Function(_NotificationItem) _then) =
__$NotificationItemCopyWithImpl;
@override
@useResult
$Res call({NotificationType type, String text, String? title});
}
abstract mixin class _$NotificationItemCopyWith<$Res> implements $NotificationItemCopyWith<$Res> {
factory _$NotificationItemCopyWith(_NotificationItem value, $Res Function(_NotificationItem) _then) = __$NotificationItemCopyWithImpl;
@override @useResult
$Res call({
NotificationType type, String text, String? title
});
}
/// @nodoc
class __$NotificationItemCopyWithImpl<$Res>
implements _$NotificationItemCopyWith<$Res> {
@ -147,71 +254,61 @@ class __$NotificationItemCopyWithImpl<$Res>
final _NotificationItem _self;
final $Res Function(_NotificationItem) _then;
/// Create a copy of NotificationItem
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$Res call({
Object? type = null,
Object? text = null,
Object? title = freezed,
}) {
return _then(_NotificationItem(
type: null == type
? _self.type
: type // ignore: cast_nullable_to_non_nullable
as NotificationType,
text: null == text
? _self.text
: text // ignore: cast_nullable_to_non_nullable
as String,
title: freezed == title
? _self.title
: title // ignore: cast_nullable_to_non_nullable
as String?,
));
}
/// Create a copy of NotificationItem
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? type = null,Object? text = null,Object? title = freezed,}) {
return _then(_NotificationItem(
type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
as NotificationType,text: null == text ? _self.text : text // ignore: cast_nullable_to_non_nullable
as String,title: freezed == title ? _self.title : title // ignore: cast_nullable_to_non_nullable
as String?,
));
}
}
/// @nodoc
mixin _$NotificationsState {
IList<NotificationItem> get queue;
/// Create a copy of NotificationsState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$NotificationsStateCopyWith<NotificationsState> get copyWith =>
_$NotificationsStateCopyWithImpl<NotificationsState>(
this as NotificationsState, _$identity);
IList<NotificationItem> get queue;
/// Create a copy of NotificationsState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$NotificationsStateCopyWith<NotificationsState> get copyWith => _$NotificationsStateCopyWithImpl<NotificationsState>(this as NotificationsState, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is NotificationsState &&
const DeepCollectionEquality().equals(other.queue, queue));
}
@override
int get hashCode =>
Object.hash(runtimeType, const DeepCollectionEquality().hash(queue));
@override
String toString() {
return 'NotificationsState(queue: $queue)';
}
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is NotificationsState&&const DeepCollectionEquality().equals(other.queue, queue));
}
@override
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(queue));
@override
String toString() {
return 'NotificationsState(queue: $queue)';
}
}
/// @nodoc
abstract mixin class $NotificationsStateCopyWith<$Res> {
factory $NotificationsStateCopyWith(
NotificationsState value, $Res Function(NotificationsState) _then) =
_$NotificationsStateCopyWithImpl;
@useResult
$Res call({IList<NotificationItem> queue});
}
abstract mixin class $NotificationsStateCopyWith<$Res> {
factory $NotificationsStateCopyWith(NotificationsState value, $Res Function(NotificationsState) _then) = _$NotificationsStateCopyWithImpl;
@useResult
$Res call({
IList<NotificationItem> queue
});
}
/// @nodoc
class _$NotificationsStateCopyWithImpl<$Res>
implements $NotificationsStateCopyWith<$Res> {
@ -220,67 +317,188 @@ class _$NotificationsStateCopyWithImpl<$Res>
final NotificationsState _self;
final $Res Function(NotificationsState) _then;
/// Create a copy of NotificationsState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? queue = null,
}) {
return _then(_self.copyWith(
queue: null == queue
? _self.queue
: queue // ignore: cast_nullable_to_non_nullable
as IList<NotificationItem>,
));
}
/// Create a copy of NotificationsState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? queue = null,}) {
return _then(_self.copyWith(
queue: null == queue ? _self.queue : queue // ignore: cast_nullable_to_non_nullable
as IList<NotificationItem>,
));
}
}
/// Adds pattern-matching-related methods to [NotificationsState].
extension NotificationsStatePatterns on NotificationsState {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _NotificationsState value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _NotificationsState() when $default != null:
return $default(_that);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _NotificationsState value) $default,){
final _that = this;
switch (_that) {
case _NotificationsState():
return $default(_that);}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _NotificationsState value)? $default,){
final _that = this;
switch (_that) {
case _NotificationsState() when $default != null:
return $default(_that);case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( IList<NotificationItem> queue)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _NotificationsState() when $default != null:
return $default(_that.queue);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( IList<NotificationItem> queue) $default,) {final _that = this;
switch (_that) {
case _NotificationsState():
return $default(_that.queue);}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( IList<NotificationItem> queue)? $default,) {final _that = this;
switch (_that) {
case _NotificationsState() when $default != null:
return $default(_that.queue);case _:
return null;
}
}
}
/// @nodoc
class _NotificationsState implements NotificationsState {
const _NotificationsState({required this.queue});
@override
final IList<NotificationItem> queue;
@override final IList<NotificationItem> queue;
/// Create a copy of NotificationsState
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$NotificationsStateCopyWith<_NotificationsState> get copyWith =>
__$NotificationsStateCopyWithImpl<_NotificationsState>(this, _$identity);
/// Create a copy of NotificationsState
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$NotificationsStateCopyWith<_NotificationsState> get copyWith => __$NotificationsStateCopyWithImpl<_NotificationsState>(this, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _NotificationsState &&
const DeepCollectionEquality().equals(other.queue, queue));
}
@override
int get hashCode =>
Object.hash(runtimeType, const DeepCollectionEquality().hash(queue));
@override
String toString() {
return 'NotificationsState(queue: $queue)';
}
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _NotificationsState&&const DeepCollectionEquality().equals(other.queue, queue));
}
@override
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(queue));
@override
String toString() {
return 'NotificationsState(queue: $queue)';
}
}
/// @nodoc
abstract mixin class _$NotificationsStateCopyWith<$Res>
implements $NotificationsStateCopyWith<$Res> {
factory _$NotificationsStateCopyWith(
_NotificationsState value, $Res Function(_NotificationsState) _then) =
__$NotificationsStateCopyWithImpl;
@override
@useResult
$Res call({IList<NotificationItem> queue});
}
abstract mixin class _$NotificationsStateCopyWith<$Res> implements $NotificationsStateCopyWith<$Res> {
factory _$NotificationsStateCopyWith(_NotificationsState value, $Res Function(_NotificationsState) _then) = __$NotificationsStateCopyWithImpl;
@override @useResult
$Res call({
IList<NotificationItem> queue
});
}
/// @nodoc
class __$NotificationsStateCopyWithImpl<$Res>
implements _$NotificationsStateCopyWith<$Res> {
@ -289,20 +507,16 @@ class __$NotificationsStateCopyWithImpl<$Res>
final _NotificationsState _self;
final $Res Function(_NotificationsState) _then;
/// Create a copy of NotificationsState
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$Res call({
Object? queue = null,
}) {
return _then(_NotificationsState(
queue: null == queue
? _self.queue
: queue // ignore: cast_nullable_to_non_nullable
as IList<NotificationItem>,
));
}
/// Create a copy of NotificationsState
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? queue = null,}) {
return _then(_NotificationsState(
queue: null == queue ? _self.queue : queue // ignore: cast_nullable_to_non_nullable
as IList<NotificationItem>,
));
}
}
// dart format on

View file

@ -7,8 +7,12 @@ import '../../theme/theme.dart';
import '../notifications.dart';
class NotificationsWidget extends StatelessWidget {
////////////////////////////////////////////////////////////////////////////
final Widget _child;
const NotificationsWidget({required Widget child, super.key})
: _child = child;
: _child = child;
////////////////////////////////////////////////////////////////////////////
// Public API
@ -18,43 +22,47 @@ class NotificationsWidget extends StatelessWidget {
final notificationsCubit = context.read<NotificationsCubit>();
return BlocListener<NotificationsCubit, NotificationsState>(
bloc: notificationsCubit,
listener: (context, state) {
if (state.queue.isNotEmpty) {
final queue = notificationsCubit.popAll();
for (final notificationItem in queue) {
switch (notificationItem.type) {
case NotificationType.info:
_info(
context: context,
text: notificationItem.text,
title: notificationItem.title);
case NotificationType.error:
_error(
context: context,
text: notificationItem.text,
title: notificationItem.title);
}
bloc: notificationsCubit,
listener: (context, state) {
if (state.queue.isNotEmpty) {
final queue = notificationsCubit.popAll();
for (final notificationItem in queue) {
switch (notificationItem.type) {
case NotificationType.info:
_info(
context: context,
text: notificationItem.text,
title: notificationItem.title,
);
case NotificationType.error:
_error(
context: context,
text: notificationItem.text,
title: notificationItem.title,
);
}
}
},
child: _child);
}
},
child: _child,
);
}
////////////////////////////////////////////////////////////////////////////
// Private Implementation
void _toast(
{required BuildContext context,
required String text,
required ScaleToastTheme toastTheme,
String? title}) {
void _toast({
required BuildContext context,
required String text,
required ScaleToastTheme toastTheme,
String? title,
}) {
toastification.show(
context: context,
title: title != null
? Text(title)
.copyWith(style: toastTheme.titleTextStyle)
.paddingLTRB(0, 0, 0, 8)
? Text(
title,
).copyWith(style: toastTheme.titleTextStyle).paddingLTRB(0, 0, 0, 8)
: null,
description: Text(text).copyWith(style: toastTheme.descriptionTextStyle),
icon: toastTheme.icon,
@ -69,25 +77,29 @@ class NotificationsWidget extends StatelessWidget {
);
}
void _info(
{required BuildContext context, required String text, String? title}) {
void _info({
required BuildContext context,
required String text,
String? title,
}) {
final theme = Theme.of(context);
final toastTheme =
theme.extension<ScaleTheme>()!.toastTheme(ScaleToastKind.info);
final toastTheme = theme.extension<ScaleTheme>()!.toastTheme(
ScaleToastKind.info,
);
_toast(context: context, text: text, toastTheme: toastTheme, title: title);
}
void _error(
{required BuildContext context, required String text, String? title}) {
void _error({
required BuildContext context,
required String text,
String? title,
}) {
final theme = Theme.of(context);
final toastTheme =
theme.extension<ScaleTheme>()!.toastTheme(ScaleToastKind.error);
final toastTheme = theme.extension<ScaleTheme>()!.toastTheme(
ScaleToastKind.error,
);
_toast(context: context, text: text, toastTheme: toastTheme, title: title);
}
////////////////////////////////////////////////////////////////////////////
final Widget _child;
}

View file

@ -14,8 +14,8 @@ import 'dart:core' as $core;
import 'package:fixnum/fixnum.dart' as $fixnum;
import 'package:protobuf/protobuf.dart' as $pb;
import 'package:veilid_support/proto/dht.pb.dart' as $1;
import 'veilid.deprecated.pb.dart' as $0;
import 'package:veilid_support/proto/dht.deprecated.pb.dart' as $1;
import 'package:veilid_support/proto/veilid.deprecated.pb.dart' as $0;
import 'veilidchat.deprecated.pbenum.dart';
export 'veilidchat.deprecated.pbenum.dart';

View file

@ -484,10 +484,10 @@ const Account$json = {
{'1': 'profile', '3': 1, '4': 1, '5': 11, '6': '.veilidchat.deprecated.Profile', '10': 'profile'},
{'1': 'invisible', '3': 2, '4': 1, '5': 8, '10': 'invisible'},
{'1': 'auto_away_timeout_min', '3': 3, '4': 1, '5': 13, '10': 'autoAwayTimeoutMin'},
{'1': 'contact_list', '3': 4, '4': 1, '5': 11, '6': '.dht.OwnedDHTRecordPointer', '10': 'contactList'},
{'1': 'contact_invitation_records', '3': 5, '4': 1, '5': 11, '6': '.dht.OwnedDHTRecordPointer', '10': 'contactInvitationRecords'},
{'1': 'chat_list', '3': 6, '4': 1, '5': 11, '6': '.dht.OwnedDHTRecordPointer', '10': 'chatList'},
{'1': 'group_chat_list', '3': 7, '4': 1, '5': 11, '6': '.dht.OwnedDHTRecordPointer', '10': 'groupChatList'},
{'1': 'contact_list', '3': 4, '4': 1, '5': 11, '6': '.dht.deprecated.OwnedDHTRecordPointer', '10': 'contactList'},
{'1': 'contact_invitation_records', '3': 5, '4': 1, '5': 11, '6': '.dht.deprecated.OwnedDHTRecordPointer', '10': 'contactInvitationRecords'},
{'1': 'chat_list', '3': 6, '4': 1, '5': 11, '6': '.dht.deprecated.OwnedDHTRecordPointer', '10': 'chatList'},
{'1': 'group_chat_list', '3': 7, '4': 1, '5': 11, '6': '.dht.deprecated.OwnedDHTRecordPointer', '10': 'groupChatList'},
{'1': 'free_message', '3': 8, '4': 1, '5': 9, '10': 'freeMessage'},
{'1': 'busy_message', '3': 9, '4': 1, '5': 9, '10': 'busyMessage'},
{'1': 'away_message', '3': 10, '4': 1, '5': 9, '10': 'awayMessage'},
@ -499,15 +499,16 @@ const Account$json = {
final $typed_data.Uint8List accountDescriptor = $convert.base64Decode(
'CgdBY2NvdW50EjgKB3Byb2ZpbGUYASABKAsyHi52ZWlsaWRjaGF0LmRlcHJlY2F0ZWQuUHJvZm'
'lsZVIHcHJvZmlsZRIcCglpbnZpc2libGUYAiABKAhSCWludmlzaWJsZRIxChVhdXRvX2F3YXlf'
'dGltZW91dF9taW4YAyABKA1SEmF1dG9Bd2F5VGltZW91dE1pbhI9Cgxjb250YWN0X2xpc3QYBC'
'ABKAsyGi5kaHQuT3duZWRESFRSZWNvcmRQb2ludGVyUgtjb250YWN0TGlzdBJYChpjb250YWN0'
'X2ludml0YXRpb25fcmVjb3JkcxgFIAEoCzIaLmRodC5Pd25lZERIVFJlY29yZFBvaW50ZXJSGG'
'NvbnRhY3RJbnZpdGF0aW9uUmVjb3JkcxI3CgljaGF0X2xpc3QYBiABKAsyGi5kaHQuT3duZWRE'
'SFRSZWNvcmRQb2ludGVyUghjaGF0TGlzdBJCCg9ncm91cF9jaGF0X2xpc3QYByABKAsyGi5kaH'
'QuT3duZWRESFRSZWNvcmRQb2ludGVyUg1ncm91cENoYXRMaXN0EiEKDGZyZWVfbWVzc2FnZRgI'
'IAEoCVILZnJlZU1lc3NhZ2USIQoMYnVzeV9tZXNzYWdlGAkgASgJUgtidXN5TWVzc2FnZRIhCg'
'xhd2F5X21lc3NhZ2UYCiABKAlSC2F3YXlNZXNzYWdlEicKD2F1dG9kZXRlY3RfYXdheRgLIAEo'
'CFIOYXV0b2RldGVjdEF3YXk=');
'dGltZW91dF9taW4YAyABKA1SEmF1dG9Bd2F5VGltZW91dE1pbhJICgxjb250YWN0X2xpc3QYBC'
'ABKAsyJS5kaHQuZGVwcmVjYXRlZC5Pd25lZERIVFJlY29yZFBvaW50ZXJSC2NvbnRhY3RMaXN0'
'EmMKGmNvbnRhY3RfaW52aXRhdGlvbl9yZWNvcmRzGAUgASgLMiUuZGh0LmRlcHJlY2F0ZWQuT3'
'duZWRESFRSZWNvcmRQb2ludGVyUhhjb250YWN0SW52aXRhdGlvblJlY29yZHMSQgoJY2hhdF9s'
'aXN0GAYgASgLMiUuZGh0LmRlcHJlY2F0ZWQuT3duZWRESFRSZWNvcmRQb2ludGVyUghjaGF0TG'
'lzdBJNCg9ncm91cF9jaGF0X2xpc3QYByABKAsyJS5kaHQuZGVwcmVjYXRlZC5Pd25lZERIVFJl'
'Y29yZFBvaW50ZXJSDWdyb3VwQ2hhdExpc3QSIQoMZnJlZV9tZXNzYWdlGAggASgJUgtmcmVlTW'
'Vzc2FnZRIhCgxidXN5X21lc3NhZ2UYCSABKAlSC2J1c3lNZXNzYWdlEiEKDGF3YXlfbWVzc2Fn'
'ZRgKIAEoCVILYXdheU1lc3NhZ2USJwoPYXV0b2RldGVjdF9hd2F5GAsgASgIUg5hdXRvZGV0ZW'
'N0QXdheQ==');
@$core.Deprecated('Use contactDescriptor instead')
const Contact$json = {
@ -639,7 +640,7 @@ final $typed_data.Uint8List signedContactResponseDescriptor = $convert.base64Dec
const ContactInvitationRecord$json = {
'1': 'ContactInvitationRecord',
'2': [
{'1': 'contact_request_inbox', '3': 1, '4': 1, '5': 11, '6': '.dht.OwnedDHTRecordPointer', '10': 'contactRequestInbox'},
{'1': 'contact_request_inbox', '3': 1, '4': 1, '5': 11, '6': '.dht.deprecated.OwnedDHTRecordPointer', '10': 'contactRequestInbox'},
{'1': 'writer_key', '3': 2, '4': 1, '5': 11, '6': '.veilid.deprecated.CryptoKey', '10': 'writerKey'},
{'1': 'writer_secret', '3': 3, '4': 1, '5': 11, '6': '.veilid.deprecated.CryptoKey', '10': 'writerSecret'},
{'1': 'local_conversation_record_key', '3': 4, '4': 1, '5': 11, '6': '.veilid.deprecated.TypedKey', '10': 'localConversationRecordKey'},
@ -652,13 +653,13 @@ const ContactInvitationRecord$json = {
/// Descriptor for `ContactInvitationRecord`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List contactInvitationRecordDescriptor = $convert.base64Decode(
'ChdDb250YWN0SW52aXRhdGlvblJlY29yZBJOChVjb250YWN0X3JlcXVlc3RfaW5ib3gYASABKA'
'syGi5kaHQuT3duZWRESFRSZWNvcmRQb2ludGVyUhNjb250YWN0UmVxdWVzdEluYm94EjsKCndy'
'aXRlcl9rZXkYAiABKAsyHC52ZWlsaWQuZGVwcmVjYXRlZC5DcnlwdG9LZXlSCXdyaXRlcktleR'
'JBCg13cml0ZXJfc2VjcmV0GAMgASgLMhwudmVpbGlkLmRlcHJlY2F0ZWQuQ3J5cHRvS2V5Ugx3'
'cml0ZXJTZWNyZXQSXgodbG9jYWxfY29udmVyc2F0aW9uX3JlY29yZF9rZXkYBCABKAsyGy52ZW'
'lsaWQuZGVwcmVjYXRlZC5UeXBlZEtleVIabG9jYWxDb252ZXJzYXRpb25SZWNvcmRLZXkSHgoK'
'ZXhwaXJhdGlvbhgFIAEoBFIKZXhwaXJhdGlvbhIeCgppbnZpdGF0aW9uGAYgASgMUgppbnZpdG'
'F0aW9uEhgKB21lc3NhZ2UYByABKAlSB21lc3NhZ2USHAoJcmVjaXBpZW50GAggASgJUglyZWNp'
'cGllbnQ=');
'ChdDb250YWN0SW52aXRhdGlvblJlY29yZBJZChVjb250YWN0X3JlcXVlc3RfaW5ib3gYASABKA'
'syJS5kaHQuZGVwcmVjYXRlZC5Pd25lZERIVFJlY29yZFBvaW50ZXJSE2NvbnRhY3RSZXF1ZXN0'
'SW5ib3gSOwoKd3JpdGVyX2tleRgCIAEoCzIcLnZlaWxpZC5kZXByZWNhdGVkLkNyeXB0b0tleV'
'IJd3JpdGVyS2V5EkEKDXdyaXRlcl9zZWNyZXQYAyABKAsyHC52ZWlsaWQuZGVwcmVjYXRlZC5D'
'cnlwdG9LZXlSDHdyaXRlclNlY3JldBJeCh1sb2NhbF9jb252ZXJzYXRpb25fcmVjb3JkX2tleR'
'gEIAEoCzIbLnZlaWxpZC5kZXByZWNhdGVkLlR5cGVkS2V5Uhpsb2NhbENvbnZlcnNhdGlvblJl'
'Y29yZEtleRIeCgpleHBpcmF0aW9uGAUgASgEUgpleHBpcmF0aW9uEh4KCmludml0YXRpb24YBi'
'ABKAxSCmludml0YXRpb24SGAoHbWVzc2FnZRgHIAEoCVIHbWVzc2FnZRIcCglyZWNpcGllbnQY'
'CCABKAlSCXJlY2lwaWVudA==');

View file

@ -9,7 +9,7 @@ syntax = "proto3";
package veilidchat.deprecated;
import "veilid.deprecated.proto";
import "dht.proto";
import "dht.deprecated.proto";
////////////////////////////////////////////////////////////////////////////////////
// Enumerations
@ -350,16 +350,16 @@ message Account {
uint32 auto_away_timeout_min = 3;
// The contacts DHTList for this account
// DHT Private
dht.OwnedDHTRecordPointer contact_list = 4;
dht.deprecated.OwnedDHTRecordPointer contact_list = 4;
// The ContactInvitationRecord DHTShortArray for this account
// DHT Private
dht.OwnedDHTRecordPointer contact_invitation_records = 5;
dht.deprecated.OwnedDHTRecordPointer contact_invitation_records = 5;
// The Chats DHTList for this account
// DHT Private
dht.OwnedDHTRecordPointer chat_list = 6;
dht.deprecated.OwnedDHTRecordPointer chat_list = 6;
// The GroupChats DHTList for this account
// DHT Private
dht.OwnedDHTRecordPointer group_chat_list = 7;
dht.deprecated.OwnedDHTRecordPointer group_chat_list = 7;
// Free message (max length 128)
string free_message = 8;
// Busy message (max length 128)
@ -466,7 +466,7 @@ message SignedContactResponse {
// Contact request record kept in Account DHTList to keep track of extant contact invitations
message ContactInvitationRecord {
// Contact request unicastinbox DHT record key (parent is accountkey)
dht.OwnedDHTRecordPointer contact_request_inbox = 1;
dht.deprecated.OwnedDHTRecordPointer contact_request_inbox = 1;
// Writer key sent to contact for the contact_request_inbox smpl inbox subkey
veilid.deprecated.CryptoKey writer_key = 2;
// Writer secret sent encrypted in the invitation

View file

@ -9,7 +9,6 @@ syntax = "proto3";
package veilidchat;
import "veilid.proto";
import "veilid.deprecated.proto";
import "dht.proto";
////////////////////////////////////////////////////////////////////////////////////

View file

@ -26,25 +26,35 @@ final _rootNavKey = GlobalKey<NavigatorState>(debugLabel: 'rootNavKey');
@freezed
sealed class RouterState with _$RouterState {
const factory RouterState({
required bool hasAnyAccount,
}) = _RouterState;
const factory RouterState({required bool hasAnyAccount}) = _RouterState;
factory RouterState.fromJson(dynamic json) =>
_$RouterStateFromJson(json as Map<String, dynamic>);
}
class RouterCubit extends Cubit<RouterState> {
////////////////
late final StreamSubscription<AccountRepositoryChange>
_accountRepositorySubscription;
GoRouter? _router;
RouterCubit(AccountRepository accountRepository)
: super(RouterState(
: super(
RouterState(
hasAnyAccount: accountRepository.getLocalAccounts().isNotEmpty,
)) {
),
) {
// Subscribe to repository streams
_accountRepositorySubscription = accountRepository.stream.listen((event) {
switch (event) {
case AccountRepositoryChange.localAccounts:
emit(state.copyWith(
hasAnyAccount: accountRepository.getLocalAccounts().isNotEmpty));
emit(
state.copyWith(
hasAnyAccount: accountRepository.getLocalAccounts().isNotEmpty,
),
);
case AccountRepositoryChange.userLogins:
case AccountRepositoryChange.activeLocalAccount:
}
@ -59,72 +69,73 @@ class RouterCubit extends Cubit<RouterState> {
/// Our application routes
List<RouteBase> get routes => [
ShellRoute(
builder: (context, state, child) => RouterShell(child: child),
routes: [
GoRoute(
path: '/',
builder: (context, state) => const HomeScreen(),
),
GoRoute(
path: '/edit_account',
redirect: (_, state) {
final extra = state.extra;
if (extra == null ||
extra is! List<Object> ||
extra[0] is! RecordKey) {
return '/';
}
return null;
},
builder: (context, state) {
final extra = state.extra! as List<Object>;
return EditAccountPage(
superIdentityRecordKey: extra[0] as RecordKey,
initialValue: extra[1] as AccountSpec,
accountRecord: extra[2] as OwnedDHTRecordPointer,
);
},
),
GoRoute(
path: '/new_account',
builder: (context, state) => const NewAccountPage(),
routes: [
GoRoute(
path: 'recovery_key',
redirect: (_, state) {
final extra = state.extra;
if (extra == null ||
extra is! List<Object> ||
extra[0] is! WritableSuperIdentity ||
extra[1] is! String ||
extra[2] is! bool) {
return '/';
}
return null;
},
builder: (context, state) {
final extra = state.extra! as List<Object>;
ShellRoute(
builder: (context, state, child) => RouterShell(child: child),
routes: [
GoRoute(path: '/', builder: (context, state) => const HomeScreen()),
GoRoute(
path: '/edit_account',
redirect: (_, state) {
final extra = state.extra;
if (extra == null ||
extra is! List<Object> ||
extra[0] is! RecordKey) {
return '/';
}
return null;
},
builder: (context, state) {
final extra = state.extra! as List<Object>;
return EditAccountPage(
superIdentityRecordKey: extra[0] as RecordKey,
initialValue: extra[1] as AccountSpec,
accountRecord: extra[2] as OwnedDHTRecordPointer,
);
},
),
GoRoute(
path: '/new_account',
builder: (context, state) => const NewAccountPage(),
routes: [
GoRoute(
path: 'recovery_key',
redirect: (_, state) {
final extra = state.extra;
if (extra == null ||
extra is! List<Object> ||
extra[0] is! WritableSuperIdentity ||
extra[1] is! String ||
extra[2] is! bool) {
return '/';
}
return null;
},
builder: (context, state) {
final extra = state.extra! as List<Object>;
return PopControl(
dismissible: false,
child: ShowRecoveryKeyPage(
writableSuperIdentity:
extra[0] as WritableSuperIdentity,
name: extra[1] as String,
isFirstAccount: extra[2] as bool));
}),
]),
GoRoute(
path: '/settings',
builder: (context, state) => const SettingsPage(),
),
GoRoute(
path: '/developer',
builder: (context, state) => const DeveloperPage(),
)
])
];
return PopControl(
dismissible: false,
child: ShowRecoveryKeyPage(
writableSuperIdentity: extra[0] as WritableSuperIdentity,
name: extra[1] as String,
isFirstAccount: extra[2] as bool,
),
);
},
),
],
),
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) {
@ -160,12 +171,6 @@ class RouterCubit extends Cubit<RouterState> {
redirect: redirect,
);
}
////////////////
late final StreamSubscription<AccountRepositoryChange>
_accountRepositorySubscription;
GoRouter? _router;
}
extension GoRouterExtension on GoRouter {

View file

@ -1,6 +1,5 @@
// dart format width=80
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// coverage:ignore-file
// 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
@ -15,159 +14,270 @@ T _$identity<T>(T value) => value;
/// @nodoc
mixin _$RouterState implements DiagnosticableTreeMixin {
bool get hasAnyAccount;
/// Create a copy of RouterState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$RouterStateCopyWith<RouterState> get copyWith =>
_$RouterStateCopyWithImpl<RouterState>(this as RouterState, _$identity);
bool get hasAnyAccount;
/// Create a copy of RouterState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$RouterStateCopyWith<RouterState> get copyWith => _$RouterStateCopyWithImpl<RouterState>(this as RouterState, _$identity);
/// Serializes this RouterState to a JSON map.
Map<String, dynamic> toJson();
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
properties
..add(DiagnosticsProperty('type', 'RouterState'))
..add(DiagnosticsProperty('hasAnyAccount', hasAnyAccount));
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
properties
..add(DiagnosticsProperty('type', 'RouterState'))
..add(DiagnosticsProperty('hasAnyAccount', hasAnyAccount));
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is RouterState &&
(identical(other.hasAnyAccount, hasAnyAccount) ||
other.hasAnyAccount == hasAnyAccount));
}
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is RouterState&&(identical(other.hasAnyAccount, hasAnyAccount) || other.hasAnyAccount == hasAnyAccount));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,hasAnyAccount);
@override
String toString({ DiagnosticLevel minLevel = DiagnosticLevel.info }) {
return 'RouterState(hasAnyAccount: $hasAnyAccount)';
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType, hasAnyAccount);
@override
String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) {
return 'RouterState(hasAnyAccount: $hasAnyAccount)';
}
}
/// @nodoc
abstract mixin class $RouterStateCopyWith<$Res> {
factory $RouterStateCopyWith(
RouterState value, $Res Function(RouterState) _then) =
_$RouterStateCopyWithImpl;
@useResult
$Res call({bool hasAnyAccount});
}
abstract mixin class $RouterStateCopyWith<$Res> {
factory $RouterStateCopyWith(RouterState value, $Res Function(RouterState) _then) = _$RouterStateCopyWithImpl;
@useResult
$Res call({
bool hasAnyAccount
});
}
/// @nodoc
class _$RouterStateCopyWithImpl<$Res> implements $RouterStateCopyWith<$Res> {
class _$RouterStateCopyWithImpl<$Res>
implements $RouterStateCopyWith<$Res> {
_$RouterStateCopyWithImpl(this._self, this._then);
final RouterState _self;
final $Res Function(RouterState) _then;
/// Create a copy of RouterState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? hasAnyAccount = null,
}) {
return _then(_self.copyWith(
hasAnyAccount: null == hasAnyAccount
? _self.hasAnyAccount
: hasAnyAccount // ignore: cast_nullable_to_non_nullable
as bool,
));
}
/// Create a copy of RouterState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? hasAnyAccount = null,}) {
return _then(_self.copyWith(
hasAnyAccount: null == hasAnyAccount ? _self.hasAnyAccount : hasAnyAccount // ignore: cast_nullable_to_non_nullable
as bool,
));
}
}
/// Adds pattern-matching-related methods to [RouterState].
extension RouterStatePatterns on RouterState {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _RouterState value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _RouterState() when $default != null:
return $default(_that);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _RouterState value) $default,){
final _that = this;
switch (_that) {
case _RouterState():
return $default(_that);}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _RouterState value)? $default,){
final _that = this;
switch (_that) {
case _RouterState() when $default != null:
return $default(_that);case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( bool hasAnyAccount)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _RouterState() when $default != null:
return $default(_that.hasAnyAccount);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( bool hasAnyAccount) $default,) {final _that = this;
switch (_that) {
case _RouterState():
return $default(_that.hasAnyAccount);}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( bool hasAnyAccount)? $default,) {final _that = this;
switch (_that) {
case _RouterState() when $default != null:
return $default(_that.hasAnyAccount);case _:
return null;
}
}
}
/// @nodoc
@JsonSerializable()
class _RouterState with DiagnosticableTreeMixin implements RouterState {
const _RouterState({required this.hasAnyAccount});
factory _RouterState.fromJson(Map<String, dynamic> json) =>
_$RouterStateFromJson(json);
factory _RouterState.fromJson(Map<String, dynamic> json) => _$RouterStateFromJson(json);
@override
final bool hasAnyAccount;
@override final bool hasAnyAccount;
/// Create a copy of RouterState
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$RouterStateCopyWith<_RouterState> get copyWith =>
__$RouterStateCopyWithImpl<_RouterState>(this, _$identity);
/// Create a copy of RouterState
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$RouterStateCopyWith<_RouterState> get copyWith => __$RouterStateCopyWithImpl<_RouterState>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$RouterStateToJson(
this,
);
}
@override
Map<String, dynamic> toJson() {
return _$RouterStateToJson(this, );
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
properties
..add(DiagnosticsProperty('type', 'RouterState'))
..add(DiagnosticsProperty('hasAnyAccount', hasAnyAccount));
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
properties
..add(DiagnosticsProperty('type', 'RouterState'))
..add(DiagnosticsProperty('hasAnyAccount', hasAnyAccount));
}
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _RouterState&&(identical(other.hasAnyAccount, hasAnyAccount) || other.hasAnyAccount == hasAnyAccount));
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _RouterState &&
(identical(other.hasAnyAccount, hasAnyAccount) ||
other.hasAnyAccount == hasAnyAccount));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,hasAnyAccount);
@override
String toString({ DiagnosticLevel minLevel = DiagnosticLevel.info }) {
return 'RouterState(hasAnyAccount: $hasAnyAccount)';
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType, hasAnyAccount);
@override
String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) {
return 'RouterState(hasAnyAccount: $hasAnyAccount)';
}
}
/// @nodoc
abstract mixin class _$RouterStateCopyWith<$Res>
implements $RouterStateCopyWith<$Res> {
factory _$RouterStateCopyWith(
_RouterState value, $Res Function(_RouterState) _then) =
__$RouterStateCopyWithImpl;
@override
@useResult
$Res call({bool hasAnyAccount});
}
abstract mixin class _$RouterStateCopyWith<$Res> implements $RouterStateCopyWith<$Res> {
factory _$RouterStateCopyWith(_RouterState value, $Res Function(_RouterState) _then) = __$RouterStateCopyWithImpl;
@override @useResult
$Res call({
bool hasAnyAccount
});
}
/// @nodoc
class __$RouterStateCopyWithImpl<$Res> implements _$RouterStateCopyWith<$Res> {
class __$RouterStateCopyWithImpl<$Res>
implements _$RouterStateCopyWith<$Res> {
__$RouterStateCopyWithImpl(this._self, this._then);
final _RouterState _self;
final $Res Function(_RouterState) _then;
/// Create a copy of RouterState
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$Res call({
Object? hasAnyAccount = null,
}) {
return _then(_RouterState(
hasAnyAccount: null == hasAnyAccount
? _self.hasAnyAccount
: hasAnyAccount // ignore: cast_nullable_to_non_nullable
as bool,
));
}
/// Create a copy of RouterState
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? hasAnyAccount = null,}) {
return _then(_RouterState(
hasAnyAccount: null == hasAnyAccount ? _self.hasAnyAccount : hasAnyAccount // ignore: cast_nullable_to_non_nullable
as bool,
));
}
}
// dart format on

View file

@ -6,11 +6,8 @@ part of 'router_cubit.dart';
// JsonSerializableGenerator
// **************************************************************************
_RouterState _$RouterStateFromJson(Map<String, dynamic> json) => _RouterState(
hasAnyAccount: json['has_any_account'] as bool,
);
_RouterState _$RouterStateFromJson(Map<String, dynamic> json) =>
_RouterState(hasAnyAccount: json['has_any_account'] as bool);
Map<String, dynamic> _$RouterStateToJson(_RouterState instance) =>
<String, dynamic>{
'has_any_account': instance.hasAnyAccount,
};
<String, dynamic>{'has_any_account': instance.hasAnyAccount};

View file

@ -7,18 +7,20 @@ import '../../settings/settings.dart';
import '../../theme/theme.dart';
class RouterShell extends StatelessWidget {
final Widget _child;
const RouterShell({required Widget child, super.key}) : _child = child;
@override
Widget build(BuildContext context) => PopControl(
dismissible: false,
child: AsyncBlocBuilder<PreferencesCubit, Preferences>(
builder: (context, state) => MediaQuery(
data: MediaQuery.of(context).copyWith(
textScaler:
TextScaler.linear(state.themePreference.displayScale)),
child: NotificationsWidget(
child: KeyboardShortcuts(child: _child)))));
final Widget _child;
dismissible: false,
child: AsyncBlocBuilder<PreferencesCubit, Preferences>(
builder: (context, state) => MediaQuery(
data: MediaQuery.of(context).copyWith(
textScaler: TextScaler.linear(state.themePreference.displayScale),
),
child: NotificationsWidget(child: KeyboardShortcuts(child: _child)),
),
),
);
}

View file

@ -11,6 +11,8 @@ part 'preferences.g.dart';
// interface and requires the identitySecretKey to be entered (pin/password/etc)
@freezed
sealed class LockPreference with _$LockPreference {
static const defaults = LockPreference();
const factory LockPreference({
@Default(0) int inactivityLockSecs,
@Default(false) bool lockWhenSwitching,
@ -19,8 +21,6 @@ sealed class LockPreference with _$LockPreference {
factory LockPreference.fromJson(dynamic json) =>
_$LockPreferenceFromJson(json as Map<String, dynamic>);
static const defaults = LockPreference();
}
// Theme supports multiple translations
@ -38,6 +38,8 @@ enum LanguagePreference {
// accounts imported/added and the app in general
@freezed
sealed class Preferences with _$Preferences {
static const defaults = Preferences();
const factory Preferences({
@Default(ThemePreferences.defaults) ThemePreferences themePreference,
@Default(LanguagePreference.defaults) LanguagePreference languagePreference,
@ -48,6 +50,4 @@ sealed class Preferences with _$Preferences {
factory Preferences.fromJson(dynamic json) =>
_$PreferencesFromJson(json as Map<String, dynamic>);
static const defaults = Preferences();
}

View file

@ -1,6 +1,5 @@
// dart format width=80
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// coverage:ignore-file
// 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
@ -15,57 +14,47 @@ T _$identity<T>(T value) => value;
/// @nodoc
mixin _$LockPreference {
int get inactivityLockSecs;
bool get lockWhenSwitching;
bool get lockWithSystemLock;
/// Create a copy of LockPreference
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$LockPreferenceCopyWith<LockPreference> get copyWith =>
_$LockPreferenceCopyWithImpl<LockPreference>(
this as LockPreference, _$identity);
int get inactivityLockSecs; bool get lockWhenSwitching; bool get lockWithSystemLock;
/// Create a copy of LockPreference
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$LockPreferenceCopyWith<LockPreference> get copyWith => _$LockPreferenceCopyWithImpl<LockPreference>(this as LockPreference, _$identity);
/// Serializes this LockPreference to a JSON map.
Map<String, dynamic> toJson();
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is LockPreference &&
(identical(other.inactivityLockSecs, inactivityLockSecs) ||
other.inactivityLockSecs == inactivityLockSecs) &&
(identical(other.lockWhenSwitching, lockWhenSwitching) ||
other.lockWhenSwitching == lockWhenSwitching) &&
(identical(other.lockWithSystemLock, lockWithSystemLock) ||
other.lockWithSystemLock == lockWithSystemLock));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(
runtimeType, inactivityLockSecs, lockWhenSwitching, lockWithSystemLock);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is LockPreference&&(identical(other.inactivityLockSecs, inactivityLockSecs) || other.inactivityLockSecs == inactivityLockSecs)&&(identical(other.lockWhenSwitching, lockWhenSwitching) || other.lockWhenSwitching == lockWhenSwitching)&&(identical(other.lockWithSystemLock, lockWithSystemLock) || other.lockWithSystemLock == lockWithSystemLock));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,inactivityLockSecs,lockWhenSwitching,lockWithSystemLock);
@override
String toString() {
return 'LockPreference(inactivityLockSecs: $inactivityLockSecs, lockWhenSwitching: $lockWhenSwitching, lockWithSystemLock: $lockWithSystemLock)';
}
@override
String toString() {
return 'LockPreference(inactivityLockSecs: $inactivityLockSecs, lockWhenSwitching: $lockWhenSwitching, lockWithSystemLock: $lockWithSystemLock)';
}
}
/// @nodoc
abstract mixin class $LockPreferenceCopyWith<$Res> {
factory $LockPreferenceCopyWith(
LockPreference value, $Res Function(LockPreference) _then) =
_$LockPreferenceCopyWithImpl;
@useResult
$Res call(
{int inactivityLockSecs,
bool lockWhenSwitching,
bool lockWithSystemLock});
}
abstract mixin class $LockPreferenceCopyWith<$Res> {
factory $LockPreferenceCopyWith(LockPreference value, $Res Function(LockPreference) _then) = _$LockPreferenceCopyWithImpl;
@useResult
$Res call({
int inactivityLockSecs, bool lockWhenSwitching, bool lockWithSystemLock
});
}
/// @nodoc
class _$LockPreferenceCopyWithImpl<$Res>
implements $LockPreferenceCopyWith<$Res> {
@ -74,105 +63,195 @@ class _$LockPreferenceCopyWithImpl<$Res>
final LockPreference _self;
final $Res Function(LockPreference) _then;
/// Create a copy of LockPreference
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? inactivityLockSecs = null,
Object? lockWhenSwitching = null,
Object? lockWithSystemLock = null,
}) {
return _then(_self.copyWith(
inactivityLockSecs: null == inactivityLockSecs
? _self.inactivityLockSecs
: inactivityLockSecs // ignore: cast_nullable_to_non_nullable
as int,
lockWhenSwitching: null == lockWhenSwitching
? _self.lockWhenSwitching
: lockWhenSwitching // ignore: cast_nullable_to_non_nullable
as bool,
lockWithSystemLock: null == lockWithSystemLock
? _self.lockWithSystemLock
: lockWithSystemLock // ignore: cast_nullable_to_non_nullable
as bool,
));
}
/// Create a copy of LockPreference
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? inactivityLockSecs = null,Object? lockWhenSwitching = null,Object? lockWithSystemLock = null,}) {
return _then(_self.copyWith(
inactivityLockSecs: null == inactivityLockSecs ? _self.inactivityLockSecs : inactivityLockSecs // ignore: cast_nullable_to_non_nullable
as int,lockWhenSwitching: null == lockWhenSwitching ? _self.lockWhenSwitching : lockWhenSwitching // ignore: cast_nullable_to_non_nullable
as bool,lockWithSystemLock: null == lockWithSystemLock ? _self.lockWithSystemLock : lockWithSystemLock // ignore: cast_nullable_to_non_nullable
as bool,
));
}
}
/// Adds pattern-matching-related methods to [LockPreference].
extension LockPreferencePatterns on LockPreference {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _LockPreference value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _LockPreference() when $default != null:
return $default(_that);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _LockPreference value) $default,){
final _that = this;
switch (_that) {
case _LockPreference():
return $default(_that);}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _LockPreference value)? $default,){
final _that = this;
switch (_that) {
case _LockPreference() when $default != null:
return $default(_that);case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( int inactivityLockSecs, bool lockWhenSwitching, bool lockWithSystemLock)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _LockPreference() when $default != null:
return $default(_that.inactivityLockSecs,_that.lockWhenSwitching,_that.lockWithSystemLock);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( int inactivityLockSecs, bool lockWhenSwitching, bool lockWithSystemLock) $default,) {final _that = this;
switch (_that) {
case _LockPreference():
return $default(_that.inactivityLockSecs,_that.lockWhenSwitching,_that.lockWithSystemLock);}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( int inactivityLockSecs, bool lockWhenSwitching, bool lockWithSystemLock)? $default,) {final _that = this;
switch (_that) {
case _LockPreference() when $default != null:
return $default(_that.inactivityLockSecs,_that.lockWhenSwitching,_that.lockWithSystemLock);case _:
return null;
}
}
}
/// @nodoc
@JsonSerializable()
class _LockPreference implements LockPreference {
const _LockPreference(
{this.inactivityLockSecs = 0,
this.lockWhenSwitching = false,
this.lockWithSystemLock = false});
factory _LockPreference.fromJson(Map<String, dynamic> json) =>
_$LockPreferenceFromJson(json);
const _LockPreference({this.inactivityLockSecs = 0, this.lockWhenSwitching = false, this.lockWithSystemLock = false});
factory _LockPreference.fromJson(Map<String, dynamic> json) => _$LockPreferenceFromJson(json);
@override
@JsonKey()
final int inactivityLockSecs;
@override
@JsonKey()
final bool lockWhenSwitching;
@override
@JsonKey()
final bool lockWithSystemLock;
@override@JsonKey() final int inactivityLockSecs;
@override@JsonKey() final bool lockWhenSwitching;
@override@JsonKey() final bool lockWithSystemLock;
/// Create a copy of LockPreference
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$LockPreferenceCopyWith<_LockPreference> get copyWith =>
__$LockPreferenceCopyWithImpl<_LockPreference>(this, _$identity);
/// Create a copy of LockPreference
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$LockPreferenceCopyWith<_LockPreference> get copyWith => __$LockPreferenceCopyWithImpl<_LockPreference>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$LockPreferenceToJson(
this,
);
}
@override
Map<String, dynamic> toJson() {
return _$LockPreferenceToJson(this, );
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _LockPreference &&
(identical(other.inactivityLockSecs, inactivityLockSecs) ||
other.inactivityLockSecs == inactivityLockSecs) &&
(identical(other.lockWhenSwitching, lockWhenSwitching) ||
other.lockWhenSwitching == lockWhenSwitching) &&
(identical(other.lockWithSystemLock, lockWithSystemLock) ||
other.lockWithSystemLock == lockWithSystemLock));
}
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _LockPreference&&(identical(other.inactivityLockSecs, inactivityLockSecs) || other.inactivityLockSecs == inactivityLockSecs)&&(identical(other.lockWhenSwitching, lockWhenSwitching) || other.lockWhenSwitching == lockWhenSwitching)&&(identical(other.lockWithSystemLock, lockWithSystemLock) || other.lockWithSystemLock == lockWithSystemLock));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,inactivityLockSecs,lockWhenSwitching,lockWithSystemLock);
@override
String toString() {
return 'LockPreference(inactivityLockSecs: $inactivityLockSecs, lockWhenSwitching: $lockWhenSwitching, lockWithSystemLock: $lockWithSystemLock)';
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(
runtimeType, inactivityLockSecs, lockWhenSwitching, lockWithSystemLock);
@override
String toString() {
return 'LockPreference(inactivityLockSecs: $inactivityLockSecs, lockWhenSwitching: $lockWhenSwitching, lockWithSystemLock: $lockWithSystemLock)';
}
}
/// @nodoc
abstract mixin class _$LockPreferenceCopyWith<$Res>
implements $LockPreferenceCopyWith<$Res> {
factory _$LockPreferenceCopyWith(
_LockPreference value, $Res Function(_LockPreference) _then) =
__$LockPreferenceCopyWithImpl;
@override
@useResult
$Res call(
{int inactivityLockSecs,
bool lockWhenSwitching,
bool lockWithSystemLock});
}
abstract mixin class _$LockPreferenceCopyWith<$Res> implements $LockPreferenceCopyWith<$Res> {
factory _$LockPreferenceCopyWith(_LockPreference value, $Res Function(_LockPreference) _then) = __$LockPreferenceCopyWithImpl;
@override @useResult
$Res call({
int inactivityLockSecs, bool lockWhenSwitching, bool lockWithSystemLock
});
}
/// @nodoc
class __$LockPreferenceCopyWithImpl<$Res>
implements _$LockPreferenceCopyWith<$Res> {
@ -181,317 +260,338 @@ class __$LockPreferenceCopyWithImpl<$Res>
final _LockPreference _self;
final $Res Function(_LockPreference) _then;
/// Create a copy of LockPreference
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$Res call({
Object? inactivityLockSecs = null,
Object? lockWhenSwitching = null,
Object? lockWithSystemLock = null,
}) {
return _then(_LockPreference(
inactivityLockSecs: null == inactivityLockSecs
? _self.inactivityLockSecs
: inactivityLockSecs // ignore: cast_nullable_to_non_nullable
as int,
lockWhenSwitching: null == lockWhenSwitching
? _self.lockWhenSwitching
: lockWhenSwitching // ignore: cast_nullable_to_non_nullable
as bool,
lockWithSystemLock: null == lockWithSystemLock
? _self.lockWithSystemLock
: lockWithSystemLock // ignore: cast_nullable_to_non_nullable
as bool,
));
}
/// Create a copy of LockPreference
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? inactivityLockSecs = null,Object? lockWhenSwitching = null,Object? lockWithSystemLock = null,}) {
return _then(_LockPreference(
inactivityLockSecs: null == inactivityLockSecs ? _self.inactivityLockSecs : inactivityLockSecs // ignore: cast_nullable_to_non_nullable
as int,lockWhenSwitching: null == lockWhenSwitching ? _self.lockWhenSwitching : lockWhenSwitching // ignore: cast_nullable_to_non_nullable
as bool,lockWithSystemLock: null == lockWithSystemLock ? _self.lockWithSystemLock : lockWithSystemLock // ignore: cast_nullable_to_non_nullable
as bool,
));
}
}
/// @nodoc
mixin _$Preferences {
ThemePreferences get themePreference;
LanguagePreference get languagePreference;
LockPreference get lockPreference;
NotificationsPreference get notificationsPreference;
/// Create a copy of Preferences
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$PreferencesCopyWith<Preferences> get copyWith =>
_$PreferencesCopyWithImpl<Preferences>(this as Preferences, _$identity);
ThemePreferences get themePreference; LanguagePreference get languagePreference; LockPreference get lockPreference; NotificationsPreference get notificationsPreference;
/// Create a copy of Preferences
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$PreferencesCopyWith<Preferences> get copyWith => _$PreferencesCopyWithImpl<Preferences>(this as Preferences, _$identity);
/// Serializes this Preferences to a JSON map.
Map<String, dynamic> toJson();
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is Preferences &&
(identical(other.themePreference, themePreference) ||
other.themePreference == themePreference) &&
(identical(other.languagePreference, languagePreference) ||
other.languagePreference == languagePreference) &&
(identical(other.lockPreference, lockPreference) ||
other.lockPreference == lockPreference) &&
(identical(
other.notificationsPreference, notificationsPreference) ||
other.notificationsPreference == notificationsPreference));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType, themePreference,
languagePreference, lockPreference, notificationsPreference);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is Preferences&&(identical(other.themePreference, themePreference) || other.themePreference == themePreference)&&(identical(other.languagePreference, languagePreference) || other.languagePreference == languagePreference)&&(identical(other.lockPreference, lockPreference) || other.lockPreference == lockPreference)&&(identical(other.notificationsPreference, notificationsPreference) || other.notificationsPreference == notificationsPreference));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,themePreference,languagePreference,lockPreference,notificationsPreference);
@override
String toString() {
return 'Preferences(themePreference: $themePreference, languagePreference: $languagePreference, lockPreference: $lockPreference, notificationsPreference: $notificationsPreference)';
}
@override
String toString() {
return 'Preferences(themePreference: $themePreference, languagePreference: $languagePreference, lockPreference: $lockPreference, notificationsPreference: $notificationsPreference)';
}
}
/// @nodoc
abstract mixin class $PreferencesCopyWith<$Res> {
factory $PreferencesCopyWith(
Preferences value, $Res Function(Preferences) _then) =
_$PreferencesCopyWithImpl;
@useResult
$Res call(
{ThemePreferences themePreference,
LanguagePreference languagePreference,
LockPreference lockPreference,
NotificationsPreference notificationsPreference});
abstract mixin class $PreferencesCopyWith<$Res> {
factory $PreferencesCopyWith(Preferences value, $Res Function(Preferences) _then) = _$PreferencesCopyWithImpl;
@useResult
$Res call({
ThemePreferences themePreference, LanguagePreference languagePreference, LockPreference lockPreference, NotificationsPreference notificationsPreference
});
$ThemePreferencesCopyWith<$Res> get themePreference;$LockPreferenceCopyWith<$Res> get lockPreference;$NotificationsPreferenceCopyWith<$Res> get notificationsPreference;
$ThemePreferencesCopyWith<$Res> get themePreference;
$LockPreferenceCopyWith<$Res> get lockPreference;
$NotificationsPreferenceCopyWith<$Res> get notificationsPreference;
}
/// @nodoc
class _$PreferencesCopyWithImpl<$Res> implements $PreferencesCopyWith<$Res> {
class _$PreferencesCopyWithImpl<$Res>
implements $PreferencesCopyWith<$Res> {
_$PreferencesCopyWithImpl(this._self, this._then);
final Preferences _self;
final $Res Function(Preferences) _then;
/// Create a copy of Preferences
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? themePreference = null,
Object? languagePreference = null,
Object? lockPreference = null,
Object? notificationsPreference = null,
}) {
return _then(_self.copyWith(
themePreference: null == themePreference
? _self.themePreference
: themePreference // ignore: cast_nullable_to_non_nullable
as ThemePreferences,
languagePreference: null == languagePreference
? _self.languagePreference
: languagePreference // ignore: cast_nullable_to_non_nullable
as LanguagePreference,
lockPreference: null == lockPreference
? _self.lockPreference
: lockPreference // ignore: cast_nullable_to_non_nullable
as LockPreference,
notificationsPreference: null == notificationsPreference
? _self.notificationsPreference
: notificationsPreference // ignore: cast_nullable_to_non_nullable
as NotificationsPreference,
));
}
/// Create a copy of Preferences
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? themePreference = null,Object? languagePreference = null,Object? lockPreference = null,Object? notificationsPreference = null,}) {
return _then(_self.copyWith(
themePreference: null == themePreference ? _self.themePreference : themePreference // ignore: cast_nullable_to_non_nullable
as ThemePreferences,languagePreference: null == languagePreference ? _self.languagePreference : languagePreference // ignore: cast_nullable_to_non_nullable
as LanguagePreference,lockPreference: null == lockPreference ? _self.lockPreference : lockPreference // ignore: cast_nullable_to_non_nullable
as LockPreference,notificationsPreference: null == notificationsPreference ? _self.notificationsPreference : notificationsPreference // ignore: cast_nullable_to_non_nullable
as NotificationsPreference,
));
}
/// Create a copy of Preferences
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$ThemePreferencesCopyWith<$Res> get themePreference {
return $ThemePreferencesCopyWith<$Res>(_self.themePreference, (value) {
return _then(_self.copyWith(themePreference: value));
});
}/// Create a copy of Preferences
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$LockPreferenceCopyWith<$Res> get lockPreference {
return $LockPreferenceCopyWith<$Res>(_self.lockPreference, (value) {
return _then(_self.copyWith(lockPreference: value));
});
}/// Create a copy of Preferences
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$NotificationsPreferenceCopyWith<$Res> get notificationsPreference {
return $NotificationsPreferenceCopyWith<$Res>(_self.notificationsPreference, (value) {
return _then(_self.copyWith(notificationsPreference: value));
});
}
}
/// Create a copy of Preferences
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$ThemePreferencesCopyWith<$Res> get themePreference {
return $ThemePreferencesCopyWith<$Res>(_self.themePreference, (value) {
return _then(_self.copyWith(themePreference: value));
});
}
/// Create a copy of Preferences
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$LockPreferenceCopyWith<$Res> get lockPreference {
return $LockPreferenceCopyWith<$Res>(_self.lockPreference, (value) {
return _then(_self.copyWith(lockPreference: value));
});
}
/// Adds pattern-matching-related methods to [Preferences].
extension PreferencesPatterns on Preferences {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _Preferences value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _Preferences() when $default != null:
return $default(_that);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _Preferences value) $default,){
final _that = this;
switch (_that) {
case _Preferences():
return $default(_that);}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _Preferences value)? $default,){
final _that = this;
switch (_that) {
case _Preferences() when $default != null:
return $default(_that);case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( ThemePreferences themePreference, LanguagePreference languagePreference, LockPreference lockPreference, NotificationsPreference notificationsPreference)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _Preferences() when $default != null:
return $default(_that.themePreference,_that.languagePreference,_that.lockPreference,_that.notificationsPreference);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( ThemePreferences themePreference, LanguagePreference languagePreference, LockPreference lockPreference, NotificationsPreference notificationsPreference) $default,) {final _that = this;
switch (_that) {
case _Preferences():
return $default(_that.themePreference,_that.languagePreference,_that.lockPreference,_that.notificationsPreference);}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( ThemePreferences themePreference, LanguagePreference languagePreference, LockPreference lockPreference, NotificationsPreference notificationsPreference)? $default,) {final _that = this;
switch (_that) {
case _Preferences() when $default != null:
return $default(_that.themePreference,_that.languagePreference,_that.lockPreference,_that.notificationsPreference);case _:
return null;
}
}
/// Create a copy of Preferences
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$NotificationsPreferenceCopyWith<$Res> get notificationsPreference {
return $NotificationsPreferenceCopyWith<$Res>(_self.notificationsPreference,
(value) {
return _then(_self.copyWith(notificationsPreference: value));
});
}
}
/// @nodoc
@JsonSerializable()
class _Preferences implements Preferences {
const _Preferences(
{this.themePreference = ThemePreferences.defaults,
this.languagePreference = LanguagePreference.defaults,
this.lockPreference = LockPreference.defaults,
this.notificationsPreference = NotificationsPreference.defaults});
factory _Preferences.fromJson(Map<String, dynamic> json) =>
_$PreferencesFromJson(json);
const _Preferences({this.themePreference = ThemePreferences.defaults, this.languagePreference = LanguagePreference.defaults, this.lockPreference = LockPreference.defaults, this.notificationsPreference = NotificationsPreference.defaults});
factory _Preferences.fromJson(Map<String, dynamic> json) => _$PreferencesFromJson(json);
@override
@JsonKey()
final ThemePreferences themePreference;
@override
@JsonKey()
final LanguagePreference languagePreference;
@override
@JsonKey()
final LockPreference lockPreference;
@override
@JsonKey()
final NotificationsPreference notificationsPreference;
@override@JsonKey() final ThemePreferences themePreference;
@override@JsonKey() final LanguagePreference languagePreference;
@override@JsonKey() final LockPreference lockPreference;
@override@JsonKey() final NotificationsPreference notificationsPreference;
/// Create a copy of Preferences
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$PreferencesCopyWith<_Preferences> get copyWith =>
__$PreferencesCopyWithImpl<_Preferences>(this, _$identity);
/// Create a copy of Preferences
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$PreferencesCopyWith<_Preferences> get copyWith => __$PreferencesCopyWithImpl<_Preferences>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$PreferencesToJson(
this,
);
}
@override
Map<String, dynamic> toJson() {
return _$PreferencesToJson(this, );
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _Preferences &&
(identical(other.themePreference, themePreference) ||
other.themePreference == themePreference) &&
(identical(other.languagePreference, languagePreference) ||
other.languagePreference == languagePreference) &&
(identical(other.lockPreference, lockPreference) ||
other.lockPreference == lockPreference) &&
(identical(
other.notificationsPreference, notificationsPreference) ||
other.notificationsPreference == notificationsPreference));
}
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _Preferences&&(identical(other.themePreference, themePreference) || other.themePreference == themePreference)&&(identical(other.languagePreference, languagePreference) || other.languagePreference == languagePreference)&&(identical(other.lockPreference, lockPreference) || other.lockPreference == lockPreference)&&(identical(other.notificationsPreference, notificationsPreference) || other.notificationsPreference == notificationsPreference));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,themePreference,languagePreference,lockPreference,notificationsPreference);
@override
String toString() {
return 'Preferences(themePreference: $themePreference, languagePreference: $languagePreference, lockPreference: $lockPreference, notificationsPreference: $notificationsPreference)';
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType, themePreference,
languagePreference, lockPreference, notificationsPreference);
@override
String toString() {
return 'Preferences(themePreference: $themePreference, languagePreference: $languagePreference, lockPreference: $lockPreference, notificationsPreference: $notificationsPreference)';
}
}
/// @nodoc
abstract mixin class _$PreferencesCopyWith<$Res>
implements $PreferencesCopyWith<$Res> {
factory _$PreferencesCopyWith(
_Preferences value, $Res Function(_Preferences) _then) =
__$PreferencesCopyWithImpl;
@override
@useResult
$Res call(
{ThemePreferences themePreference,
LanguagePreference languagePreference,
LockPreference lockPreference,
NotificationsPreference notificationsPreference});
abstract mixin class _$PreferencesCopyWith<$Res> implements $PreferencesCopyWith<$Res> {
factory _$PreferencesCopyWith(_Preferences value, $Res Function(_Preferences) _then) = __$PreferencesCopyWithImpl;
@override @useResult
$Res call({
ThemePreferences themePreference, LanguagePreference languagePreference, LockPreference lockPreference, NotificationsPreference notificationsPreference
});
@override $ThemePreferencesCopyWith<$Res> get themePreference;@override $LockPreferenceCopyWith<$Res> get lockPreference;@override $NotificationsPreferenceCopyWith<$Res> get notificationsPreference;
@override
$ThemePreferencesCopyWith<$Res> get themePreference;
@override
$LockPreferenceCopyWith<$Res> get lockPreference;
@override
$NotificationsPreferenceCopyWith<$Res> get notificationsPreference;
}
/// @nodoc
class __$PreferencesCopyWithImpl<$Res> implements _$PreferencesCopyWith<$Res> {
class __$PreferencesCopyWithImpl<$Res>
implements _$PreferencesCopyWith<$Res> {
__$PreferencesCopyWithImpl(this._self, this._then);
final _Preferences _self;
final $Res Function(_Preferences) _then;
/// Create a copy of Preferences
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$Res call({
Object? themePreference = null,
Object? languagePreference = null,
Object? lockPreference = null,
Object? notificationsPreference = null,
}) {
return _then(_Preferences(
themePreference: null == themePreference
? _self.themePreference
: themePreference // ignore: cast_nullable_to_non_nullable
as ThemePreferences,
languagePreference: null == languagePreference
? _self.languagePreference
: languagePreference // ignore: cast_nullable_to_non_nullable
as LanguagePreference,
lockPreference: null == lockPreference
? _self.lockPreference
: lockPreference // ignore: cast_nullable_to_non_nullable
as LockPreference,
notificationsPreference: null == notificationsPreference
? _self.notificationsPreference
: notificationsPreference // ignore: cast_nullable_to_non_nullable
as NotificationsPreference,
));
}
/// Create a copy of Preferences
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? themePreference = null,Object? languagePreference = null,Object? lockPreference = null,Object? notificationsPreference = null,}) {
return _then(_Preferences(
themePreference: null == themePreference ? _self.themePreference : themePreference // ignore: cast_nullable_to_non_nullable
as ThemePreferences,languagePreference: null == languagePreference ? _self.languagePreference : languagePreference // ignore: cast_nullable_to_non_nullable
as LanguagePreference,lockPreference: null == lockPreference ? _self.lockPreference : lockPreference // ignore: cast_nullable_to_non_nullable
as LockPreference,notificationsPreference: null == notificationsPreference ? _self.notificationsPreference : notificationsPreference // ignore: cast_nullable_to_non_nullable
as NotificationsPreference,
));
}
/// Create a copy of Preferences
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$ThemePreferencesCopyWith<$Res> get themePreference {
return $ThemePreferencesCopyWith<$Res>(_self.themePreference, (value) {
return _then(_self.copyWith(themePreference: value));
});
}
/// Create a copy of Preferences
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$LockPreferenceCopyWith<$Res> get lockPreference {
return $LockPreferenceCopyWith<$Res>(_self.lockPreference, (value) {
return _then(_self.copyWith(lockPreference: value));
});
}
/// Create a copy of Preferences
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$NotificationsPreferenceCopyWith<$Res> get notificationsPreference {
return $NotificationsPreferenceCopyWith<$Res>(_self.notificationsPreference,
(value) {
return _then(_self.copyWith(notificationsPreference: value));
});
}
/// Create a copy of Preferences
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$ThemePreferencesCopyWith<$Res> get themePreference {
return $ThemePreferencesCopyWith<$Res>(_self.themePreference, (value) {
return _then(_self.copyWith(themePreference: value));
});
}/// Create a copy of Preferences
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$LockPreferenceCopyWith<$Res> get lockPreference {
return $LockPreferenceCopyWith<$Res>(_self.lockPreference, (value) {
return _then(_self.copyWith(lockPreference: value));
});
}/// Create a copy of Preferences
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$NotificationsPreferenceCopyWith<$Res> get notificationsPreference {
return $NotificationsPreferenceCopyWith<$Res>(_self.notificationsPreference, (value) {
return _then(_self.copyWith(notificationsPreference: value));
});
}
}
// dart format on

View file

@ -21,19 +21,19 @@ Map<String, dynamic> _$LockPreferenceToJson(_LockPreference instance) =>
};
_Preferences _$PreferencesFromJson(Map<String, dynamic> json) => _Preferences(
themePreference: json['theme_preference'] == null
? ThemePreferences.defaults
: ThemePreferences.fromJson(json['theme_preference']),
languagePreference: json['language_preference'] == null
? LanguagePreference.defaults
: LanguagePreference.fromJson(json['language_preference']),
lockPreference: json['lock_preference'] == null
? LockPreference.defaults
: LockPreference.fromJson(json['lock_preference']),
notificationsPreference: json['notifications_preference'] == null
? NotificationsPreference.defaults
: NotificationsPreference.fromJson(json['notifications_preference']),
);
themePreference: json['theme_preference'] == null
? ThemePreferences.defaults
: ThemePreferences.fromJson(json['theme_preference']),
languagePreference: json['language_preference'] == null
? LanguagePreference.defaults
: LanguagePreference.fromJson(json['language_preference']),
lockPreference: json['lock_preference'] == null
? LockPreference.defaults
: LockPreference.fromJson(json['lock_preference']),
notificationsPreference: json['notifications_preference'] == null
? NotificationsPreference.defaults
: NotificationsPreference.fromJson(json['notifications_preference']),
);
Map<String, dynamic> _$PreferencesToJson(_Preferences instance) =>
<String, dynamic>{

View file

@ -6,31 +6,35 @@ import '../tools/tools.dart';
import 'models/models.dart';
class PreferencesRepository {
PreferencesRepository._();
late final SharedPreferencesValue<Preferences> _data;
Preferences get value => _data.requireValue;
Stream<Preferences> get stream => _data.stream;
//////////////////////////////////////////////////////////////
/// Singleton initialization
static PreferencesRepository instance = PreferencesRepository._();
static var instance = PreferencesRepository._();
PreferencesRepository._();
Preferences get value => _data.requireValue;
Stream<Preferences> get stream => _data.stream;
Future<void> init() async {
final sharedPreferences = await SharedPreferences.getInstance();
// Allow environment for debugging
// ignore: do_not_use_environment
const namespace = String.fromEnvironment('NAMESPACE');
_data = SharedPreferencesValue<Preferences>(
sharedPreferences: sharedPreferences,
keyName: namespace.isEmpty ? 'preferences' : 'preferences_$namespace',
valueFromJson: (obj) =>
obj != null ? Preferences.fromJson(obj) : Preferences.defaults,
valueToJson: (val) => val.toJson());
sharedPreferences: sharedPreferences,
keyName: namespace.isEmpty ? 'preferences' : 'preferences_$namespace',
valueFromJson: (obj) =>
obj != null ? Preferences.fromJson(obj) : Preferences.defaults,
valueToJson: (val) => val.toJson(),
);
await _data.get();
}
Future<void> set(Preferences value) => _data.set(value);
Future<Preferences> get() => _data.get();
}

View file

@ -45,7 +45,10 @@ enum _RadixBaseColor {
}
RadixColor _radixGraySteps(
Brightness brightness, bool alpha, _RadixBaseColor baseColor) {
Brightness brightness,
bool alpha,
_RadixBaseColor baseColor,
) {
switch (baseColor) {
case _RadixBaseColor.tomato:
case _RadixBaseColor.red:
@ -56,251 +59,278 @@ RadixColor _radixGraySteps(
case _RadixBaseColor.violet:
return alpha
? (brightness == Brightness.dark
? RadixColors.dark.mauveA
: RadixColors.mauveA)
? RadixColors.dark.mauveA
: RadixColors.mauveA)
: (brightness == Brightness.dark
? RadixColors.dark.mauve
: RadixColors.mauve);
? RadixColors.dark.mauve
: RadixColors.mauve);
case _RadixBaseColor.indigo:
case _RadixBaseColor.blue:
case _RadixBaseColor.sky:
case _RadixBaseColor.cyan:
return alpha
? (brightness == Brightness.dark
? RadixColors.dark.slateA
: RadixColors.slateA)
? RadixColors.dark.slateA
: RadixColors.slateA)
: (brightness == Brightness.dark
? RadixColors.dark.slate
: RadixColors.slate);
? RadixColors.dark.slate
: RadixColors.slate);
case _RadixBaseColor.teal:
case _RadixBaseColor.mint:
case _RadixBaseColor.green:
return alpha
? (brightness == Brightness.dark
? RadixColors.dark.sageA
: RadixColors.sageA)
? RadixColors.dark.sageA
: RadixColors.sageA)
: (brightness == Brightness.dark
? RadixColors.dark.sage
: RadixColors.sage);
? RadixColors.dark.sage
: RadixColors.sage);
case _RadixBaseColor.lime:
case _RadixBaseColor.grass:
return alpha
? (brightness == Brightness.dark
? RadixColors.dark.oliveA
: RadixColors.oliveA)
? RadixColors.dark.oliveA
: RadixColors.oliveA)
: (brightness == Brightness.dark
? RadixColors.dark.olive
: RadixColors.olive);
? RadixColors.dark.olive
: RadixColors.olive);
case _RadixBaseColor.yellow:
case _RadixBaseColor.amber:
case _RadixBaseColor.orange:
case _RadixBaseColor.brown:
return alpha
? (brightness == Brightness.dark
? RadixColors.dark.sandA
: RadixColors.sandA)
? RadixColors.dark.sandA
: RadixColors.sandA)
: (brightness == Brightness.dark
? RadixColors.dark.sand
: RadixColors.sand);
? RadixColors.dark.sand
: RadixColors.sand);
}
}
RadixColor _radixColorSteps(
Brightness brightness, bool alpha, _RadixBaseColor baseColor) {
Brightness brightness,
bool alpha,
_RadixBaseColor baseColor,
) {
switch (baseColor) {
case _RadixBaseColor.tomato:
return alpha
? (brightness == Brightness.dark
? RadixColors.dark.tomatoA
: RadixColors.tomatoA)
? RadixColors.dark.tomatoA
: RadixColors.tomatoA)
: (brightness == Brightness.dark
? RadixColors.dark.tomato
: RadixColors.tomato);
? RadixColors.dark.tomato
: RadixColors.tomato);
case _RadixBaseColor.red:
return alpha
? (brightness == Brightness.dark
? RadixColors.dark.redA
: RadixColors.redA)
? RadixColors.dark.redA
: RadixColors.redA)
: (brightness == Brightness.dark
? RadixColors.dark.red
: RadixColors.red);
? RadixColors.dark.red
: RadixColors.red);
case _RadixBaseColor.crimson:
return alpha
? (brightness == Brightness.dark
? RadixColors.dark.crimsonA
: RadixColors.crimsonA)
? RadixColors.dark.crimsonA
: RadixColors.crimsonA)
: (brightness == Brightness.dark
? RadixColors.dark.crimson
: RadixColors.crimson);
? RadixColors.dark.crimson
: RadixColors.crimson);
case _RadixBaseColor.pink:
return alpha
? (brightness == Brightness.dark
? RadixColors.dark.pinkA
: RadixColors.pinkA)
? RadixColors.dark.pinkA
: RadixColors.pinkA)
: (brightness == Brightness.dark
? RadixColors.dark.pink
: RadixColors.pink);
? RadixColors.dark.pink
: RadixColors.pink);
case _RadixBaseColor.plum:
return alpha
? (brightness == Brightness.dark
? RadixColors.dark.plumA
: RadixColors.plumA)
? RadixColors.dark.plumA
: RadixColors.plumA)
: (brightness == Brightness.dark
? RadixColors.dark.plum
: RadixColors.plum);
? RadixColors.dark.plum
: RadixColors.plum);
case _RadixBaseColor.purple:
return alpha
? (brightness == Brightness.dark
? RadixColors.dark.purpleA
: RadixColors.purpleA)
? RadixColors.dark.purpleA
: RadixColors.purpleA)
: (brightness == Brightness.dark
? RadixColors.dark.purple
: RadixColors.purple);
? RadixColors.dark.purple
: RadixColors.purple);
case _RadixBaseColor.violet:
return alpha
? (brightness == Brightness.dark
? RadixColors.dark.violetA
: RadixColors.violetA)
? RadixColors.dark.violetA
: RadixColors.violetA)
: (brightness == Brightness.dark
? RadixColors.dark.violet
: RadixColors.violet);
? RadixColors.dark.violet
: RadixColors.violet);
case _RadixBaseColor.indigo:
return alpha
? (brightness == Brightness.dark
? RadixColors.dark.indigoA
: RadixColors.indigoA)
? RadixColors.dark.indigoA
: RadixColors.indigoA)
: (brightness == Brightness.dark
? RadixColors.dark.indigo
: RadixColors.indigo);
? RadixColors.dark.indigo
: RadixColors.indigo);
case _RadixBaseColor.blue:
return alpha
? (brightness == Brightness.dark
? RadixColors.dark.blueA
: RadixColors.blueA)
? RadixColors.dark.blueA
: RadixColors.blueA)
: (brightness == Brightness.dark
? RadixColors.dark.blue
: RadixColors.blue);
? RadixColors.dark.blue
: RadixColors.blue);
case _RadixBaseColor.sky:
return alpha
? (brightness == Brightness.dark
? RadixColors.dark.skyA
: RadixColors.skyA)
? RadixColors.dark.skyA
: RadixColors.skyA)
: (brightness == Brightness.dark
? RadixColors.dark.sky
: RadixColors.sky);
? RadixColors.dark.sky
: RadixColors.sky);
case _RadixBaseColor.cyan:
return alpha
? (brightness == Brightness.dark
? RadixColors.dark.cyanA
: RadixColors.cyanA)
? RadixColors.dark.cyanA
: RadixColors.cyanA)
: (brightness == Brightness.dark
? RadixColors.dark.cyan
: RadixColors.cyan);
? RadixColors.dark.cyan
: RadixColors.cyan);
case _RadixBaseColor.teal:
return alpha
? (brightness == Brightness.dark
? RadixColors.dark.tealA
: RadixColors.tealA)
? RadixColors.dark.tealA
: RadixColors.tealA)
: (brightness == Brightness.dark
? RadixColors.dark.teal
: RadixColors.teal);
? RadixColors.dark.teal
: RadixColors.teal);
case _RadixBaseColor.mint:
return alpha
? (brightness == Brightness.dark
? RadixColors.dark.mintA
: RadixColors.mintA)
? RadixColors.dark.mintA
: RadixColors.mintA)
: (brightness == Brightness.dark
? RadixColors.dark.mint
: RadixColors.mint);
? RadixColors.dark.mint
: RadixColors.mint);
case _RadixBaseColor.green:
return alpha
? (brightness == Brightness.dark
? RadixColors.dark.greenA
: RadixColors.greenA)
? RadixColors.dark.greenA
: RadixColors.greenA)
: (brightness == Brightness.dark
? RadixColors.dark.green
: RadixColors.green);
? RadixColors.dark.green
: RadixColors.green);
case _RadixBaseColor.grass:
return alpha
? (brightness == Brightness.dark
? RadixColors.dark.grassA
: RadixColors.grassA)
? RadixColors.dark.grassA
: RadixColors.grassA)
: (brightness == Brightness.dark
? RadixColors.dark.grass
: RadixColors.grass);
? RadixColors.dark.grass
: RadixColors.grass);
case _RadixBaseColor.lime:
return alpha
? (brightness == Brightness.dark
? RadixColors.dark.limeA
: RadixColors.limeA)
? RadixColors.dark.limeA
: RadixColors.limeA)
: (brightness == Brightness.dark
? RadixColors.dark.lime
: RadixColors.lime);
? RadixColors.dark.lime
: RadixColors.lime);
case _RadixBaseColor.yellow:
return alpha
? (brightness == Brightness.dark
? RadixColors.dark.yellowA
: RadixColors.yellowA)
? RadixColors.dark.yellowA
: RadixColors.yellowA)
: (brightness == Brightness.dark
? RadixColors.dark.yellow
: RadixColors.yellow);
? RadixColors.dark.yellow
: RadixColors.yellow);
case _RadixBaseColor.amber:
return alpha
? (brightness == Brightness.dark
? RadixColors.dark.amberA
: RadixColors.amberA)
? RadixColors.dark.amberA
: RadixColors.amberA)
: (brightness == Brightness.dark
? RadixColors.dark.amber
: RadixColors.amber);
? RadixColors.dark.amber
: RadixColors.amber);
case _RadixBaseColor.orange:
return alpha
? (brightness == Brightness.dark
? RadixColors.dark.orangeA
: RadixColors.orangeA)
? RadixColors.dark.orangeA
: RadixColors.orangeA)
: (brightness == Brightness.dark
? RadixColors.dark.orange
: RadixColors.orange);
? RadixColors.dark.orange
: RadixColors.orange);
case _RadixBaseColor.brown:
return alpha
? (brightness == Brightness.dark
? RadixColors.dark.brownA
: RadixColors.brownA)
? RadixColors.dark.brownA
: RadixColors.brownA)
: (brightness == Brightness.dark
? RadixColors.dark.brown
: RadixColors.brown);
? RadixColors.dark.brown
: RadixColors.brown);
}
}
extension ToScaleColor on RadixColor {
ScaleColor toScale(RadixScaleExtra scaleExtra) => ScaleColor(
appBackground: step1,
subtleBackground: step2,
elementBackground: step3,
hoverElementBackground: step4,
activeElementBackground: step5,
subtleBorder: step6,
border: step7,
hoverBorder: step8,
primary: step9,
hoverPrimary: step10,
subtleText: step11,
appText: step12,
primaryText: scaleExtra.foregroundText,
borderText: step12,
dialogBorder: step9,
dialogBorderText: scaleExtra.foregroundText,
calloutBackground: step9,
calloutText: scaleExtra.foregroundText,
);
appBackground: step1,
subtleBackground: step2,
elementBackground: step3,
hoverElementBackground: step4,
activeElementBackground: step5,
subtleBorder: step6,
border: step7,
hoverBorder: step8,
primary: step9,
hoverPrimary: step10,
subtleText: step11,
appText: step12,
primaryText: scaleExtra.foregroundText,
borderText: step12,
dialogBorder: step9,
dialogBorderText: scaleExtra.foregroundText,
calloutBackground: step9,
calloutText: scaleExtra.foregroundText,
);
}
class RadixScaleExtra {
RadixScaleExtra({required this.foregroundText});
final Color foregroundText;
RadixScaleExtra({required this.foregroundText});
}
class RadixScheme {
final RadixColor primaryScale;
final RadixScaleExtra primaryExtra;
final RadixColor primaryAlphaScale;
final RadixScaleExtra primaryAlphaExtra;
final RadixColor secondaryScale;
final RadixScaleExtra secondaryExtra;
final RadixColor tertiaryScale;
final RadixScaleExtra tertiaryExtra;
final RadixColor grayScale;
final RadixScaleExtra grayExtra;
final RadixColor errorScale;
final RadixScaleExtra errorExtra;
const RadixScheme({
required this.primaryScale,
required this.primaryExtra,
@ -316,27 +346,14 @@ class RadixScheme {
required this.errorExtra,
});
final RadixColor primaryScale;
final RadixScaleExtra primaryExtra;
final RadixColor primaryAlphaScale;
final RadixScaleExtra primaryAlphaExtra;
final RadixColor secondaryScale;
final RadixScaleExtra secondaryExtra;
final RadixColor tertiaryScale;
final RadixScaleExtra tertiaryExtra;
final RadixColor grayScale;
final RadixScaleExtra grayExtra;
final RadixColor errorScale;
final RadixScaleExtra errorExtra;
ScaleScheme toScale() => ScaleScheme(
primaryScale: primaryScale.toScale(primaryExtra),
primaryAlphaScale: primaryAlphaScale.toScale(primaryAlphaExtra),
secondaryScale: secondaryScale.toScale(secondaryExtra),
tertiaryScale: tertiaryScale.toScale(tertiaryExtra),
grayScale: grayScale.toScale(grayExtra),
errorScale: errorScale.toScale(errorExtra),
);
primaryScale: primaryScale.toScale(primaryExtra),
primaryAlphaScale: primaryAlphaScale.toScale(primaryAlphaExtra),
secondaryScale: secondaryScale.toScale(secondaryExtra),
tertiaryScale: tertiaryScale.toScale(tertiaryExtra),
grayScale: grayScale.toScale(grayExtra),
errorScale: errorScale.toScale(errorExtra),
);
}
RadixScheme _radixScheme(Brightness brightness, RadixThemeColor themeColor) {
@ -347,14 +364,23 @@ RadixScheme _radixScheme(Brightness brightness, RadixThemeColor themeColor) {
radixScheme = RadixScheme(
primaryScale: _radixColorSteps(brightness, false, _RadixBaseColor.red),
primaryExtra: RadixScaleExtra(foregroundText: Colors.white),
primaryAlphaScale:
_radixColorSteps(brightness, true, _RadixBaseColor.red),
primaryAlphaScale: _radixColorSteps(
brightness,
true,
_RadixBaseColor.red,
),
primaryAlphaExtra: RadixScaleExtra(foregroundText: Colors.white),
secondaryScale:
_radixColorSteps(brightness, false, _RadixBaseColor.violet),
secondaryScale: _radixColorSteps(
brightness,
false,
_RadixBaseColor.violet,
),
secondaryExtra: RadixScaleExtra(foregroundText: Colors.white),
tertiaryScale:
_radixColorSteps(brightness, false, _RadixBaseColor.tomato),
tertiaryScale: _radixColorSteps(
brightness,
false,
_RadixBaseColor.tomato,
),
tertiaryExtra: RadixScaleExtra(foregroundText: Colors.white),
grayScale: _radixGraySteps(brightness, false, _RadixBaseColor.red),
grayExtra: RadixScaleExtra(foregroundText: Colors.white),
@ -365,162 +391,258 @@ RadixScheme _radixScheme(Brightness brightness, RadixThemeColor themeColor) {
// crimson + pink + purple
case RadixThemeColor.babydoll:
radixScheme = RadixScheme(
primaryScale:
_radixColorSteps(brightness, false, _RadixBaseColor.crimson),
primaryExtra: RadixScaleExtra(foregroundText: Colors.white),
primaryAlphaScale:
_radixColorSteps(brightness, true, _RadixBaseColor.crimson),
primaryAlphaExtra: RadixScaleExtra(foregroundText: Colors.white),
secondaryScale:
_radixColorSteps(brightness, false, _RadixBaseColor.pink),
secondaryExtra: RadixScaleExtra(foregroundText: Colors.white),
tertiaryScale:
_radixColorSteps(brightness, false, _RadixBaseColor.purple),
tertiaryExtra: RadixScaleExtra(foregroundText: Colors.white),
grayScale:
_radixGraySteps(brightness, false, _RadixBaseColor.crimson),
grayExtra: RadixScaleExtra(foregroundText: Colors.white),
errorScale:
_radixColorSteps(brightness, false, _RadixBaseColor.orange),
errorExtra: RadixScaleExtra(foregroundText: Colors.white));
primaryScale: _radixColorSteps(
brightness,
false,
_RadixBaseColor.crimson,
),
primaryExtra: RadixScaleExtra(foregroundText: Colors.white),
primaryAlphaScale: _radixColorSteps(
brightness,
true,
_RadixBaseColor.crimson,
),
primaryAlphaExtra: RadixScaleExtra(foregroundText: Colors.white),
secondaryScale: _radixColorSteps(
brightness,
false,
_RadixBaseColor.pink,
),
secondaryExtra: RadixScaleExtra(foregroundText: Colors.white),
tertiaryScale: _radixColorSteps(
brightness,
false,
_RadixBaseColor.purple,
),
tertiaryExtra: RadixScaleExtra(foregroundText: Colors.white),
grayScale: _radixGraySteps(brightness, false, _RadixBaseColor.crimson),
grayExtra: RadixScaleExtra(foregroundText: Colors.white),
errorScale: _radixColorSteps(brightness, false, _RadixBaseColor.orange),
errorExtra: RadixScaleExtra(foregroundText: Colors.white),
);
// pink + cyan + plum
case RadixThemeColor.vapor:
radixScheme = RadixScheme(
primaryScale:
_radixColorSteps(brightness, false, _RadixBaseColor.pink),
primaryExtra: RadixScaleExtra(foregroundText: Colors.white),
primaryAlphaScale:
_radixColorSteps(brightness, true, _RadixBaseColor.pink),
primaryAlphaExtra: RadixScaleExtra(foregroundText: Colors.white),
secondaryScale:
_radixColorSteps(brightness, false, _RadixBaseColor.cyan),
secondaryExtra: RadixScaleExtra(foregroundText: Colors.white),
tertiaryScale:
_radixColorSteps(brightness, false, _RadixBaseColor.plum),
tertiaryExtra: RadixScaleExtra(foregroundText: Colors.white),
grayScale: _radixGraySteps(brightness, false, _RadixBaseColor.pink),
grayExtra: RadixScaleExtra(foregroundText: Colors.white),
errorScale: _radixColorSteps(brightness, false, _RadixBaseColor.red),
errorExtra: RadixScaleExtra(foregroundText: Colors.white));
primaryScale: _radixColorSteps(brightness, false, _RadixBaseColor.pink),
primaryExtra: RadixScaleExtra(foregroundText: Colors.white),
primaryAlphaScale: _radixColorSteps(
brightness,
true,
_RadixBaseColor.pink,
),
primaryAlphaExtra: RadixScaleExtra(foregroundText: Colors.white),
secondaryScale: _radixColorSteps(
brightness,
false,
_RadixBaseColor.cyan,
),
secondaryExtra: RadixScaleExtra(foregroundText: Colors.white),
tertiaryScale: _radixColorSteps(
brightness,
false,
_RadixBaseColor.plum,
),
tertiaryExtra: RadixScaleExtra(foregroundText: Colors.white),
grayScale: _radixGraySteps(brightness, false, _RadixBaseColor.pink),
grayExtra: RadixScaleExtra(foregroundText: Colors.white),
errorScale: _radixColorSteps(brightness, false, _RadixBaseColor.red),
errorExtra: RadixScaleExtra(foregroundText: Colors.white),
);
// yellow + amber + orange
case RadixThemeColor.gold:
radixScheme = RadixScheme(
primaryScale:
_radixColorSteps(brightness, false, _RadixBaseColor.yellow),
primaryExtra: RadixScaleExtra(foregroundText: Colors.black),
primaryAlphaScale:
_radixColorSteps(brightness, true, _RadixBaseColor.yellow),
primaryAlphaExtra: RadixScaleExtra(foregroundText: Colors.black),
secondaryScale:
_radixColorSteps(brightness, false, _RadixBaseColor.amber),
secondaryExtra: RadixScaleExtra(foregroundText: Colors.black),
tertiaryScale:
_radixColorSteps(brightness, false, _RadixBaseColor.orange),
tertiaryExtra: RadixScaleExtra(foregroundText: Colors.black),
grayScale: _radixGraySteps(brightness, false, _RadixBaseColor.yellow),
grayExtra: RadixScaleExtra(foregroundText: Colors.white),
errorScale: _radixColorSteps(brightness, false, _RadixBaseColor.red),
errorExtra: RadixScaleExtra(foregroundText: Colors.white));
primaryScale: _radixColorSteps(
brightness,
false,
_RadixBaseColor.yellow,
),
primaryExtra: RadixScaleExtra(foregroundText: Colors.black),
primaryAlphaScale: _radixColorSteps(
brightness,
true,
_RadixBaseColor.yellow,
),
primaryAlphaExtra: RadixScaleExtra(foregroundText: Colors.black),
secondaryScale: _radixColorSteps(
brightness,
false,
_RadixBaseColor.amber,
),
secondaryExtra: RadixScaleExtra(foregroundText: Colors.black),
tertiaryScale: _radixColorSteps(
brightness,
false,
_RadixBaseColor.orange,
),
tertiaryExtra: RadixScaleExtra(foregroundText: Colors.black),
grayScale: _radixGraySteps(brightness, false, _RadixBaseColor.yellow),
grayExtra: RadixScaleExtra(foregroundText: Colors.white),
errorScale: _radixColorSteps(brightness, false, _RadixBaseColor.red),
errorExtra: RadixScaleExtra(foregroundText: Colors.white),
);
// grass + orange + brown
case RadixThemeColor.garden:
radixScheme = RadixScheme(
primaryScale:
_radixColorSteps(brightness, false, _RadixBaseColor.grass),
primaryExtra: RadixScaleExtra(foregroundText: Colors.white),
primaryAlphaScale:
_radixColorSteps(brightness, true, _RadixBaseColor.grass),
primaryAlphaExtra: RadixScaleExtra(foregroundText: Colors.white),
secondaryScale:
_radixColorSteps(brightness, false, _RadixBaseColor.orange),
secondaryExtra: RadixScaleExtra(foregroundText: Colors.white),
tertiaryScale:
_radixColorSteps(brightness, false, _RadixBaseColor.brown),
tertiaryExtra: RadixScaleExtra(foregroundText: Colors.white),
grayScale: _radixGraySteps(brightness, false, _RadixBaseColor.grass),
grayExtra: RadixScaleExtra(foregroundText: Colors.white),
errorScale:
_radixColorSteps(brightness, false, _RadixBaseColor.tomato),
errorExtra: RadixScaleExtra(foregroundText: Colors.white));
primaryScale: _radixColorSteps(
brightness,
false,
_RadixBaseColor.grass,
),
primaryExtra: RadixScaleExtra(foregroundText: Colors.white),
primaryAlphaScale: _radixColorSteps(
brightness,
true,
_RadixBaseColor.grass,
),
primaryAlphaExtra: RadixScaleExtra(foregroundText: Colors.white),
secondaryScale: _radixColorSteps(
brightness,
false,
_RadixBaseColor.orange,
),
secondaryExtra: RadixScaleExtra(foregroundText: Colors.white),
tertiaryScale: _radixColorSteps(
brightness,
false,
_RadixBaseColor.brown,
),
tertiaryExtra: RadixScaleExtra(foregroundText: Colors.white),
grayScale: _radixGraySteps(brightness, false, _RadixBaseColor.grass),
grayExtra: RadixScaleExtra(foregroundText: Colors.white),
errorScale: _radixColorSteps(brightness, false, _RadixBaseColor.tomato),
errorExtra: RadixScaleExtra(foregroundText: Colors.white),
);
// green + brown + amber
case RadixThemeColor.forest:
radixScheme = RadixScheme(
primaryScale:
_radixColorSteps(brightness, false, _RadixBaseColor.green),
primaryExtra: RadixScaleExtra(foregroundText: Colors.white),
primaryAlphaScale:
_radixColorSteps(brightness, true, _RadixBaseColor.green),
primaryAlphaExtra: RadixScaleExtra(foregroundText: Colors.white),
secondaryScale:
_radixColorSteps(brightness, false, _RadixBaseColor.brown),
secondaryExtra: RadixScaleExtra(foregroundText: Colors.white),
tertiaryScale:
_radixColorSteps(brightness, false, _RadixBaseColor.amber),
tertiaryExtra: RadixScaleExtra(foregroundText: Colors.black),
grayScale: _radixGraySteps(brightness, false, _RadixBaseColor.green),
grayExtra: RadixScaleExtra(foregroundText: Colors.white),
errorScale:
_radixColorSteps(brightness, false, _RadixBaseColor.tomato),
errorExtra: RadixScaleExtra(foregroundText: Colors.white));
primaryScale: _radixColorSteps(
brightness,
false,
_RadixBaseColor.green,
),
primaryExtra: RadixScaleExtra(foregroundText: Colors.white),
primaryAlphaScale: _radixColorSteps(
brightness,
true,
_RadixBaseColor.green,
),
primaryAlphaExtra: RadixScaleExtra(foregroundText: Colors.white),
secondaryScale: _radixColorSteps(
brightness,
false,
_RadixBaseColor.brown,
),
secondaryExtra: RadixScaleExtra(foregroundText: Colors.white),
tertiaryScale: _radixColorSteps(
brightness,
false,
_RadixBaseColor.amber,
),
tertiaryExtra: RadixScaleExtra(foregroundText: Colors.black),
grayScale: _radixGraySteps(brightness, false, _RadixBaseColor.green),
grayExtra: RadixScaleExtra(foregroundText: Colors.white),
errorScale: _radixColorSteps(brightness, false, _RadixBaseColor.tomato),
errorExtra: RadixScaleExtra(foregroundText: Colors.white),
);
// sky + teal + violet
case RadixThemeColor.arctic:
radixScheme = RadixScheme(
primaryScale:
_radixColorSteps(brightness, false, _RadixBaseColor.sky),
primaryExtra: RadixScaleExtra(foregroundText: Colors.black),
primaryAlphaScale:
_radixColorSteps(brightness, true, _RadixBaseColor.sky),
primaryAlphaExtra: RadixScaleExtra(foregroundText: Colors.black),
secondaryScale:
_radixColorSteps(brightness, false, _RadixBaseColor.teal),
secondaryExtra: RadixScaleExtra(foregroundText: Colors.white),
tertiaryScale:
_radixColorSteps(brightness, false, _RadixBaseColor.violet),
tertiaryExtra: RadixScaleExtra(foregroundText: Colors.white),
grayScale: _radixGraySteps(brightness, false, _RadixBaseColor.sky),
grayExtra: RadixScaleExtra(foregroundText: Colors.white),
errorScale:
_radixColorSteps(brightness, false, _RadixBaseColor.crimson),
errorExtra: RadixScaleExtra(foregroundText: Colors.white));
primaryScale: _radixColorSteps(brightness, false, _RadixBaseColor.sky),
primaryExtra: RadixScaleExtra(foregroundText: Colors.black),
primaryAlphaScale: _radixColorSteps(
brightness,
true,
_RadixBaseColor.sky,
),
primaryAlphaExtra: RadixScaleExtra(foregroundText: Colors.black),
secondaryScale: _radixColorSteps(
brightness,
false,
_RadixBaseColor.teal,
),
secondaryExtra: RadixScaleExtra(foregroundText: Colors.white),
tertiaryScale: _radixColorSteps(
brightness,
false,
_RadixBaseColor.violet,
),
tertiaryExtra: RadixScaleExtra(foregroundText: Colors.white),
grayScale: _radixGraySteps(brightness, false, _RadixBaseColor.sky),
grayExtra: RadixScaleExtra(foregroundText: Colors.white),
errorScale: _radixColorSteps(
brightness,
false,
_RadixBaseColor.crimson,
),
errorExtra: RadixScaleExtra(foregroundText: Colors.white),
);
// blue + indigo + mint
case RadixThemeColor.lapis:
radixScheme = RadixScheme(
primaryScale: _radixColorSteps(brightness, false, _RadixBaseColor.blue),
primaryExtra: RadixScaleExtra(foregroundText: Colors.white),
primaryAlphaScale:
_radixColorSteps(brightness, true, _RadixBaseColor.blue),
primaryAlphaScale: _radixColorSteps(
brightness,
true,
_RadixBaseColor.blue,
),
primaryAlphaExtra: RadixScaleExtra(foregroundText: Colors.white),
secondaryScale:
_radixColorSteps(brightness, false, _RadixBaseColor.indigo),
secondaryScale: _radixColorSteps(
brightness,
false,
_RadixBaseColor.indigo,
),
secondaryExtra: RadixScaleExtra(foregroundText: Colors.white),
tertiaryScale:
_radixColorSteps(brightness, false, _RadixBaseColor.mint),
tertiaryScale: _radixColorSteps(
brightness,
false,
_RadixBaseColor.mint,
),
tertiaryExtra: RadixScaleExtra(foregroundText: Colors.black),
grayScale: _radixGraySteps(brightness, false, _RadixBaseColor.blue),
grayExtra: RadixScaleExtra(foregroundText: Colors.white),
errorScale:
_radixColorSteps(brightness, false, _RadixBaseColor.crimson),
errorScale: _radixColorSteps(
brightness,
false,
_RadixBaseColor.crimson,
),
errorExtra: RadixScaleExtra(foregroundText: Colors.white),
);
// violet + purple + indigo
case RadixThemeColor.eggplant:
radixScheme = RadixScheme(
primaryScale:
_radixColorSteps(brightness, false, _RadixBaseColor.violet),
primaryScale: _radixColorSteps(
brightness,
false,
_RadixBaseColor.violet,
),
primaryExtra: RadixScaleExtra(foregroundText: Colors.white),
primaryAlphaScale:
_radixColorSteps(brightness, true, _RadixBaseColor.violet),
primaryAlphaScale: _radixColorSteps(
brightness,
true,
_RadixBaseColor.violet,
),
primaryAlphaExtra: RadixScaleExtra(foregroundText: Colors.white),
secondaryScale:
_radixColorSteps(brightness, false, _RadixBaseColor.purple),
secondaryScale: _radixColorSteps(
brightness,
false,
_RadixBaseColor.purple,
),
secondaryExtra: RadixScaleExtra(foregroundText: Colors.white),
tertiaryScale:
_radixColorSteps(brightness, false, _RadixBaseColor.indigo),
tertiaryScale: _radixColorSteps(
brightness,
false,
_RadixBaseColor.indigo,
),
tertiaryExtra: RadixScaleExtra(foregroundText: Colors.white),
grayScale: _radixGraySteps(brightness, false, _RadixBaseColor.violet),
grayExtra: RadixScaleExtra(foregroundText: Colors.white),
errorScale:
_radixColorSteps(brightness, false, _RadixBaseColor.crimson),
errorScale: _radixColorSteps(
brightness,
false,
_RadixBaseColor.crimson,
),
errorExtra: RadixScaleExtra(foregroundText: Colors.white),
);
// lime + yellow + orange
@ -528,14 +650,23 @@ RadixScheme _radixScheme(Brightness brightness, RadixThemeColor themeColor) {
radixScheme = RadixScheme(
primaryScale: _radixColorSteps(brightness, false, _RadixBaseColor.lime),
primaryExtra: RadixScaleExtra(foregroundText: Colors.black),
primaryAlphaScale:
_radixColorSteps(brightness, true, _RadixBaseColor.lime),
primaryAlphaScale: _radixColorSteps(
brightness,
true,
_RadixBaseColor.lime,
),
primaryAlphaExtra: RadixScaleExtra(foregroundText: Colors.black),
secondaryScale:
_radixColorSteps(brightness, false, _RadixBaseColor.yellow),
secondaryScale: _radixColorSteps(
brightness,
false,
_RadixBaseColor.yellow,
),
secondaryExtra: RadixScaleExtra(foregroundText: Colors.black),
tertiaryScale:
_radixColorSteps(brightness, false, _RadixBaseColor.orange),
tertiaryScale: _radixColorSteps(
brightness,
false,
_RadixBaseColor.orange,
),
tertiaryExtra: RadixScaleExtra(foregroundText: Colors.white),
grayScale: _radixGraySteps(brightness, false, _RadixBaseColor.lime),
grayExtra: RadixScaleExtra(foregroundText: Colors.white),
@ -545,17 +676,29 @@ RadixScheme _radixScheme(Brightness brightness, RadixThemeColor themeColor) {
// mauve + slate + sage
case RadixThemeColor.grim:
radixScheme = RadixScheme(
primaryScale:
_radixGraySteps(brightness, false, _RadixBaseColor.tomato),
primaryScale: _radixGraySteps(
brightness,
false,
_RadixBaseColor.tomato,
),
primaryExtra: RadixScaleExtra(foregroundText: Colors.white),
primaryAlphaScale:
_radixGraySteps(brightness, true, _RadixBaseColor.tomato),
primaryAlphaScale: _radixGraySteps(
brightness,
true,
_RadixBaseColor.tomato,
),
primaryAlphaExtra: RadixScaleExtra(foregroundText: Colors.white),
secondaryScale:
_radixColorSteps(brightness, false, _RadixBaseColor.purple),
secondaryScale: _radixColorSteps(
brightness,
false,
_RadixBaseColor.purple,
),
secondaryExtra: RadixScaleExtra(foregroundText: Colors.white),
tertiaryScale:
_radixColorSteps(brightness, false, _RadixBaseColor.brown),
tertiaryScale: _radixColorSteps(
brightness,
false,
_RadixBaseColor.brown,
),
tertiaryExtra: RadixScaleExtra(foregroundText: Colors.white),
grayScale: brightness == Brightness.dark
? RadixColors.dark.gray
@ -642,7 +785,10 @@ ThemeData radixGenerator(Brightness brightness, RadixThemeColor themeColor) {
);
final scaleTheme = ScaleTheme(
textTheme: textTheme, scheme: scaleScheme, config: scaleConfig);
textTheme: textTheme,
scheme: scaleScheme,
config: scaleConfig,
);
final themeData = scaleTheme.toThemeData(brightness);

View file

@ -3,15 +3,17 @@ import 'package:flutter/material.dart';
import 'scale_theme.dart';
class ScaleAppBarTheme {
final TextStyle textStyle;
final Color iconColor;
final Color backgroundColor;
ScaleAppBarTheme({
required this.textStyle,
required this.iconColor,
required this.backgroundColor,
});
final TextStyle textStyle;
final Color iconColor;
final Color backgroundColor;
}
extension ScaleAppBarThemeExt on ScaleTheme {

View file

@ -5,71 +5,6 @@ import 'package:flutter_chat_core/flutter_chat_core.dart' as fccore;
import 'scale_theme.dart';
class ScaleChatTheme {
ScaleChatTheme({
// Default chat theme
required this.chatTheme,
// Customization fields (from v1 of flutter chat ui)
required this.attachmentButtonIcon,
// required this.attachmentButtonMargin,
required this.backgroundColor,
required this.bubbleBorderSide,
// required this.dateDividerMargin,
// required this.chatContentMargin,
required this.dateDividerTextStyle,
// required this.deliveredIcon,
// required this.documentIcon,
// required this.emptyChatPlaceholderTextStyle,
// required this.errorColor,
// required this.errorIcon,
required this.inputBackgroundColor,
// required this.inputSurfaceTintColor,
// required this.inputElevation,
required this.inputBorderRadius,
// required this.inputMargin,
required this.inputPadding,
required this.inputTextColor,
required this.inputTextStyle,
required this.messageBorderRadius,
required this.messageInsetsHorizontal,
required this.messageInsetsVertical,
// required this.messageMaxWidth,
required this.primaryColor,
required this.receivedEmojiMessageTextStyle,
required this.receivedMessageBodyTextStyle,
// required this.receivedMessageCaptionTextStyle,
// required this.receivedMessageDocumentIconColor,
// required this.receivedMessageLinkDescriptionTextStyle,
// required this.receivedMessageLinkTitleTextStyle,
required this.secondaryColor,
// required this.seenIcon,
required this.sendButtonIcon,
// required this.sendButtonMargin,
// required this.sendingIcon,
required this.onlyEmojiFontSize,
required this.timeStyle,
required this.sentMessageBodyTextStyle,
// required this.sentMessageCaptionTextStyle,
// required this.sentMessageDocumentIconColor,
// required this.sentMessageLinkDescriptionTextStyle,
// required this.sentMessageLinkTitleTextStyle,
// required this.statusIconPadding,
// required this.userAvatarImageBackgroundColor,
// required this.userAvatarNameColors,
// required this.userAvatarTextStyle,
// required this.userNameTextStyle,
// required this.bubbleMargin,
required this.inputContainerDecoration,
// required this.inputTextCursorColor,
// required this.receivedMessageBodyBoldTextStyle,
// required this.receivedMessageBodyCodeTextStyle,
// required this.receivedMessageBodyLinkTextStyle,
// required this.sentMessageBodyBoldTextStyle,
// required this.sentMessageBodyCodeTextStyle,
// required this.sentMessageBodyLinkTextStyle,
// required this.highlightMessageColor,
});
final fccore.ChatTheme chatTheme;
/// Icon for select attachment button.
@ -81,9 +16,6 @@ class ScaleChatTheme {
/// Used as a background color of a chat widget.
final Color backgroundColor;
// Margin around the message bubble.
// final EdgeInsetsGeometry? bubbleMargin;
/// Border for chat bubbles
final BorderSide bubbleBorderSide;
@ -227,6 +159,71 @@ class ScaleChatTheme {
/// of sent messages.
final TextStyle sentMessageBodyTextStyle;
ScaleChatTheme({
// Default chat theme
required this.chatTheme,
// Customization fields (from v1 of flutter chat ui)
required this.attachmentButtonIcon,
// required this.attachmentButtonMargin,
required this.backgroundColor,
required this.bubbleBorderSide,
// required this.dateDividerMargin,
// required this.chatContentMargin,
required this.dateDividerTextStyle,
// required this.deliveredIcon,
// required this.documentIcon,
// required this.emptyChatPlaceholderTextStyle,
// required this.errorColor,
// required this.errorIcon,
required this.inputBackgroundColor,
// required this.inputSurfaceTintColor,
// required this.inputElevation,
required this.inputBorderRadius,
// required this.inputMargin,
required this.inputPadding,
required this.inputTextColor,
required this.inputTextStyle,
required this.messageBorderRadius,
required this.messageInsetsHorizontal,
required this.messageInsetsVertical,
// required this.messageMaxWidth,
required this.primaryColor,
required this.receivedEmojiMessageTextStyle,
required this.receivedMessageBodyTextStyle,
// required this.receivedMessageCaptionTextStyle,
// required this.receivedMessageDocumentIconColor,
// required this.receivedMessageLinkDescriptionTextStyle,
// required this.receivedMessageLinkTitleTextStyle,
required this.secondaryColor,
// required this.seenIcon,
required this.sendButtonIcon,
// required this.sendButtonMargin,
// required this.sendingIcon,
required this.onlyEmojiFontSize,
required this.timeStyle,
required this.sentMessageBodyTextStyle,
// required this.sentMessageCaptionTextStyle,
// required this.sentMessageDocumentIconColor,
// required this.sentMessageLinkDescriptionTextStyle,
// required this.sentMessageLinkTitleTextStyle,
// required this.statusIconPadding,
// required this.userAvatarImageBackgroundColor,
// required this.userAvatarNameColors,
// required this.userAvatarTextStyle,
// required this.userNameTextStyle,
// required this.bubbleMargin,
required this.inputContainerDecoration,
// required this.inputTextCursorColor,
// required this.receivedMessageBodyBoldTextStyle,
// required this.receivedMessageBodyCodeTextStyle,
// required this.receivedMessageBodyLinkTextStyle,
// required this.sentMessageBodyBoldTextStyle,
// required this.sentMessageBodyCodeTextStyle,
// required this.sentMessageBodyLinkTextStyle,
// required this.highlightMessageColor,
});
/// Caption text style used for displaying secondary info (e.g. file size) on
/// different types of sent messages.
// final TextStyle sentMessageCaptionTextStyle;
@ -277,96 +274,99 @@ extension ScaleChatThemeExt on ScaleTheme {
: scheme.secondaryScale.calloutBackground;
final colors = fccore.ChatColors(
// Primary color, often used for sent messages and accents.
primary: config.preferBorders
? scheme.primaryScale.calloutText
: scheme.primaryScale.calloutBackground,
// Color for text and icons displayed on top of [primary].
onPrimary: scheme.primaryScale.primaryText,
// The main background color of the chat screen.
surface:
scheme.grayScale.appBackground.withAlpha(config.wallpaperAlpha),
// Primary color, often used for sent messages and accents.
primary: config.preferBorders
? scheme.primaryScale.calloutText
: scheme.primaryScale.calloutBackground,
// Color for text and icons displayed on top of [primary].
onPrimary: scheme.primaryScale.primaryText,
// The main background color of the chat screen.
surface: scheme.grayScale.appBackground.withAlpha(config.wallpaperAlpha),
// Color for text and icons displayed on top of [surface].
onSurface: scheme.primaryScale.appText,
// Color for text and icons displayed on top of [surface].
onSurface: scheme.primaryScale.appText,
// Background color for elements like received messages.
surfaceContainer: surfaceContainer,
// Background color for elements like received messages.
surfaceContainer: surfaceContainer,
// A slightly lighter/darker variant of [surfaceContainer].
surfaceContainerLow: surfaceContainer.darken(25),
// A slightly lighter/darker variant of [surfaceContainer].
surfaceContainerLow: surfaceContainer.darken(25),
// A slightly lighter/darker variant of [surfaceContainer].
surfaceContainerHigh: surfaceContainer.lighten(25));
// A slightly lighter/darker variant of [surfaceContainer].
surfaceContainerHigh: surfaceContainer.lighten(25),
);
final chatTheme = fccore.ChatTheme(
colors: colors,
typography: typography,
shape:
BorderRadius.all(Radius.circular(config.borderRadiusScale * 12)));
colors: colors,
typography: typography,
shape: BorderRadius.all(Radius.circular(config.borderRadiusScale * 12)),
);
return ScaleChatTheme(
chatTheme: chatTheme,
primaryColor: config.preferBorders
? scheme.primaryScale.calloutText
: scheme.primaryScale.calloutBackground,
secondaryColor: config.preferBorders
? scheme.secondaryScale.calloutText
: scheme.secondaryScale.calloutBackground,
backgroundColor:
scheme.grayScale.appBackground.withAlpha(config.wallpaperAlpha),
messageBorderRadius: config.borderRadiusScale * 12,
bubbleBorderSide: config.preferBorders
? BorderSide(
color: scheme.primaryScale.calloutBackground,
width: 2,
chatTheme: chatTheme,
primaryColor: config.preferBorders
? scheme.primaryScale.calloutText
: scheme.primaryScale.calloutBackground,
secondaryColor: config.preferBorders
? scheme.secondaryScale.calloutText
: scheme.secondaryScale.calloutBackground,
backgroundColor: scheme.grayScale.appBackground.withAlpha(
config.wallpaperAlpha,
),
messageBorderRadius: config.borderRadiusScale * 12,
bubbleBorderSide: config.preferBorders
? BorderSide(color: scheme.primaryScale.calloutBackground, width: 2)
: BorderSide(width: 2, color: Colors.black.withAlpha(96)),
sendButtonIcon: Image.asset(
'assets/icon-send.png',
color: config.preferBorders
? scheme.primaryScale.border
: scheme.primaryScale.borderText,
package: 'flutter_chat_ui',
),
inputBackgroundColor: Colors.blue,
inputBorderRadius: BorderRadius.zero,
inputTextStyle: textTheme.bodyLarge!,
inputContainerDecoration: BoxDecoration(
border: config.preferBorders
? Border(
top: BorderSide(color: scheme.primaryScale.border, width: 2),
)
: BorderSide(width: 2, color: Colors.black.withAlpha(96)),
sendButtonIcon: Image.asset(
'assets/icon-send.png',
color: config.preferBorders
? scheme.primaryScale.border
: scheme.primaryScale.borderText,
package: 'flutter_chat_ui',
),
inputBackgroundColor: Colors.blue,
inputBorderRadius: BorderRadius.zero,
inputTextStyle: textTheme.bodyLarge!,
inputContainerDecoration: BoxDecoration(
border: config.preferBorders
? Border(
top:
BorderSide(color: scheme.primaryScale.border, width: 2))
: null,
color: config.preferBorders
? scheme.primaryScale.elementBackground
: scheme.primaryScale.border),
inputPadding: const EdgeInsets.all(6),
inputTextColor: !config.preferBorders
? scheme.primaryScale.appText
: null,
color: config.preferBorders
? scheme.primaryScale.elementBackground
: scheme.primaryScale.border,
messageInsetsHorizontal: 12,
messageInsetsVertical: 8,
attachmentButtonIcon: const Icon(Icons.attach_file),
sentMessageBodyTextStyle: textTheme.bodyLarge!.copyWith(
color: config.preferBorders
? scheme.primaryScale.calloutBackground
: scheme.primaryScale.calloutText,
),
onlyEmojiFontSize: 64,
timeStyle: textTheme.bodySmall!.copyWith(fontSize: 9).copyWith(
),
inputPadding: const EdgeInsets.all(6),
inputTextColor: !config.preferBorders
? scheme.primaryScale.appText
: scheme.primaryScale.border,
messageInsetsHorizontal: 12,
messageInsetsVertical: 8,
attachmentButtonIcon: const Icon(Icons.attach_file),
sentMessageBodyTextStyle: textTheme.bodyLarge!.copyWith(
color: config.preferBorders
? scheme.primaryScale.calloutBackground
: scheme.primaryScale.calloutText,
),
onlyEmojiFontSize: 64,
timeStyle: textTheme.bodySmall!
.copyWith(fontSize: 9)
.copyWith(
color: config.preferBorders || config.useVisualIndicators
? scheme.primaryScale.calloutBackground
: scheme.primaryScale.borderText),
receivedMessageBodyTextStyle: textTheme.bodyLarge!.copyWith(
color: config.preferBorders
? scheme.secondaryScale.calloutBackground
: scheme.secondaryScale.calloutText,
),
receivedEmojiMessageTextStyle: const TextStyle(
color: Colors.white,
fontSize: 64,
),
dateDividerTextStyle: textTheme.labelSmall!);
: scheme.primaryScale.borderText,
),
receivedMessageBodyTextStyle: textTheme.bodyLarge!.copyWith(
color: config.preferBorders
? scheme.secondaryScale.calloutBackground
: scheme.secondaryScale.calloutText,
),
receivedEmojiMessageTextStyle: const TextStyle(
color: Colors.white,
fontSize: 64,
),
dateDividerTextStyle: textTheme.labelSmall!,
);
}
}

View file

@ -1,6 +1,42 @@
import 'dart:ui';
class ScaleColor {
Color appBackground;
Color subtleBackground;
Color elementBackground;
Color hoverElementBackground;
Color activeElementBackground;
Color subtleBorder;
Color border;
Color hoverBorder;
Color primary;
Color hoverPrimary;
Color subtleText;
Color appText;
Color primaryText;
Color borderText;
Color dialogBorder;
Color dialogBorderText;
Color calloutBackground;
Color calloutText;
ScaleColor({
required this.appBackground,
required this.subtleBackground,
@ -22,25 +58,6 @@ class ScaleColor {
required this.calloutText,
});
Color appBackground;
Color subtleBackground;
Color elementBackground;
Color hoverElementBackground;
Color activeElementBackground;
Color subtleBorder;
Color border;
Color hoverBorder;
Color primary;
Color hoverPrimary;
Color subtleText;
Color appText;
Color primaryText;
Color borderText;
Color dialogBorder;
Color dialogBorderText;
Color calloutBackground;
Color calloutText;
ScaleColor copyWith({
Color? appBackground,
Color? subtleBackground,
@ -60,70 +77,74 @@ class ScaleColor {
Color? dialogBorderText,
Color? calloutBackground,
Color? calloutText,
}) =>
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,
primary: primary ?? this.primary,
hoverPrimary: hoverPrimary ?? this.hoverPrimary,
subtleText: subtleText ?? this.subtleText,
appText: appText ?? this.appText,
primaryText: primaryText ?? this.primaryText,
borderText: borderText ?? this.borderText,
dialogBorder: dialogBorder ?? this.dialogBorder,
dialogBorderText: dialogBorderText ?? this.dialogBorderText,
calloutBackground: calloutBackground ?? this.calloutBackground,
calloutText: calloutText ?? this.calloutText);
}) => 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,
primary: primary ?? this.primary,
hoverPrimary: hoverPrimary ?? this.hoverPrimary,
subtleText: subtleText ?? this.subtleText,
appText: appText ?? this.appText,
primaryText: primaryText ?? this.primaryText,
borderText: borderText ?? this.borderText,
dialogBorder: dialogBorder ?? this.dialogBorder,
dialogBorderText: dialogBorderText ?? this.dialogBorderText,
calloutBackground: calloutBackground ?? this.calloutBackground,
calloutText: calloutText ?? this.calloutText,
);
// Use static method
// 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),
primary: Color.lerp(a.primary, b.primary, t) ?? const Color(0x00000000),
hoverPrimary: Color.lerp(a.hoverPrimary, b.hoverPrimary, t) ??
const Color(0x00000000),
subtleText: Color.lerp(a.subtleText, b.subtleText, t) ??
const Color(0x00000000),
appText: Color.lerp(a.appText, b.appText, t) ?? const Color(0x00000000),
primaryText: Color.lerp(a.primaryText, b.primaryText, t) ??
const Color(0x00000000),
borderText: Color.lerp(a.borderText, b.borderText, t) ??
const Color(0x00000000),
dialogBorder: Color.lerp(a.dialogBorder, b.dialogBorder, t) ??
const Color(0x00000000),
dialogBorderText:
Color.lerp(a.dialogBorderText, b.dialogBorderText, t) ??
const Color(0x00000000),
calloutBackground:
Color.lerp(a.calloutBackground, b.calloutBackground, t) ??
const Color(0x00000000),
calloutText: Color.lerp(a.calloutText, b.calloutText, t) ??
const Color(0x00000000),
);
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),
primary: Color.lerp(a.primary, b.primary, t) ?? const Color(0x00000000),
hoverPrimary:
Color.lerp(a.hoverPrimary, b.hoverPrimary, t) ??
const Color(0x00000000),
subtleText:
Color.lerp(a.subtleText, b.subtleText, t) ?? const Color(0x00000000),
appText: Color.lerp(a.appText, b.appText, t) ?? const Color(0x00000000),
primaryText:
Color.lerp(a.primaryText, b.primaryText, t) ?? const Color(0x00000000),
borderText:
Color.lerp(a.borderText, b.borderText, t) ?? const Color(0x00000000),
dialogBorder:
Color.lerp(a.dialogBorder, b.dialogBorder, t) ??
const Color(0x00000000),
dialogBorderText:
Color.lerp(a.dialogBorderText, b.dialogBorderText, t) ??
const Color(0x00000000),
calloutBackground:
Color.lerp(a.calloutBackground, b.calloutBackground, t) ??
const Color(0x00000000),
calloutText:
Color.lerp(a.calloutText, b.calloutText, t) ?? const Color(0x00000000),
);
}

View file

@ -4,6 +4,20 @@ import 'package:flutter/material.dart';
import 'scale_theme.dart';
class ScaleCustomDropdownTheme {
final CustomDropdownDecoration decoration;
final EdgeInsets closedHeaderPadding;
final EdgeInsets expandedHeaderPadding;
final EdgeInsets itemsListPadding;
final EdgeInsets listItemPadding;
final CustomDropdownDisabledDecoration disabledDecoration;
final TextStyle textStyle;
ScaleCustomDropdownTheme({
required this.decoration,
required this.closedHeaderPadding,
@ -13,14 +27,6 @@ class ScaleCustomDropdownTheme {
required this.disabledDecoration,
required this.textStyle,
});
final CustomDropdownDecoration decoration;
final EdgeInsets closedHeaderPadding;
final EdgeInsets expandedHeaderPadding;
final EdgeInsets itemsListPadding;
final EdgeInsets listItemPadding;
final CustomDropdownDisabledDecoration disabledDecoration;
final TextStyle textStyle;
}
extension ScaleCustomDropdownThemeExt on ScaleTheme {
@ -39,9 +45,11 @@ extension ScaleCustomDropdownThemeExt on ScaleTheme {
// final largeTextStyle = textTheme.labelMedium!.copyWith(color: textColor);
// final smallTextStyle = textTheme.labelSmall!.copyWith(color: textColor);
final border = Border.fromBorderSide(config.useVisualIndicators
? BorderSide(width: 2, color: borderColor, strokeAlign: 0)
: BorderSide.none);
final border = Border.fromBorderSide(
config.useVisualIndicators
? BorderSide(width: 2, color: borderColor, strokeAlign: 0)
: BorderSide.none,
);
final borderRadius = BorderRadius.circular(8 * config.borderRadiusScale);
final decoration = CustomDropdownDecoration(
@ -51,33 +59,13 @@ extension ScaleCustomDropdownThemeExt on ScaleTheme {
expandedShadow: [],
closedSuffixIcon: Icon(Icons.arrow_drop_down, color: borderColor),
expandedSuffixIcon: Icon(Icons.arrow_drop_up, color: borderColor),
prefixIcon: null,
closedBorder: border,
closedBorderRadius: borderRadius,
closedErrorBorder: null,
closedErrorBorderRadius: null,
expandedBorder: border,
expandedBorderRadius: borderRadius,
hintStyle: null,
headerStyle: null,
noResultFoundStyle: null,
errorStyle: null,
listItemStyle: null,
overlayScrollbarDecoration: null,
searchFieldDecoration: null,
listItemDecoration: null,
);
const disabledDecoration = CustomDropdownDisabledDecoration(
fillColor: null,
shadow: null,
suffixIcon: null,
prefixIcon: null,
border: null,
borderRadius: null,
headerStyle: null,
hintStyle: null,
);
const disabledDecoration = CustomDropdownDisabledDecoration();
return ScaleCustomDropdownTheme(
textStyle: textTheme.labelSmall!.copyWith(color: borderColor),

View file

@ -4,220 +4,243 @@ import 'package:flutter/material.dart';
import 'scale_theme.dart';
class ScaleInputDecoratorTheme extends InputDecorationTheme {
ScaleInputDecoratorTheme(
this._scaleScheme, ScaleConfig scaleConfig, this._textTheme)
: hintAlpha = scaleConfig.preferBorders ? 127 : 255,
super(
contentPadding: const EdgeInsets.all(8),
labelStyle: TextStyle(color: _scaleScheme.primaryScale.subtleText),
floatingLabelStyle:
TextStyle(color: _scaleScheme.primaryScale.subtleText),
border: OutlineInputBorder(
borderSide: BorderSide(color: _scaleScheme.primaryScale.border),
borderRadius:
BorderRadius.circular(8 * scaleConfig.borderRadiusScale)),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: _scaleScheme.primaryScale.border),
borderRadius:
BorderRadius.circular(8 * scaleConfig.borderRadiusScale)),
disabledBorder: OutlineInputBorder(
borderSide: BorderSide(
color: _scaleScheme.grayScale.border.withAlpha(0x7F)),
borderRadius:
BorderRadius.circular(8 * scaleConfig.borderRadiusScale)),
errorBorder: OutlineInputBorder(
borderSide: BorderSide(color: _scaleScheme.errorScale.border),
borderRadius:
BorderRadius.circular(8 * scaleConfig.borderRadiusScale)),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(
color: _scaleScheme.primaryScale.hoverBorder, width: 2),
borderRadius:
BorderRadius.circular(8 * scaleConfig.borderRadiusScale)),
hoverColor:
_scaleScheme.primaryScale.hoverElementBackground.withAlpha(0x7F),
filled: true,
focusedErrorBorder: OutlineInputBorder(
borderSide:
BorderSide(color: _scaleScheme.errorScale.border, width: 2),
borderRadius:
BorderRadius.circular(8 * scaleConfig.borderRadiusScale)),
);
final ScaleScheme _scaleScheme;
final TextTheme _textTheme;
final int hintAlpha;
final int disabledAlpha = 127;
final disabledAlpha = 127;
ScaleInputDecoratorTheme(
this._scaleScheme,
ScaleConfig scaleConfig,
this._textTheme, {
super.key,
}) : hintAlpha = scaleConfig.preferBorders ? 127 : 255,
super(
contentPadding: const EdgeInsets.all(8),
labelStyle: TextStyle(color: _scaleScheme.primaryScale.subtleText),
floatingLabelStyle: TextStyle(
color: _scaleScheme.primaryScale.subtleText,
),
border: OutlineInputBorder(
borderSide: BorderSide(color: _scaleScheme.primaryScale.border),
borderRadius: BorderRadius.circular(
8 * scaleConfig.borderRadiusScale,
),
),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: _scaleScheme.primaryScale.border),
borderRadius: BorderRadius.circular(
8 * scaleConfig.borderRadiusScale,
),
),
disabledBorder: OutlineInputBorder(
borderSide: BorderSide(
color: _scaleScheme.grayScale.border.withAlpha(0x7F),
),
borderRadius: BorderRadius.circular(
8 * scaleConfig.borderRadiusScale,
),
),
errorBorder: OutlineInputBorder(
borderSide: BorderSide(color: _scaleScheme.errorScale.border),
borderRadius: BorderRadius.circular(
8 * scaleConfig.borderRadiusScale,
),
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(
color: _scaleScheme.primaryScale.hoverBorder,
width: 2,
),
borderRadius: BorderRadius.circular(
8 * scaleConfig.borderRadiusScale,
),
),
hoverColor: _scaleScheme.primaryScale.hoverElementBackground.withAlpha(
0x7F,
),
filled: true,
focusedErrorBorder: OutlineInputBorder(
borderSide: BorderSide(
color: _scaleScheme.errorScale.border,
width: 2,
),
borderRadius: BorderRadius.circular(
8 * scaleConfig.borderRadiusScale,
),
),
);
@override
TextStyle? get hintStyle => WidgetStateTextStyle.resolveWith((states) {
if (states.contains(WidgetState.disabled)) {
return TextStyle(color: _scaleScheme.grayScale.border);
}
return TextStyle(
color: _scaleScheme.primaryScale.border.withAlpha(hintAlpha));
});
if (states.contains(WidgetState.disabled)) {
return TextStyle(color: _scaleScheme.grayScale.border);
}
return TextStyle(
color: _scaleScheme.primaryScale.border.withAlpha(hintAlpha),
);
});
@override
Color? get fillColor => WidgetStateColor.resolveWith((states) {
if (states.contains(WidgetState.disabled)) {
return _scaleScheme.grayScale.primary.withAlpha(10);
}
return _scaleScheme.primaryScale.primary.withAlpha(10);
});
if (states.contains(WidgetState.disabled)) {
return _scaleScheme.grayScale.primary.withAlpha(10);
}
return _scaleScheme.primaryScale.primary.withAlpha(10);
});
@override
BorderSide? get activeIndicatorBorder =>
WidgetStateBorderSide.resolveWith((states) {
if (states.contains(WidgetState.disabled)) {
return BorderSide(
color: _scaleScheme.grayScale.border.withAlpha(disabledAlpha));
}
if (states.contains(WidgetState.error)) {
if (states.contains(WidgetState.hovered)) {
return BorderSide(color: _scaleScheme.errorScale.hoverBorder);
}
if (states.contains(WidgetState.focused)) {
return BorderSide(color: _scaleScheme.errorScale.border, width: 2);
}
return BorderSide(color: _scaleScheme.errorScale.subtleBorder);
}
if (states.contains(WidgetState.hovered)) {
return BorderSide(color: _scaleScheme.secondaryScale.hoverBorder);
}
if (states.contains(WidgetState.focused)) {
return BorderSide(
color: _scaleScheme.secondaryScale.border, width: 2);
}
return BorderSide(color: _scaleScheme.secondaryScale.subtleBorder);
});
BorderSide? get activeIndicatorBorder => WidgetStateBorderSide.resolveWith((
states,
) {
if (states.contains(WidgetState.disabled)) {
return BorderSide(
color: _scaleScheme.grayScale.border.withAlpha(disabledAlpha),
);
}
if (states.contains(WidgetState.error)) {
if (states.contains(WidgetState.hovered)) {
return BorderSide(color: _scaleScheme.errorScale.hoverBorder);
}
if (states.contains(WidgetState.focused)) {
return BorderSide(color: _scaleScheme.errorScale.border, width: 2);
}
return BorderSide(color: _scaleScheme.errorScale.subtleBorder);
}
if (states.contains(WidgetState.hovered)) {
return BorderSide(color: _scaleScheme.secondaryScale.hoverBorder);
}
if (states.contains(WidgetState.focused)) {
return BorderSide(color: _scaleScheme.secondaryScale.border, width: 2);
}
return BorderSide(color: _scaleScheme.secondaryScale.subtleBorder);
});
@override
BorderSide? get outlineBorder => WidgetStateBorderSide.resolveWith((states) {
if (states.contains(WidgetState.disabled)) {
return BorderSide(
color: _scaleScheme.grayScale.border.withAlpha(disabledAlpha));
}
if (states.contains(WidgetState.error)) {
if (states.contains(WidgetState.hovered)) {
return BorderSide(color: _scaleScheme.errorScale.hoverBorder);
}
if (states.contains(WidgetState.focused)) {
return BorderSide(color: _scaleScheme.errorScale.border, width: 2);
}
return BorderSide(color: _scaleScheme.errorScale.subtleBorder);
}
if (states.contains(WidgetState.hovered)) {
return BorderSide(color: _scaleScheme.primaryScale.hoverBorder);
}
if (states.contains(WidgetState.focused)) {
return BorderSide(color: _scaleScheme.primaryScale.border, width: 2);
}
return BorderSide(color: _scaleScheme.primaryScale.subtleBorder);
});
if (states.contains(WidgetState.disabled)) {
return BorderSide(
color: _scaleScheme.grayScale.border.withAlpha(disabledAlpha),
);
}
if (states.contains(WidgetState.error)) {
if (states.contains(WidgetState.hovered)) {
return BorderSide(color: _scaleScheme.errorScale.hoverBorder);
}
if (states.contains(WidgetState.focused)) {
return BorderSide(color: _scaleScheme.errorScale.border, width: 2);
}
return BorderSide(color: _scaleScheme.errorScale.subtleBorder);
}
if (states.contains(WidgetState.hovered)) {
return BorderSide(color: _scaleScheme.primaryScale.hoverBorder);
}
if (states.contains(WidgetState.focused)) {
return BorderSide(color: _scaleScheme.primaryScale.border, width: 2);
}
return BorderSide(color: _scaleScheme.primaryScale.subtleBorder);
});
@override
Color? get iconColor => _scaleScheme.primaryScale.primary;
@override
Color? get prefixIconColor => WidgetStateColor.resolveWith((states) {
if (states.contains(WidgetState.disabled)) {
return _scaleScheme.primaryScale.primary.withAlpha(disabledAlpha);
}
if (states.contains(WidgetState.error)) {
return _scaleScheme.errorScale.primary;
}
return _scaleScheme.primaryScale.primary;
});
if (states.contains(WidgetState.disabled)) {
return _scaleScheme.primaryScale.primary.withAlpha(disabledAlpha);
}
if (states.contains(WidgetState.error)) {
return _scaleScheme.errorScale.primary;
}
return _scaleScheme.primaryScale.primary;
});
@override
Color? get suffixIconColor => WidgetStateColor.resolveWith((states) {
if (states.contains(WidgetState.disabled)) {
return _scaleScheme.primaryScale.primary.withAlpha(disabledAlpha);
}
if (states.contains(WidgetState.error)) {
return _scaleScheme.errorScale.primary;
}
return _scaleScheme.primaryScale.primary;
});
if (states.contains(WidgetState.disabled)) {
return _scaleScheme.primaryScale.primary.withAlpha(disabledAlpha);
}
if (states.contains(WidgetState.error)) {
return _scaleScheme.errorScale.primary;
}
return _scaleScheme.primaryScale.primary;
});
@override
TextStyle? get labelStyle => WidgetStateTextStyle.resolveWith((states) {
final textStyle = _textTheme.bodyLarge ?? const TextStyle();
if (states.contains(WidgetState.disabled)) {
return textStyle.copyWith(
color: _scaleScheme.grayScale.border.withAlpha(disabledAlpha));
}
if (states.contains(WidgetState.error)) {
if (states.contains(WidgetState.hovered)) {
return textStyle.copyWith(
color: _scaleScheme.errorScale.hoverBorder);
}
if (states.contains(WidgetState.focused)) {
return textStyle.copyWith(
color: _scaleScheme.errorScale.hoverBorder);
}
return textStyle.copyWith(
color: _scaleScheme.errorScale.subtleBorder);
}
if (states.contains(WidgetState.hovered)) {
return textStyle.copyWith(
color: _scaleScheme.primaryScale.hoverBorder);
}
if (states.contains(WidgetState.focused)) {
return textStyle.copyWith(
color: _scaleScheme.primaryScale.hoverBorder);
}
return textStyle.copyWith(
color: _scaleScheme.primaryScale.border.withAlpha(hintAlpha));
});
final textStyle = _textTheme.bodyLarge ?? const TextStyle();
if (states.contains(WidgetState.disabled)) {
return textStyle.copyWith(
color: _scaleScheme.grayScale.border.withAlpha(disabledAlpha),
);
}
if (states.contains(WidgetState.error)) {
if (states.contains(WidgetState.hovered)) {
return textStyle.copyWith(color: _scaleScheme.errorScale.hoverBorder);
}
if (states.contains(WidgetState.focused)) {
return textStyle.copyWith(color: _scaleScheme.errorScale.hoverBorder);
}
return textStyle.copyWith(color: _scaleScheme.errorScale.subtleBorder);
}
if (states.contains(WidgetState.hovered)) {
return textStyle.copyWith(color: _scaleScheme.primaryScale.hoverBorder);
}
if (states.contains(WidgetState.focused)) {
return textStyle.copyWith(color: _scaleScheme.primaryScale.hoverBorder);
}
return textStyle.copyWith(
color: _scaleScheme.primaryScale.border.withAlpha(hintAlpha),
);
});
@override
TextStyle? get floatingLabelStyle =>
WidgetStateTextStyle.resolveWith((states) {
final textStyle = _textTheme.bodyLarge ?? const TextStyle();
if (states.contains(WidgetState.disabled)) {
return textStyle.copyWith(
color: _scaleScheme.grayScale.border.withAlpha(disabledAlpha));
}
if (states.contains(WidgetState.error)) {
if (states.contains(WidgetState.hovered)) {
return textStyle.copyWith(
color: _scaleScheme.errorScale.hoverBorder);
}
if (states.contains(WidgetState.focused)) {
return textStyle.copyWith(
color: _scaleScheme.errorScale.hoverBorder);
}
return textStyle.copyWith(
color: _scaleScheme.errorScale.subtleBorder);
}
if (states.contains(WidgetState.hovered)) {
return textStyle.copyWith(
color: _scaleScheme.primaryScale.hoverBorder);
}
if (states.contains(WidgetState.focused)) {
return textStyle.copyWith(
color: _scaleScheme.primaryScale.hoverBorder);
}
return textStyle.copyWith(color: _scaleScheme.primaryScale.border);
});
TextStyle? get floatingLabelStyle => WidgetStateTextStyle.resolveWith((
states,
) {
final textStyle = _textTheme.bodyLarge ?? const TextStyle();
if (states.contains(WidgetState.disabled)) {
return textStyle.copyWith(
color: _scaleScheme.grayScale.border.withAlpha(disabledAlpha),
);
}
if (states.contains(WidgetState.error)) {
if (states.contains(WidgetState.hovered)) {
return textStyle.copyWith(color: _scaleScheme.errorScale.hoverBorder);
}
if (states.contains(WidgetState.focused)) {
return textStyle.copyWith(color: _scaleScheme.errorScale.hoverBorder);
}
return textStyle.copyWith(color: _scaleScheme.errorScale.subtleBorder);
}
if (states.contains(WidgetState.hovered)) {
return textStyle.copyWith(color: _scaleScheme.primaryScale.hoverBorder);
}
if (states.contains(WidgetState.focused)) {
return textStyle.copyWith(color: _scaleScheme.primaryScale.hoverBorder);
}
return textStyle.copyWith(color: _scaleScheme.primaryScale.border);
});
@override
TextStyle? get helperStyle => WidgetStateTextStyle.resolveWith((states) {
final textStyle = _textTheme.bodySmall ?? const TextStyle();
if (states.contains(WidgetState.disabled)) {
return textStyle.copyWith(color: _scaleScheme.grayScale.border);
}
return textStyle.copyWith(
color: _scaleScheme.primaryScale.border.withAlpha(hintAlpha));
});
final textStyle = _textTheme.bodySmall ?? const TextStyle();
if (states.contains(WidgetState.disabled)) {
return textStyle.copyWith(color: _scaleScheme.grayScale.border);
}
return textStyle.copyWith(
color: _scaleScheme.primaryScale.border.withAlpha(hintAlpha),
);
});
@override
TextStyle? get errorStyle => WidgetStateTextStyle.resolveWith((states) {
final textStyle = _textTheme.bodySmall ?? const TextStyle();
return textStyle.copyWith(color: _scaleScheme.errorScale.primary);
});
final textStyle = _textTheme.bodySmall ?? const TextStyle();
return textStyle.copyWith(color: _scaleScheme.errorScale.primary);
});
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {

View file

@ -8,6 +8,18 @@ import 'scale_color.dart';
enum ScaleKind { primary, primaryAlpha, secondary, tertiary, gray, error }
class ScaleScheme extends ThemeExtension<ScaleScheme> {
final ScaleColor primaryScale;
final ScaleColor primaryAlphaScale;
final ScaleColor secondaryScale;
final ScaleColor tertiaryScale;
final ScaleColor grayScale;
final ScaleColor errorScale;
ScaleScheme({
required this.primaryScale,
required this.primaryAlphaScale,
@ -17,13 +29,6 @@ class ScaleScheme extends ThemeExtension<ScaleScheme> {
required this.errorScale,
});
final ScaleColor primaryScale;
final ScaleColor primaryAlphaScale;
final ScaleColor secondaryScale;
final ScaleColor tertiaryScale;
final ScaleColor grayScale;
final ScaleColor errorScale;
ScaleColor scale(ScaleKind kind) {
switch (kind) {
case ScaleKind.primary:
@ -42,21 +47,21 @@ class ScaleScheme extends ThemeExtension<ScaleScheme> {
}
@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,
);
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) {
@ -65,8 +70,11 @@ class ScaleScheme extends ThemeExtension<ScaleScheme> {
}
return ScaleScheme(
primaryScale: ScaleColor.lerp(primaryScale, other.primaryScale, t),
primaryAlphaScale:
ScaleColor.lerp(primaryAlphaScale, other.primaryAlphaScale, 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),
@ -75,38 +83,46 @@ class ScaleScheme extends ThemeExtension<ScaleScheme> {
}
ColorScheme toColorScheme(Brightness brightness) => ColorScheme(
brightness: brightness,
primary: primaryScale.primary,
onPrimary: primaryScale.primaryText,
// primaryContainer: primaryScale.hoverElementBackground,
// onPrimaryContainer: primaryScale.subtleText,
secondary: secondaryScale.primary,
onSecondary: secondaryScale.primaryText,
// secondaryContainer: secondaryScale.hoverElementBackground,
// onSecondaryContainer: secondaryScale.subtleText,
tertiary: tertiaryScale.primary,
onTertiary: tertiaryScale.primaryText,
// tertiaryContainer: tertiaryScale.hoverElementBackground,
// onTertiaryContainer: tertiaryScale.subtleText,
error: errorScale.primary,
onError: errorScale.primaryText,
// errorContainer: errorScale.hoverElementBackground,
// onErrorContainer: errorScale.subtleText,
surface: primaryScale.appBackground,
onSurface: primaryScale.appText,
onSurfaceVariant: secondaryScale.appText,
outline: primaryScale.border,
outlineVariant: secondaryScale.border,
shadow: primaryScale.primary.darken(60),
//scrim: primaryScale.background,
// inverseSurface: primaryScale.subtleText,
// onInverseSurface: primaryScale.subtleBackground,
// inversePrimary: primaryScale.hoverBackground,
// surfaceTint: primaryAlphaScale.hoverElementBackground,
);
brightness: brightness,
primary: primaryScale.primary,
onPrimary: primaryScale.primaryText,
// primaryContainer: primaryScale.hoverElementBackground,
// onPrimaryContainer: primaryScale.subtleText,
secondary: secondaryScale.primary,
onSecondary: secondaryScale.primaryText,
// secondaryContainer: secondaryScale.hoverElementBackground,
// onSecondaryContainer: secondaryScale.subtleText,
tertiary: tertiaryScale.primary,
onTertiary: tertiaryScale.primaryText,
// tertiaryContainer: tertiaryScale.hoverElementBackground,
// onTertiaryContainer: tertiaryScale.subtleText,
error: errorScale.primary,
onError: errorScale.primaryText,
// errorContainer: errorScale.hoverElementBackground,
// onErrorContainer: errorScale.subtleText,
surface: primaryScale.appBackground,
onSurface: primaryScale.appText,
onSurfaceVariant: secondaryScale.appText,
outline: primaryScale.border,
outlineVariant: secondaryScale.border,
shadow: primaryScale.primary.darken(60),
//scrim: primaryScale.background,
// inverseSurface: primaryScale.subtleText,
// onInverseSurface: primaryScale.subtleBackground,
// inversePrimary: primaryScale.hoverBackground,
// surfaceTint: primaryAlphaScale.hoverElementBackground,
);
}
class ScaleConfig extends ThemeExtension<ScaleConfig> {
final bool useVisualIndicators;
final bool preferBorders;
final double borderRadiusScale;
final double wallpaperOpacity;
ScaleConfig({
required this.useVisualIndicators,
required this.preferBorders,
@ -114,25 +130,20 @@ class ScaleConfig extends ThemeExtension<ScaleConfig> {
required this.wallpaperOpacity,
});
final bool useVisualIndicators;
final bool preferBorders;
final double borderRadiusScale;
final double wallpaperOpacity;
int get wallpaperAlpha => wallpaperOpacity.toInt();
@override
ScaleConfig copyWith(
{bool? useVisualIndicators,
bool? preferBorders,
double? borderRadiusScale,
double? wallpaperOpacity}) =>
ScaleConfig(
useVisualIndicators: useVisualIndicators ?? this.useVisualIndicators,
preferBorders: preferBorders ?? this.preferBorders,
borderRadiusScale: borderRadiusScale ?? this.borderRadiusScale,
wallpaperOpacity: wallpaperOpacity ?? this.wallpaperOpacity,
);
ScaleConfig copyWith({
bool? useVisualIndicators,
bool? preferBorders,
double? borderRadiusScale,
double? wallpaperOpacity,
}) => ScaleConfig(
useVisualIndicators: useVisualIndicators ?? this.useVisualIndicators,
preferBorders: preferBorders ?? this.preferBorders,
borderRadiusScale: borderRadiusScale ?? this.borderRadiusScale,
wallpaperOpacity: wallpaperOpacity ?? this.wallpaperOpacity,
);
@override
ScaleConfig lerp(ScaleConfig? other, double t) {
@ -140,12 +151,14 @@ class ScaleConfig extends ThemeExtension<ScaleConfig> {
return this;
}
return ScaleConfig(
useVisualIndicators:
t < .5 ? useVisualIndicators : other.useVisualIndicators,
preferBorders: t < .5 ? preferBorders : other.preferBorders,
borderRadiusScale:
lerpDouble(borderRadiusScale, other.borderRadiusScale, t) ?? 1,
wallpaperOpacity:
lerpDouble(wallpaperOpacity, other.wallpaperOpacity, t) ?? 1);
useVisualIndicators: t < .5
? useVisualIndicators
: other.useVisualIndicators,
preferBorders: t < .5 ? preferBorders : other.preferBorders,
borderRadiusScale:
lerpDouble(borderRadiusScale, other.borderRadiusScale, t) ?? 1,
wallpaperOpacity:
lerpDouble(wallpaperOpacity, other.wallpaperOpacity, t) ?? 1,
);
}
}

View file

@ -12,27 +12,28 @@ export 'scale_tile_theme.dart';
export 'scale_toast_theme.dart';
class ScaleTheme extends ThemeExtension<ScaleTheme> {
final TextTheme textTheme;
final ScaleScheme scheme;
final ScaleConfig config;
ScaleTheme({
required this.textTheme,
required this.scheme,
required this.config,
});
final TextTheme textTheme;
final ScaleScheme scheme;
final ScaleConfig config;
@override
ScaleTheme copyWith({
TextTheme? textTheme,
ScaleScheme? scheme,
ScaleConfig? config,
}) =>
ScaleTheme(
textTheme: textTheme ?? this.textTheme,
scheme: scheme ?? this.scheme,
config: config ?? this.config,
);
}) => ScaleTheme(
textTheme: textTheme ?? this.textTheme,
scheme: scheme ?? this.scheme,
config: config ?? this.config,
);
@override
ScaleTheme lerp(ScaleTheme? other, double t) {
@ -40,34 +41,37 @@ class ScaleTheme extends ThemeExtension<ScaleTheme> {
return this;
}
return ScaleTheme(
textTheme: TextTheme.lerp(textTheme, other.textTheme, t),
scheme: scheme.lerp(other.scheme, t),
config: config.lerp(other.config, t));
textTheme: TextTheme.lerp(textTheme, other.textTheme, t),
scheme: scheme.lerp(other.scheme, t),
config: config.lerp(other.config, t),
);
}
WidgetStateProperty<BorderSide?> elementBorderWidgetStateProperty() =>
WidgetStateProperty.resolveWith((states) {
if (states.contains(WidgetState.disabled)) {
return BorderSide(
color: scheme.grayScale.border.withAlpha(0x7F),
strokeAlign: BorderSide.strokeAlignOutside);
} else if (states.contains(WidgetState.pressed)) {
return BorderSide(
color: scheme.primaryScale.border,
color: scheme.grayScale.border.withAlpha(0x7F),
strokeAlign: BorderSide.strokeAlignOutside,
);
} else if (states.contains(WidgetState.pressed)) {
return BorderSide(color: scheme.primaryScale.border);
} else if (states.contains(WidgetState.hovered)) {
return BorderSide(
color: scheme.primaryScale.hoverBorder,
strokeAlign: BorderSide.strokeAlignOutside);
color: scheme.primaryScale.hoverBorder,
strokeAlign: BorderSide.strokeAlignOutside,
);
} else if (states.contains(WidgetState.focused)) {
return BorderSide(
color: scheme.primaryScale.hoverBorder,
width: 2,
strokeAlign: BorderSide.strokeAlignOutside);
color: scheme.primaryScale.hoverBorder,
width: 2,
strokeAlign: BorderSide.strokeAlignOutside,
);
}
return BorderSide(
color: scheme.primaryScale.border,
strokeAlign: BorderSide.strokeAlignOutside);
color: scheme.primaryScale.border,
strokeAlign: BorderSide.strokeAlignOutside,
);
});
WidgetStateProperty<Color?> elementColorWidgetStateProperty() =>
@ -82,7 +86,10 @@ class ScaleTheme extends ThemeExtension<ScaleTheme> {
return scheme.primaryScale.borderText;
}
return Color.lerp(
scheme.primaryScale.borderText, scheme.primaryScale.primary, 0.25);
scheme.primaryScale.borderText,
scheme.primaryScale.primary,
0.25,
);
});
WidgetStateProperty<Color?> checkboxFillColorWidgetStateProperty() =>
@ -103,102 +110,112 @@ class ScaleTheme extends ThemeExtension<ScaleTheme> {
}
});
// WidgetStateProperty<Color?> elementBackgroundWidgetStateProperty() {
// return null;
// }
ThemeData toThemeData(Brightness brightness) {
final colorScheme = scheme.toColorScheme(brightness);
final baseThemeData = ThemeData.from(
colorScheme: colorScheme, textTheme: textTheme, useMaterial3: true);
colorScheme: colorScheme,
textTheme: textTheme,
useMaterial3: true,
);
final elevatedButtonTheme = ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
elevation: 0,
textStyle: textTheme.labelSmall,
backgroundColor: scheme.primaryScale.elementBackground,
disabledBackgroundColor:
scheme.grayScale.elementBackground.withAlpha(0x7F),
disabledForegroundColor:
scheme.grayScale.primary.withAlpha(0x7F),
shape: RoundedRectangleBorder(
side: BorderSide(color: scheme.primaryScale.border),
borderRadius:
BorderRadius.circular(8 * config.borderRadiusScale)))
.copyWith(
foregroundColor: elementColorWidgetStateProperty(),
side: elementBorderWidgetStateProperty(),
iconColor: elementColorWidgetStateProperty(),
));
style:
ElevatedButton.styleFrom(
elevation: 0,
textStyle: textTheme.labelSmall,
backgroundColor: scheme.primaryScale.elementBackground,
disabledBackgroundColor: scheme.grayScale.elementBackground
.withAlpha(0x7F),
disabledForegroundColor: scheme.grayScale.primary.withAlpha(0x7F),
shape: RoundedRectangleBorder(
side: BorderSide(color: scheme.primaryScale.border),
borderRadius: BorderRadius.circular(8 * config.borderRadiusScale),
),
).copyWith(
foregroundColor: elementColorWidgetStateProperty(),
side: elementBorderWidgetStateProperty(),
iconColor: elementColorWidgetStateProperty(),
),
);
final sliderTheme = SliderThemeData.fromPrimaryColors(
primaryColor: scheme.primaryScale.hoverBorder,
primaryColorDark: scheme.primaryScale.border,
primaryColorLight: scheme.primaryScale.border,
valueIndicatorTextStyle: textTheme.labelMedium!
.copyWith(color: scheme.primaryScale.borderText));
primaryColor: scheme.primaryScale.hoverBorder,
primaryColorDark: scheme.primaryScale.border,
primaryColorLight: scheme.primaryScale.border,
valueIndicatorTextStyle: textTheme.labelMedium!.copyWith(
color: scheme.primaryScale.borderText,
),
);
final themeData = baseThemeData.copyWith(
scrollbarTheme: baseThemeData.scrollbarTheme.copyWith(
thumbColor: WidgetStateProperty.resolveWith((states) {
scrollbarTheme: baseThemeData.scrollbarTheme.copyWith(
thumbColor: WidgetStateProperty.resolveWith((states) {
if (states.contains(WidgetState.pressed)) {
return scheme.primaryScale.border;
} else if (states.contains(WidgetState.hovered)) {
return scheme.primaryScale.hoverBorder;
}
return scheme.primaryScale.subtleBorder;
}), trackColor: WidgetStateProperty.resolveWith((states) {
}),
trackColor: WidgetStateProperty.resolveWith((states) {
if (states.contains(WidgetState.pressed)) {
return scheme.primaryScale.activeElementBackground;
} else if (states.contains(WidgetState.hovered)) {
return scheme.primaryScale.hoverElementBackground;
}
return scheme.primaryScale.elementBackground;
}), trackBorderColor: WidgetStateProperty.resolveWith((states) {
}),
trackBorderColor: WidgetStateProperty.resolveWith((states) {
if (states.contains(WidgetState.pressed)) {
return scheme.primaryScale.subtleBorder;
} else if (states.contains(WidgetState.hovered)) {
return scheme.primaryScale.subtleBorder;
}
return scheme.primaryScale.subtleBorder;
})),
appBarTheme: baseThemeData.appBarTheme.copyWith(
backgroundColor: scheme.primaryScale.border,
foregroundColor: scheme.primaryScale.borderText,
toolbarHeight: 48,
}),
),
appBarTheme: baseThemeData.appBarTheme.copyWith(
backgroundColor: scheme.primaryScale.border,
foregroundColor: scheme.primaryScale.borderText,
toolbarHeight: 48,
),
bottomSheetTheme: baseThemeData.bottomSheetTheme.copyWith(
elevation: 0,
modalElevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(16 * config.borderRadiusScale),
topRight: Radius.circular(16 * config.borderRadiusScale),
),
),
bottomSheetTheme: baseThemeData.bottomSheetTheme.copyWith(
elevation: 0,
modalElevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(16 * config.borderRadiusScale),
topRight: Radius.circular(16 * config.borderRadiusScale)))),
canvasColor: scheme.primaryScale.subtleBackground,
checkboxTheme: baseThemeData.checkboxTheme.copyWith(
),
canvasColor: scheme.primaryScale.subtleBackground,
checkboxTheme: baseThemeData.checkboxTheme.copyWith(
side: BorderSide(color: scheme.primaryScale.border, width: 2),
checkColor: elementColorWidgetStateProperty(),
fillColor: checkboxFillColorWidgetStateProperty(),
),
chipTheme: baseThemeData.chipTheme.copyWith(
backgroundColor: scheme.primaryScale.elementBackground,
selectedColor: scheme.primaryScale.activeElementBackground,
surfaceTintColor: scheme.primaryScale.hoverElementBackground,
checkmarkColor: scheme.primaryScale.primary,
side: BorderSide(color: scheme.primaryScale.border),
),
elevatedButtonTheme: elevatedButtonTheme,
inputDecorationTheme: ScaleInputDecoratorTheme(scheme, config, textTheme),
sliderTheme: sliderTheme,
popupMenuTheme: PopupMenuThemeData(
color: scheme.primaryScale.elementBackground,
shadowColor: Colors.transparent,
shape: RoundedRectangleBorder(
side: BorderSide(color: scheme.primaryScale.border, width: 2),
checkColor: elementColorWidgetStateProperty(),
fillColor: checkboxFillColorWidgetStateProperty(),
borderRadius: BorderRadius.circular(8 * config.borderRadiusScale),
),
chipTheme: baseThemeData.chipTheme.copyWith(
backgroundColor: scheme.primaryScale.elementBackground,
selectedColor: scheme.primaryScale.activeElementBackground,
surfaceTintColor: scheme.primaryScale.hoverElementBackground,
checkmarkColor: scheme.primaryScale.primary,
side: BorderSide(color: scheme.primaryScale.border)),
elevatedButtonTheme: elevatedButtonTheme,
inputDecorationTheme:
ScaleInputDecoratorTheme(scheme, config, textTheme),
sliderTheme: sliderTheme,
popupMenuTheme: PopupMenuThemeData(
color: scheme.primaryScale.elementBackground,
shadowColor: Colors.transparent,
shape: RoundedRectangleBorder(
side: BorderSide(color: scheme.primaryScale.border, width: 2),
borderRadius:
BorderRadius.circular(8 * config.borderRadiusScale))),
extensions: <ThemeExtension<dynamic>>[scheme, config, this]);
),
extensions: <ThemeExtension<dynamic>>[scheme, config, this],
);
return themeData;
}

View file

@ -3,27 +3,34 @@ import 'package:flutter/material.dart';
import 'scale_theme.dart';
class ScaleTileTheme {
ScaleTileTheme(
{required this.textColor,
required this.backgroundColor,
required this.borderColor,
required this.shapeBorder,
required this.largeTextStyle,
required this.smallTextStyle});
final Color textColor;
final Color backgroundColor;
final Color borderColor;
final ShapeBorder shapeBorder;
final TextStyle largeTextStyle;
final TextStyle smallTextStyle;
ScaleTileTheme({
required this.textColor,
required this.backgroundColor,
required this.borderColor,
required this.shapeBorder,
required this.largeTextStyle,
required this.smallTextStyle,
});
}
extension ScaleTileThemeExt on ScaleTheme {
ScaleTileTheme tileTheme(
{bool disabled = false,
bool selected = false,
ScaleKind scaleKind = ScaleKind.primary}) {
ScaleTileTheme tileTheme({
bool disabled = false,
bool selected = false,
ScaleKind scaleKind = ScaleKind.primary,
}) {
final tileColor = scheme.scale(!disabled ? scaleKind : ScaleKind.gray);
final borderColor = selected ? tileColor.hoverBorder : tileColor.border;
@ -38,13 +45,11 @@ extension ScaleTileThemeExt on ScaleTheme {
final smallTextStyle = textTheme.labelSmall!.copyWith(color: textColor);
final shapeBorder = RoundedRectangleBorder(
side: config.useVisualIndicators
? BorderSide(
width: 2,
color: borderColor,
)
: BorderSide.none,
borderRadius: BorderRadius.circular(8 * config.borderRadiusScale));
side: config.useVisualIndicators
? BorderSide(width: 2, color: borderColor)
: BorderSide.none,
borderRadius: BorderRadius.circular(8 * config.borderRadiusScale),
);
return ScaleTileTheme(
textColor: textColor,

View file

@ -2,32 +2,38 @@ import 'package:flutter/material.dart';
import 'scale_theme.dart';
enum ScaleToastKind {
info,
error,
}
enum ScaleToastKind { info, error }
class ScaleToastTheme {
ScaleToastTheme(
{required this.primaryColor,
required this.backgroundColor,
required this.foregroundColor,
required this.borderSide,
required this.borderRadius,
required this.padding,
required this.icon,
required this.titleTextStyle,
required this.descriptionTextStyle});
final Color primaryColor;
final Color backgroundColor;
final Color foregroundColor;
final BorderSide? borderSide;
final BorderRadiusGeometry borderRadius;
final EdgeInsetsGeometry padding;
final Icon icon;
final TextStyle titleTextStyle;
final TextStyle descriptionTextStyle;
ScaleToastTheme({
required this.primaryColor,
required this.backgroundColor,
required this.foregroundColor,
required this.borderSide,
required this.borderRadius,
required this.padding,
required this.icon,
required this.titleTextStyle,
required this.descriptionTextStyle,
});
}
extension ScaleToastThemeExt on ScaleTheme {
@ -54,17 +60,17 @@ extension ScaleToastThemeExt on ScaleTheme {
}
return ScaleToastTheme(
primaryColor: primaryColor,
backgroundColor: backgroundColor,
foregroundColor: textColor,
borderSide: (config.useVisualIndicators || config.preferBorders)
? BorderSide(color: borderColor, width: 2)
: const BorderSide(color: Colors.transparent, width: 0),
borderRadius: BorderRadius.circular(12 * config.borderRadiusScale),
padding: const EdgeInsets.all(8),
icon: icon,
titleTextStyle: textTheme.labelMedium!.copyWith(color: titleColor),
descriptionTextStyle:
textTheme.labelMedium!.copyWith(color: textColor));
primaryColor: primaryColor,
backgroundColor: backgroundColor,
foregroundColor: textColor,
borderSide: (config.useVisualIndicators || config.preferBorders)
? BorderSide(color: borderColor, width: 2)
: const BorderSide(color: Colors.transparent, width: 0),
borderRadius: BorderRadius.circular(12 * config.borderRadiusScale),
padding: const EdgeInsets.all(8),
icon: icon,
titleTextStyle: textTheme.labelMedium!.copyWith(color: titleColor),
descriptionTextStyle: textTheme.labelMedium!.copyWith(color: textColor),
);
}
}

View file

@ -50,6 +50,8 @@ enum ColorPreference {
@freezed
sealed class ThemePreferences with _$ThemePreferences {
static const defaults = ThemePreferences();
const factory ThemePreferences({
@Default(BrightnessPreference.system)
BrightnessPreference brightnessPreference,
@ -60,8 +62,6 @@ sealed class ThemePreferences with _$ThemePreferences {
factory ThemePreferences.fromJson(dynamic json) =>
_$ThemePreferencesFromJson(json as Map<String, dynamic>);
static const defaults = ThemePreferences();
}
extension ThemePreferencesExt on ThemePreferences {
@ -99,10 +99,11 @@ extension ThemePreferencesExt on ThemePreferences {
themeData = contrastGenerator(
brightness: brightness,
scaleConfig: ScaleConfig(
useVisualIndicators: true,
preferBorders: false,
borderRadiusScale: 1,
wallpaperOpacity: 255),
useVisualIndicators: true,
preferBorders: false,
borderRadiusScale: 1,
wallpaperOpacity: 255,
),
primaryFront: Colors.black,
primaryBack: Colors.white,
secondaryFront: Colors.black,
@ -119,10 +120,11 @@ extension ThemePreferencesExt on ThemePreferences {
? contrastGenerator(
brightness: Brightness.light,
scaleConfig: ScaleConfig(
useVisualIndicators: true,
preferBorders: true,
borderRadiusScale: 0.2,
wallpaperOpacity: 208),
useVisualIndicators: true,
preferBorders: true,
borderRadiusScale: 0.2,
wallpaperOpacity: 208,
),
primaryFront: const Color(0xFF000000),
primaryBack: const Color(0xFF00FF00),
secondaryFront: const Color(0xFF000000),
@ -133,14 +135,16 @@ extension ThemePreferencesExt on ThemePreferences {
grayBack: const Color(0xFFFFFFFF),
errorFront: const Color(0xFFC0C0C0),
errorBack: const Color(0xFF0000FF),
customTextTheme: makeMonoSpaceTextTheme(Brightness.light))
customTextTheme: makeMonoSpaceTextTheme(Brightness.light),
)
: contrastGenerator(
brightness: Brightness.dark,
scaleConfig: ScaleConfig(
useVisualIndicators: true,
preferBorders: true,
borderRadiusScale: 0.2,
wallpaperOpacity: 192),
useVisualIndicators: true,
preferBorders: true,
borderRadiusScale: 0.2,
wallpaperOpacity: 192,
),
primaryFront: const Color(0xFF000000),
primaryBack: const Color(0xFF00FF00),
secondaryFront: const Color(0xFF000000),

View file

@ -1,6 +1,5 @@
// dart format width=80
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// coverage:ignore-file
// 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
@ -15,61 +14,47 @@ T _$identity<T>(T value) => value;
/// @nodoc
mixin _$ThemePreferences {
BrightnessPreference get brightnessPreference;
ColorPreference get colorPreference;
double get displayScale;
bool get enableWallpaper;
/// Create a copy of ThemePreferences
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$ThemePreferencesCopyWith<ThemePreferences> get copyWith =>
_$ThemePreferencesCopyWithImpl<ThemePreferences>(
this as ThemePreferences, _$identity);
BrightnessPreference get brightnessPreference; ColorPreference get colorPreference; double get displayScale; bool get enableWallpaper;
/// Create a copy of ThemePreferences
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$ThemePreferencesCopyWith<ThemePreferences> get copyWith => _$ThemePreferencesCopyWithImpl<ThemePreferences>(this as ThemePreferences, _$identity);
/// Serializes this ThemePreferences to a JSON map.
Map<String, dynamic> toJson();
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is ThemePreferences &&
(identical(other.brightnessPreference, brightnessPreference) ||
other.brightnessPreference == brightnessPreference) &&
(identical(other.colorPreference, colorPreference) ||
other.colorPreference == colorPreference) &&
(identical(other.displayScale, displayScale) ||
other.displayScale == displayScale) &&
(identical(other.enableWallpaper, enableWallpaper) ||
other.enableWallpaper == enableWallpaper));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType, brightnessPreference,
colorPreference, displayScale, enableWallpaper);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is ThemePreferences&&(identical(other.brightnessPreference, brightnessPreference) || other.brightnessPreference == brightnessPreference)&&(identical(other.colorPreference, colorPreference) || other.colorPreference == colorPreference)&&(identical(other.displayScale, displayScale) || other.displayScale == displayScale)&&(identical(other.enableWallpaper, enableWallpaper) || other.enableWallpaper == enableWallpaper));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,brightnessPreference,colorPreference,displayScale,enableWallpaper);
@override
String toString() {
return 'ThemePreferences(brightnessPreference: $brightnessPreference, colorPreference: $colorPreference, displayScale: $displayScale, enableWallpaper: $enableWallpaper)';
}
@override
String toString() {
return 'ThemePreferences(brightnessPreference: $brightnessPreference, colorPreference: $colorPreference, displayScale: $displayScale, enableWallpaper: $enableWallpaper)';
}
}
/// @nodoc
abstract mixin class $ThemePreferencesCopyWith<$Res> {
factory $ThemePreferencesCopyWith(
ThemePreferences value, $Res Function(ThemePreferences) _then) =
_$ThemePreferencesCopyWithImpl;
@useResult
$Res call(
{BrightnessPreference brightnessPreference,
ColorPreference colorPreference,
double displayScale,
bool enableWallpaper});
}
abstract mixin class $ThemePreferencesCopyWith<$Res> {
factory $ThemePreferencesCopyWith(ThemePreferences value, $Res Function(ThemePreferences) _then) = _$ThemePreferencesCopyWithImpl;
@useResult
$Res call({
BrightnessPreference brightnessPreference, ColorPreference colorPreference, double displayScale, bool enableWallpaper
});
}
/// @nodoc
class _$ThemePreferencesCopyWithImpl<$Res>
implements $ThemePreferencesCopyWith<$Res> {
@ -78,117 +63,197 @@ class _$ThemePreferencesCopyWithImpl<$Res>
final ThemePreferences _self;
final $Res Function(ThemePreferences) _then;
/// Create a copy of ThemePreferences
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? brightnessPreference = null,
Object? colorPreference = null,
Object? displayScale = null,
Object? enableWallpaper = null,
}) {
return _then(_self.copyWith(
brightnessPreference: null == brightnessPreference
? _self.brightnessPreference
: brightnessPreference // ignore: cast_nullable_to_non_nullable
as BrightnessPreference,
colorPreference: null == colorPreference
? _self.colorPreference
: colorPreference // ignore: cast_nullable_to_non_nullable
as ColorPreference,
displayScale: null == displayScale
? _self.displayScale
: displayScale // ignore: cast_nullable_to_non_nullable
as double,
enableWallpaper: null == enableWallpaper
? _self.enableWallpaper
: enableWallpaper // ignore: cast_nullable_to_non_nullable
as bool,
));
}
/// Create a copy of ThemePreferences
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? brightnessPreference = null,Object? colorPreference = null,Object? displayScale = null,Object? enableWallpaper = null,}) {
return _then(_self.copyWith(
brightnessPreference: null == brightnessPreference ? _self.brightnessPreference : brightnessPreference // ignore: cast_nullable_to_non_nullable
as BrightnessPreference,colorPreference: null == colorPreference ? _self.colorPreference : colorPreference // ignore: cast_nullable_to_non_nullable
as ColorPreference,displayScale: null == displayScale ? _self.displayScale : displayScale // ignore: cast_nullable_to_non_nullable
as double,enableWallpaper: null == enableWallpaper ? _self.enableWallpaper : enableWallpaper // ignore: cast_nullable_to_non_nullable
as bool,
));
}
}
/// Adds pattern-matching-related methods to [ThemePreferences].
extension ThemePreferencesPatterns on ThemePreferences {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _ThemePreferences value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _ThemePreferences() when $default != null:
return $default(_that);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _ThemePreferences value) $default,){
final _that = this;
switch (_that) {
case _ThemePreferences():
return $default(_that);}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _ThemePreferences value)? $default,){
final _that = this;
switch (_that) {
case _ThemePreferences() when $default != null:
return $default(_that);case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( BrightnessPreference brightnessPreference, ColorPreference colorPreference, double displayScale, bool enableWallpaper)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _ThemePreferences() when $default != null:
return $default(_that.brightnessPreference,_that.colorPreference,_that.displayScale,_that.enableWallpaper);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( BrightnessPreference brightnessPreference, ColorPreference colorPreference, double displayScale, bool enableWallpaper) $default,) {final _that = this;
switch (_that) {
case _ThemePreferences():
return $default(_that.brightnessPreference,_that.colorPreference,_that.displayScale,_that.enableWallpaper);}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( BrightnessPreference brightnessPreference, ColorPreference colorPreference, double displayScale, bool enableWallpaper)? $default,) {final _that = this;
switch (_that) {
case _ThemePreferences() when $default != null:
return $default(_that.brightnessPreference,_that.colorPreference,_that.displayScale,_that.enableWallpaper);case _:
return null;
}
}
}
/// @nodoc
@JsonSerializable()
class _ThemePreferences implements ThemePreferences {
const _ThemePreferences(
{this.brightnessPreference = BrightnessPreference.system,
this.colorPreference = ColorPreference.vapor,
this.displayScale = 1,
this.enableWallpaper = true});
factory _ThemePreferences.fromJson(Map<String, dynamic> json) =>
_$ThemePreferencesFromJson(json);
const _ThemePreferences({this.brightnessPreference = BrightnessPreference.system, this.colorPreference = ColorPreference.vapor, this.displayScale = 1, this.enableWallpaper = true});
factory _ThemePreferences.fromJson(Map<String, dynamic> json) => _$ThemePreferencesFromJson(json);
@override
@JsonKey()
final BrightnessPreference brightnessPreference;
@override
@JsonKey()
final ColorPreference colorPreference;
@override
@JsonKey()
final double displayScale;
@override
@JsonKey()
final bool enableWallpaper;
@override@JsonKey() final BrightnessPreference brightnessPreference;
@override@JsonKey() final ColorPreference colorPreference;
@override@JsonKey() final double displayScale;
@override@JsonKey() final bool enableWallpaper;
/// Create a copy of ThemePreferences
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$ThemePreferencesCopyWith<_ThemePreferences> get copyWith =>
__$ThemePreferencesCopyWithImpl<_ThemePreferences>(this, _$identity);
/// Create a copy of ThemePreferences
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$ThemePreferencesCopyWith<_ThemePreferences> get copyWith => __$ThemePreferencesCopyWithImpl<_ThemePreferences>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$ThemePreferencesToJson(
this,
);
}
@override
Map<String, dynamic> toJson() {
return _$ThemePreferencesToJson(this, );
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _ThemePreferences &&
(identical(other.brightnessPreference, brightnessPreference) ||
other.brightnessPreference == brightnessPreference) &&
(identical(other.colorPreference, colorPreference) ||
other.colorPreference == colorPreference) &&
(identical(other.displayScale, displayScale) ||
other.displayScale == displayScale) &&
(identical(other.enableWallpaper, enableWallpaper) ||
other.enableWallpaper == enableWallpaper));
}
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _ThemePreferences&&(identical(other.brightnessPreference, brightnessPreference) || other.brightnessPreference == brightnessPreference)&&(identical(other.colorPreference, colorPreference) || other.colorPreference == colorPreference)&&(identical(other.displayScale, displayScale) || other.displayScale == displayScale)&&(identical(other.enableWallpaper, enableWallpaper) || other.enableWallpaper == enableWallpaper));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,brightnessPreference,colorPreference,displayScale,enableWallpaper);
@override
String toString() {
return 'ThemePreferences(brightnessPreference: $brightnessPreference, colorPreference: $colorPreference, displayScale: $displayScale, enableWallpaper: $enableWallpaper)';
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType, brightnessPreference,
colorPreference, displayScale, enableWallpaper);
@override
String toString() {
return 'ThemePreferences(brightnessPreference: $brightnessPreference, colorPreference: $colorPreference, displayScale: $displayScale, enableWallpaper: $enableWallpaper)';
}
}
/// @nodoc
abstract mixin class _$ThemePreferencesCopyWith<$Res>
implements $ThemePreferencesCopyWith<$Res> {
factory _$ThemePreferencesCopyWith(
_ThemePreferences value, $Res Function(_ThemePreferences) _then) =
__$ThemePreferencesCopyWithImpl;
@override
@useResult
$Res call(
{BrightnessPreference brightnessPreference,
ColorPreference colorPreference,
double displayScale,
bool enableWallpaper});
}
abstract mixin class _$ThemePreferencesCopyWith<$Res> implements $ThemePreferencesCopyWith<$Res> {
factory _$ThemePreferencesCopyWith(_ThemePreferences value, $Res Function(_ThemePreferences) _then) = __$ThemePreferencesCopyWithImpl;
@override @useResult
$Res call({
BrightnessPreference brightnessPreference, ColorPreference colorPreference, double displayScale, bool enableWallpaper
});
}
/// @nodoc
class __$ThemePreferencesCopyWithImpl<$Res>
implements _$ThemePreferencesCopyWith<$Res> {
@ -197,35 +262,19 @@ class __$ThemePreferencesCopyWithImpl<$Res>
final _ThemePreferences _self;
final $Res Function(_ThemePreferences) _then;
/// Create a copy of ThemePreferences
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$Res call({
Object? brightnessPreference = null,
Object? colorPreference = null,
Object? displayScale = null,
Object? enableWallpaper = null,
}) {
return _then(_ThemePreferences(
brightnessPreference: null == brightnessPreference
? _self.brightnessPreference
: brightnessPreference // ignore: cast_nullable_to_non_nullable
as BrightnessPreference,
colorPreference: null == colorPreference
? _self.colorPreference
: colorPreference // ignore: cast_nullable_to_non_nullable
as ColorPreference,
displayScale: null == displayScale
? _self.displayScale
: displayScale // ignore: cast_nullable_to_non_nullable
as double,
enableWallpaper: null == enableWallpaper
? _self.enableWallpaper
: enableWallpaper // ignore: cast_nullable_to_non_nullable
as bool,
));
}
/// Create a copy of ThemePreferences
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? brightnessPreference = null,Object? colorPreference = null,Object? displayScale = null,Object? enableWallpaper = null,}) {
return _then(_ThemePreferences(
brightnessPreference: null == brightnessPreference ? _self.brightnessPreference : brightnessPreference // ignore: cast_nullable_to_non_nullable
as BrightnessPreference,colorPreference: null == colorPreference ? _self.colorPreference : colorPreference // ignore: cast_nullable_to_non_nullable
as ColorPreference,displayScale: null == displayScale ? _self.displayScale : displayScale // ignore: cast_nullable_to_non_nullable
as double,enableWallpaper: null == enableWallpaper ? _self.enableWallpaper : enableWallpaper // ignore: cast_nullable_to_non_nullable
as bool,
));
}
}
// dart format on

View file

@ -7,15 +7,12 @@ import 'package:flutter_translate/flutter_translate.dart';
import '../theme.dart';
class EnterPasswordDialog extends StatefulWidget {
const EnterPasswordDialog({
this.matchPass,
this.description,
super.key,
});
final String? matchPass;
final String? description;
const EnterPasswordDialog({this.matchPass, this.description, super.key});
@override
State<EnterPasswordDialog> createState() => _EnterPasswordDialogState();
@ -52,70 +49,77 @@ class _EnterPasswordDialogState extends State<EnterPasswordDialog> {
final scale = theme.extension<ScaleScheme>()!;
return StyledDialog(
title: widget.matchPass == null
? translate('enter_password_dialog.enter_password')
: translate('enter_password_dialog.reenter_password'),
child: Form(
key: formKey,
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextField(
controller: passwordController,
focusNode: focusNode,
autofocus: true,
enableSuggestions: false,
obscureText: !_passwordVisible,
obscuringCharacter: '*',
inputFormatters: <TextInputFormatter>[
FilteringTextInputFormatter.singleLineFormatter
],
onSubmitted: (password) {
Navigator.pop(context, password);
},
onChanged: (_) {
setState(() {});
},
decoration: InputDecoration(
prefixIcon: widget.matchPass == null
? null
: Icon(Icons.check_circle,
color: passwordController.text == widget.matchPass
? scale.primaryScale.primary
: scale.grayScale.subtleBackground),
suffixIcon: IconButton(
icon: Icon(
_passwordVisible
? Icons.visibility
: Icons.visibility_off,
color: scale.primaryScale.appText,
title: widget.matchPass == null
? translate('enter_password_dialog.enter_password')
: translate('enter_password_dialog.reenter_password'),
child: Form(
key: formKey,
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextField(
controller: passwordController,
focusNode: focusNode,
autofocus: true,
enableSuggestions: false,
obscureText: !_passwordVisible,
obscuringCharacter: '*',
inputFormatters: <TextInputFormatter>[
FilteringTextInputFormatter.singleLineFormatter,
],
onSubmitted: (password) {
Navigator.pop(context, password);
},
onChanged: (_) {
setState(() {});
},
decoration: InputDecoration(
prefixIcon: widget.matchPass == null
? null
: Icon(
Icons.check_circle,
color: passwordController.text == widget.matchPass
? scale.primaryScale.primary
: scale.grayScale.subtleBackground,
),
onPressed: () {
setState(() {
_passwordVisible = !_passwordVisible;
});
},
),
)).paddingAll(16),
if (widget.description != null)
SizedBox(
width: 400,
child: Text(
widget.description!,
textAlign: TextAlign.center,
).paddingAll(16))
],
),
));
suffixIcon: IconButton(
icon: Icon(
_passwordVisible ? Icons.visibility : Icons.visibility_off,
color: scale.primaryScale.appText,
),
onPressed: () {
setState(() {
_passwordVisible = !_passwordVisible;
});
},
),
),
).paddingAll(16),
if (widget.description != null)
SizedBox(
width: 400,
child: Text(
widget.description!,
textAlign: TextAlign.center,
).paddingAll(16),
),
],
),
),
);
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties
..add(DiagnosticsProperty<TextEditingController>(
'passwordController', passwordController))
..add(
DiagnosticsProperty<TextEditingController>(
'passwordController',
passwordController,
),
)
..add(DiagnosticsProperty<FocusNode>('focusNode', focusNode))
..add(DiagnosticsProperty<GlobalKey<FormState>>('formKey', formKey));
}

View file

@ -8,15 +8,16 @@ import 'package:pinput/pinput.dart';
import '../theme.dart';
class EnterPinDialog extends StatefulWidget {
final bool reenter;
final String? description;
const EnterPinDialog({
required this.reenter,
required this.description,
super.key,
});
final bool reenter;
final String? description;
@override
State<EnterPinDialog> createState() => _EnterPinDialogState();
@ -69,69 +70,75 @@ class _EnterPinDialogState extends State<EnterPinDialog> {
/// Optionally you can use form to validate the Pinput
return StyledDialog(
title: !widget.reenter
? translate('enter_pin_dialog.enter_pin')
: translate('enter_pin_dialog.reenter_pin'),
child: Form(
key: formKey,
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Directionality(
// Specify direction if desired
textDirection: TextDirection.ltr,
child: Pinput(
controller: pinController,
focusNode: focusNode,
autofocus: true,
defaultPinTheme: defaultPinTheme,
enableSuggestions: false,
inputFormatters: <TextInputFormatter>[
FilteringTextInputFormatter.digitsOnly
],
hapticFeedbackType: HapticFeedbackType.lightImpact,
onCompleted: (pin) {
Navigator.pop(context, pin);
},
cursor: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Container(
margin: const EdgeInsets.only(bottom: 9),
width: 22,
height: 1,
color: focusedBorderColor,
),
],
),
focusedPinTheme: defaultPinTheme.copyWith(
height: 68,
width: 64,
decoration: defaultPinTheme.decoration!.copyWith(
border: Border.all(color: borderColor),
title: !widget.reenter
? translate('enter_pin_dialog.enter_pin')
: translate('enter_pin_dialog.reenter_pin'),
child: Form(
key: formKey,
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Directionality(
// Specify direction if desired
textDirection: TextDirection.ltr,
child: Pinput(
controller: pinController,
focusNode: focusNode,
autofocus: true,
defaultPinTheme: defaultPinTheme,
enableSuggestions: false,
inputFormatters: <TextInputFormatter>[
FilteringTextInputFormatter.digitsOnly,
],
hapticFeedbackType: HapticFeedbackType.lightImpact,
onCompleted: (pin) {
Navigator.pop(context, pin);
},
cursor: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Container(
margin: const EdgeInsets.only(bottom: 9),
width: 22,
height: 1,
color: focusedBorderColor,
),
],
),
focusedPinTheme: defaultPinTheme.copyWith(
height: 68,
width: 64,
decoration: defaultPinTheme.decoration!.copyWith(
border: Border.all(color: borderColor),
),
),
).paddingAll(16),
),
if (widget.description != null)
SizedBox(
width: 400,
child: Text(
widget.description!,
textAlign: TextAlign.center,
).paddingAll(16),
),
if (widget.description != null)
SizedBox(
width: 400,
child: Text(
widget.description!,
textAlign: TextAlign.center,
).paddingAll(16))
],
),
));
],
),
),
);
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties
..add(DiagnosticsProperty<TextEditingController>(
'pinController', pinController))
..add(
DiagnosticsProperty<TextEditingController>(
'pinController',
pinController,
),
)
..add(DiagnosticsProperty<FocusNode>('focusNode', focusNode))
..add(DiagnosticsProperty<GlobalKey<FormState>>('formKey', formKey));
}

View file

@ -2,11 +2,11 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
class PopControl extends StatelessWidget {
const PopControl({
required this.child,
required this.dismissible,
super.key,
});
final bool dismissible;
final Widget child;
const PopControl({required this.child, required this.dismissible, super.key});
void _doDismiss(BuildContext context) {
if (!dismissible) {
@ -25,15 +25,16 @@ class PopControl extends StatelessWidget {
}
return PopScope(
canPop: false,
onPopInvokedWithResult: (didPop, _) {
if (didPop) {
return;
}
_doDismiss(context);
canPop: false,
onPopInvokedWithResult: (didPop, _) {
if (didPop) {
return;
},
child: child);
}
_doDismiss(context);
return;
},
child: child,
);
}
@override
@ -41,24 +42,23 @@ class PopControl extends StatelessWidget {
super.debugFillProperties(properties);
properties.add(DiagnosticsProperty<bool>('dismissible', dismissible));
}
final bool dismissible;
final Widget child;
}
class PopControlDialogRoute<T> extends DialogRoute<T> {
PopControlDialogRoute(
{required super.context,
required super.builder,
super.themes,
super.barrierColor = Colors.black54,
super.barrierDismissible,
super.barrierLabel,
super.useSafeArea,
super.settings,
super.anchorPoint,
super.traversalEdgeBehavior})
: _barrierDismissible = barrierDismissible;
bool _barrierDismissible;
PopControlDialogRoute({
required super.context,
required super.builder,
super.themes,
super.barrierColor = Colors.black54,
super.barrierDismissible,
super.barrierLabel,
super.useSafeArea,
super.settings,
super.anchorPoint,
super.traversalEdgeBehavior,
}) : _barrierDismissible = barrierDismissible;
@override
bool get barrierDismissible => _barrierDismissible;
@ -67,8 +67,6 @@ class PopControlDialogRoute<T> extends DialogRoute<T> {
_barrierDismissible = d;
changedInternalState();
}
bool _barrierDismissible;
}
bool _debugIsActive(BuildContext context) {
@ -76,8 +74,9 @@ bool _debugIsActive(BuildContext context) {
throw FlutterError.fromParts(<DiagnosticsNode>[
ErrorSummary('This BuildContext is no longer valid.'),
ErrorDescription(
'The showPopControlDialog function context parameter is a '
'BuildContext that is no longer valid.'),
'The showPopControlDialog function context parameter is a '
'BuildContext that is no longer valid.',
),
ErrorHint(
'This can commonly occur when the showPopControlDialog function is '
'called after awaiting a Future. '
@ -103,29 +102,29 @@ Future<T?> showPopControlDialog<T>({
TraversalEdgeBehavior? traversalEdgeBehavior,
}) {
assert(_debugIsActive(context), 'debug is active check');
assert(debugCheckHasMaterialLocalizations(context),
'check has material localizations');
assert(
debugCheckHasMaterialLocalizations(context),
'check has material localizations',
);
final themes = InheritedTheme.capture(
from: context,
to: Navigator.of(
context,
rootNavigator: useRootNavigator,
).context,
to: Navigator.of(context, rootNavigator: useRootNavigator).context,
);
return Navigator.of(context, rootNavigator: useRootNavigator)
.push<T>(PopControlDialogRoute<T>(
context: context,
builder: builder,
barrierColor: barrierColor ?? Colors.black54,
barrierDismissible: barrierDismissible,
barrierLabel: barrierLabel,
useSafeArea: useSafeArea,
settings: routeSettings,
themes: themes,
anchorPoint: anchorPoint,
traversalEdgeBehavior:
traversalEdgeBehavior ?? TraversalEdgeBehavior.closedLoop,
));
return Navigator.of(context, rootNavigator: useRootNavigator).push<T>(
PopControlDialogRoute<T>(
context: context,
builder: builder,
barrierColor: barrierColor ?? Colors.black54,
barrierDismissible: barrierDismissible,
barrierLabel: barrierLabel,
useSafeArea: useSafeArea,
settings: routeSettings,
themes: themes,
anchorPoint: anchorPoint,
traversalEdgeBehavior:
traversalEdgeBehavior ?? TraversalEdgeBehavior.closedLoop,
),
);
}

Some files were not shown because too many files have changed in this diff Show more