This commit is contained in:
Christien Rioux 2024-06-16 22:12:24 -04:00
parent 2ccad50f9a
commit 360ba436f8
29 changed files with 501 additions and 317 deletions

View File

@ -1,23 +1,23 @@
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:veilid_support/veilid_support.dart';
import '../models/models.dart';
import '../repository/account_repository.dart';
class ActiveAccountInfoCubit extends Cubit<AccountInfo> {
ActiveAccountInfoCubit(AccountRepository accountRepository)
class AccountInfoCubit extends Cubit<AccountInfo> {
AccountInfoCubit(
AccountRepository accountRepository, TypedKey superIdentityRecordKey)
: _accountRepository = accountRepository,
super(accountRepository
.getAccountInfo(accountRepository.getActiveLocalAccount())) {
super(accountRepository.getAccountInfo(superIdentityRecordKey)) {
// Subscribe to streams
_accountRepositorySubscription = _accountRepository.stream.listen((change) {
switch (change) {
case AccountRepositoryChange.activeLocalAccount:
case AccountRepositoryChange.localAccounts:
case AccountRepositoryChange.userLogins:
emit(accountRepository
.getAccountInfo(accountRepository.getActiveLocalAccount()));
emit(accountRepository.getAccountInfo(superIdentityRecordKey));
break;
}
});

View File

@ -11,7 +11,7 @@ typedef AccountRecordState = proto.Account;
/// The saved state of a VeilidChat Account on the DHT
/// Used to synchronize status, profile, and options for a specific account
/// across multiple clients. This DHT record is the 'source of truth' for an
/// account and is privately encrypted with an owned recrod from the 'userLogin'
/// 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(

View File

@ -0,0 +1,35 @@
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:veilid_support/veilid_support.dart';
import '../repository/account_repository.dart';
class ActiveLocalAccountCubit extends Cubit<TypedKey?> {
ActiveLocalAccountCubit(AccountRepository accountRepository)
: _accountRepository = accountRepository,
super(accountRepository.getActiveLocalAccount()) {
// Subscribe to streams
_accountRepositorySubscription = _accountRepository.stream.listen((change) {
switch (change) {
case AccountRepositoryChange.activeLocalAccount:
emit(_accountRepository.getActiveLocalAccount());
break;
// Ignore these
case AccountRepositoryChange.localAccounts:
case AccountRepositoryChange.userLogins:
break;
}
});
}
@override
Future<void> close() async {
await super.close();
await _accountRepositorySubscription.cancel();
}
final AccountRepository _accountRepository;
late final StreamSubscription<AccountRepositoryChange>
_accountRepositorySubscription;
}

View File

@ -1,5 +1,6 @@
export 'account_info_cubit.dart';
export 'account_record_cubit.dart';
export 'account_records_bloc_map_cubit.dart';
export 'active_account_info_cubit.dart';
export 'active_local_account_cubit.dart';
export 'local_accounts_cubit.dart';
export 'user_logins_cubit.dart';

View File

@ -7,7 +7,7 @@ enum AccountInfoStatus {
noAccount,
accountInvalid,
accountLocked,
accountReady,
accountUnlocked,
}
@immutable

View File

@ -129,7 +129,7 @@ class AccountRepository {
// Got account, decrypted and decoded
return AccountInfo(
status: AccountInfoStatus.accountReady,
status: AccountInfoStatus.accountUnlocked,
active: active,
unlockedAccountInfo:
UnlockedAccountInfo(localAccount: localAccount, userLogin: userLogin),

View File

@ -70,9 +70,6 @@ class _EditAccountPageState extends State<EditAccountPage> {
@override
Widget build(BuildContext context) {
final displayModalHUD = _isInAsyncCall;
final accountRecordsCubit = context.watch<AccountRecordsBlocMapCubit>();
final accountRecordCubit = accountRecordsCubit
.operate(widget.superIdentityRecordKey, closure: (c) => c);
return Scaffold(
// resizeToAvoidBottomInset: false,
@ -118,9 +115,15 @@ class _EditAccountPageState extends State<EditAccountPage> {
_isInAsyncCall = true;
});
try {
// Update account profile DHT record
// This triggers ConversationCubits to update
await accountRecordCubit.updateProfile(newProfile);
// Look up account cubit for this specific account
final accountRecordsCubit =
context.read<AccountRecordsBlocMapCubit>();
await accountRecordsCubit.operateAsync(
widget.superIdentityRecordKey, closure: (c) async {
// Update account profile DHT record
// This triggers ConversationCubits to update
await c.updateProfile(newProfile);
});
// Update local account profile
await AccountRepository.instance.editAccountProfile(

View File

@ -11,6 +11,7 @@ import 'package:provider/provider.dart';
import 'package:veilid_support/veilid_support.dart';
import 'account_manager/account_manager.dart';
import 'account_manager/cubits/active_local_account_cubit.dart';
import 'init.dart';
import 'layout/splash.dart';
import 'router/router.dart';
@ -122,9 +123,9 @@ class VeilidChatApp extends StatelessWidget {
create: (context) =>
UserLoginsCubit(AccountRepository.instance),
),
BlocProvider<ActiveAccountInfoCubit>(
BlocProvider<ActiveLocalAccountCubit>(
create: (context) =>
ActiveAccountInfoCubit(AccountRepository.instance),
ActiveLocalAccountCubit(AccountRepository.instance),
),
BlocProvider<PreferencesCubit>(
create: (context) =>

View File

@ -1,19 +1,13 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:veilid_support/veilid_support.dart';
import '../../router/router.dart';
// XXX: if we ever want to have more than one chat 'open', we should put the
// operations and state for that here.
class ActiveChatCubit extends Cubit<TypedKey?> {
ActiveChatCubit(super.initialState, {required RouterCubit routerCubit})
: _routerCubit = routerCubit;
ActiveChatCubit(super.initialState);
void setActiveChat(TypedKey? activeChatLocalConversationRecordKey) {
emit(activeChatLocalConversationRecordKey);
_routerCubit.setHasActiveChat(activeChatLocalConversationRecordKey != null);
}
final RouterCubit _routerCubit;
}

View File

@ -27,15 +27,19 @@ const metadataKeyAttachments = 'attachments';
class ChatComponentCubit extends Cubit<ChatComponentState> {
ChatComponentCubit._({
required Locator locator,
required List<ActiveConversationCubit> conversationCubits,
required SingleContactMessagesCubit messagesCubit,
required types.User localUser,
required IMap<TypedKey, types.User> remoteUsers,
}) : _messagesCubit = messagesCubit,
}) : _locator = locator,
_conversationCubits = conversationCubits,
_messagesCubit = messagesCubit,
super(ChatComponentState(
chatKey: GlobalKey<ChatState>(),
scrollController: AutoScrollController(),
localUser: localUser,
remoteUsers: remoteUsers,
localUser: null,
remoteUsers: const IMap.empty(),
historicalRemoteUsers: const IMap.empty(),
unknownUsers: const IMap.empty(),
messageWindow: const AsyncLoading(),
title: '',
)) {
@ -43,58 +47,40 @@ class ChatComponentCubit extends Cubit<ChatComponentState> {
_initWait.add(_init);
}
// ignore: prefer_constructors_over_static_methods
static ChatComponentCubit singleContact(
{required Locator locator,
required ActiveConversationCubit activeConversationCubit,
required SingleContactMessagesCubit messagesCubit}) {
// Get account info
final unlockedAccountInfo =
locator<ActiveAccountInfoCubit>().state.unlockedAccountInfo!;
final account = locator<AccountRecordCubit>().state.asData!.value;
// Make local 'User'
final localUserIdentityKey = unlockedAccountInfo.identityTypedPublicKey;
final localUser = types.User(
id: localUserIdentityKey.toString(),
firstName: account.profile.name,
metadata: {metadataKeyIdentityPublicKey: localUserIdentityKey});
// Make remote 'User's
final remoteUsers = {
activeConversationState.contact.identityPublicKey.toVeilid(): types.User(
id: activeConversationState.contact.identityPublicKey
.toVeilid()
.toString(),
firstName: activeConversationState.contact.displayName,
metadata: {
metadataKeyIdentityPublicKey:
activeConversationState.contact.identityPublicKey.toVeilid()
})
}.toIMap();
return ChatComponentCubit._(
messagesCubit: messagesCubit,
localUser: localUser,
remoteUsers: remoteUsers,
);
}
factory ChatComponentCubit.singleContact(
{required Locator locator,
required ActiveConversationCubit activeConversationCubit,
required SingleContactMessagesCubit messagesCubit}) =>
ChatComponentCubit._(
locator: locator,
conversationCubits: [activeConversationCubit],
messagesCubit: messagesCubit,
);
Future<void> _init() async {
_messagesSubscription = _messagesCubit.stream.listen((messagesState) {
emit(state.copyWith(
messageWindow: _convertMessages(messagesState),
));
});
emit(state.copyWith(
messageWindow: _convertMessages(_messagesCubit.state),
title: _getTitle(),
));
// Get local user info and account record cubit
final unlockedAccountInfo =
_locator<AccountInfoCubit>().state.unlockedAccountInfo!;
_localUserIdentityKey = unlockedAccountInfo.identityTypedPublicKey;
_localUserAccountRecordCubit = _locator<AccountRecordCubit>();
// Subscribe to local user info
_localUserAccountRecordSubscription = _localUserAccountRecordCubit.stream
.listen(_onChangedLocalUserAccountRecord);
_onChangedLocalUserAccountRecord(_localUserAccountRecordCubit.state);
// Subscribe to remote user info
await _updateConversationSubscriptions();
// Subscribe to messages
_messagesSubscription = _messagesCubit.stream.listen(_onChangedMessages);
_onChangedMessages(_messagesCubit.state);
}
@override
Future<void> close() async {
await _initWait();
await _localUserAccountRecordSubscription.cancel();
await _messagesSubscription.cancel();
await super.close();
}
@ -159,23 +145,130 @@ class ChatComponentCubit extends Cubit<ChatComponentState> {
////////////////////////////////////////////////////////////////////////////
// Private Implementation
String _getTitle() {
if (state.remoteUsers.length == 1) {
final remoteUser = state.remoteUsers.values.first;
return remoteUser.firstName ?? '<unnamed>';
} else {
return '<group chat with ${state.remoteUsers.length} users>';
void _onChangedLocalUserAccountRecord(AsyncValue<proto.Account> avAccount) {
final account = avAccount.asData?.value;
if (account == null) {
emit(state.copyWith(localUser: null));
return;
}
// Make local 'User'
final localUser = types.User(
id: _localUserIdentityKey.toString(),
firstName: account.profile.name,
metadata: {metadataKeyIdentityPublicKey: _localUserIdentityKey});
emit(state.copyWith(localUser: localUser));
}
types.Message? _messageStateToChatMessage(MessageState message) {
void _onChangedMessages(
AsyncValue<WindowState<MessageState>> avMessagesState) {
emit(_convertMessages(state, avMessagesState));
}
void _onChangedConversation(
TypedKey remoteIdentityPublicKey,
AsyncValue<ActiveConversationState> avConversationState,
) {
//
}
types.User _convertRemoteUser(TypedKey remoteIdentityPublicKey,
ActiveConversationState activeConversationState) =>
types.User(
id: remoteIdentityPublicKey.toString(),
firstName: activeConversationState.contact.displayName,
metadata: {metadataKeyIdentityPublicKey: remoteIdentityPublicKey});
types.User _convertUnknownUser(TypedKey remoteIdentityPublicKey) =>
types.User(
id: remoteIdentityPublicKey.toString(),
firstName: '<$remoteIdentityPublicKey>',
metadata: {metadataKeyIdentityPublicKey: remoteIdentityPublicKey});
Future<void> _updateConversationSubscriptions() async {
// Get existing subscription keys and state
final existing = _conversationSubscriptions.keys.toList();
var currentRemoteUsersState = state.remoteUsers;
// Process cubit list
for (final cc in _conversationCubits) {
// Get the remote identity key
final remoteIdentityPublicKey = cc.input.remoteIdentityPublicKey;
// If the cubit is already being listened to we have nothing to do
if (existing.remove(remoteIdentityPublicKey)) {
continue;
}
// If the cubit is not already being listened to we should do that
_conversationSubscriptions[remoteIdentityPublicKey] = cc.stream.listen(
(avConv) => _onChangedConversation(remoteIdentityPublicKey, avConv));
final activeConversationState = cc.state.asData?.value;
if (activeConversationState != null) {
final remoteUser = _convertRemoteUser(
remoteIdentityPublicKey, activeConversationState);
currentRemoteUsersState =
currentRemoteUsersState.add(remoteIdentityPublicKey, 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);
cancels.add(_conversationSubscriptions.remove(deadUser)!.cancel());
}
await cancels.wait;
// Emit change to remote users state
emit(_updateTitle(state.copyWith(remoteUsers: currentRemoteUsersState)));
}
ChatComponentState _updateTitle(ChatComponentState currentState) {
if (currentState.remoteUsers.length == 0) {
return currentState.copyWith(title: 'Empty Chat');
}
if (currentState.remoteUsers.length == 1) {
final remoteUser = currentState.remoteUsers.values.first;
return currentState.copyWith(title: remoteUser.firstName ?? '<unnamed>');
}
return currentState.copyWith(
title: '<group chat with ${state.remoteUsers.length} users>');
}
(ChatComponentState, types.Message?) _messageStateToChatMessage(
ChatComponentState currentState, MessageState message) {
final authorIdentityPublicKey = message.content.author.toVeilid();
final author =
state.remoteUsers[authorIdentityPublicKey] ?? state.localUser;
late final types.User author;
if (authorIdentityPublicKey == _localUserIdentityKey &&
currentState.localUser != null) {
author = currentState.localUser!;
} else {
final remoteUser = currentState.remoteUsers[authorIdentityPublicKey];
if (remoteUser != null) {
author = remoteUser;
} else {
final historicalRemoteUser =
currentState.historicalRemoteUsers[authorIdentityPublicKey];
if (historicalRemoteUser != null) {
author = historicalRemoteUser;
} else {
final unknownRemoteUser =
currentState.unknownUsers[authorIdentityPublicKey];
if (unknownRemoteUser != null) {
author = unknownRemoteUser;
} else {
final unknownUser = _convertUnknownUser(authorIdentityPublicKey);
currentState = currentState.copyWith(
unknownUsers: currentState.unknownUsers
.add(authorIdentityPublicKey, unknownUser));
author = unknownUser;
}
}
}
}
types.Status? status;
if (message.sendState != null) {
assert(author == state.localUser,
assert(author.id == _localUserIdentityKey.toString(),
'send state should only be on sent messages');
switch (message.sendState!) {
case MessageSendState.sending:
@ -198,7 +291,7 @@ class ChatComponentCubit extends Cubit<ChatComponentState> {
text: contextText.text,
showStatus: status != null,
status: status);
return textMessage;
return (currentState, textMessage);
case proto.Message_Kind.secret:
case proto.Message_Kind.delete:
case proto.Message_Kind.erase:
@ -207,17 +300,24 @@ class ChatComponentCubit extends Cubit<ChatComponentState> {
case proto.Message_Kind.membership:
case proto.Message_Kind.moderation:
case proto.Message_Kind.notSet:
return null;
return (currentState, null);
}
}
AsyncValue<WindowState<types.Message>> _convertMessages(
ChatComponentState _convertMessages(ChatComponentState currentState,
AsyncValue<WindowState<MessageState>> avMessagesState) {
// Clear out unknown users
currentState = state.copyWith(unknownUsers: const IMap.empty());
final asError = avMessagesState.asError;
if (asError != null) {
return AsyncValue.error(asError.error, asError.stackTrace);
return currentState.copyWith(
unknownUsers: const IMap.empty(),
messageWindow: AsyncValue.error(asError.error, asError.stackTrace));
} else if (avMessagesState.asLoading != null) {
return const AsyncValue.loading();
return currentState.copyWith(
unknownUsers: const IMap.empty(),
messageWindow: const AsyncValue.loading());
}
final messagesState = avMessagesState.asData!.value;
@ -225,7 +325,9 @@ class ChatComponentCubit extends Cubit<ChatComponentState> {
final chatMessages = <types.Message>[];
final tsSet = <String>{};
for (final message in messagesState.window) {
final chatMessage = _messageStateToChatMessage(message);
final (newState, chatMessage) =
_messageStateToChatMessage(currentState, message);
currentState = newState;
if (chatMessage == null) {
continue;
}
@ -238,12 +340,13 @@ class ChatComponentCubit extends Cubit<ChatComponentState> {
assert(false, 'should not have duplicate id');
}
}
return AsyncValue.data(WindowState<types.Message>(
window: chatMessages.toIList(),
length: messagesState.length,
windowTail: messagesState.windowTail,
windowCount: messagesState.windowCount,
follow: messagesState.follow));
return currentState.copyWith(
messageWindow: AsyncValue.data(WindowState<types.Message>(
window: chatMessages.toIList(),
length: messagesState.length,
windowTail: messagesState.windowTail,
windowCount: messagesState.windowCount,
follow: messagesState.follow)));
}
void _addTextMessage(
@ -271,7 +374,18 @@ class ChatComponentCubit extends Cubit<ChatComponentState> {
////////////////////////////////////////////////////////////////////////////
final _initWait = WaitSet<void>();
final Locator _locator;
final List<ActiveConversationCubit> _conversationCubits;
final SingleContactMessagesCubit _messagesCubit;
late final TypedKey _localUserIdentityKey;
late final AccountRecordCubit _localUserAccountRecordCubit;
late final StreamSubscription<AsyncValue<proto.Account>>
_localUserAccountRecordSubscription;
final Map<TypedKey, StreamSubscription<AsyncValue<ActiveConversationState>>>
_conversationSubscriptions = {};
late StreamSubscription<SingleContactMessagesState> _messagesSubscription;
double scrollOffset = 0;
}

View File

@ -88,7 +88,7 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
// Initialize everything
Future<void> _init() async {
_unlockedAccountInfo =
_locator<ActiveAccountInfoCubit>().state.unlockedAccountInfo!;
_locator<AccountInfoCubit>().state.unlockedAccountInfo!;
_unsentMessagesQueue = PersistentQueue<proto.Message>(
table: 'SingleContactUnsentMessages',

View File

@ -20,9 +20,13 @@ class ChatComponentState with _$ChatComponentState {
// ScrollController for the chat
required AutoScrollController scrollController,
// Local user
required User localUser,
// Remote users
required User? localUser,
// Active remote users
required IMap<TypedKey, User> remoteUsers,
// Historical remote users
required IMap<TypedKey, User> historicalRemoteUsers,
// Unknown users
required IMap<TypedKey, User> unknownUsers,
// Messages state
required AsyncValue<WindowState<Message>> messageWindow,
// Title of the chat

View File

@ -21,8 +21,13 @@ mixin _$ChatComponentState {
throw _privateConstructorUsedError; // ScrollController for the chat
AutoScrollController get scrollController =>
throw _privateConstructorUsedError; // Local user
User get localUser => throw _privateConstructorUsedError; // Remote users
User? get localUser =>
throw _privateConstructorUsedError; // Active remote users
IMap<Typed<FixedEncodedString43>, User> get remoteUsers =>
throw _privateConstructorUsedError; // Historical remote users
IMap<Typed<FixedEncodedString43>, User> get historicalRemoteUsers =>
throw _privateConstructorUsedError; // Unknown users
IMap<Typed<FixedEncodedString43>, User> get unknownUsers =>
throw _privateConstructorUsedError; // Messages state
AsyncValue<WindowState<Message>> get messageWindow =>
throw _privateConstructorUsedError; // Title of the chat
@ -42,8 +47,10 @@ abstract class $ChatComponentStateCopyWith<$Res> {
$Res call(
{GlobalKey<ChatState> chatKey,
AutoScrollController scrollController,
User localUser,
User? localUser,
IMap<Typed<FixedEncodedString43>, User> remoteUsers,
IMap<Typed<FixedEncodedString43>, User> historicalRemoteUsers,
IMap<Typed<FixedEncodedString43>, User> unknownUsers,
AsyncValue<WindowState<Message>> messageWindow,
String title});
@ -65,8 +72,10 @@ class _$ChatComponentStateCopyWithImpl<$Res, $Val extends ChatComponentState>
$Res call({
Object? chatKey = null,
Object? scrollController = null,
Object? localUser = null,
Object? localUser = freezed,
Object? remoteUsers = null,
Object? historicalRemoteUsers = null,
Object? unknownUsers = null,
Object? messageWindow = null,
Object? title = null,
}) {
@ -79,14 +88,22 @@ class _$ChatComponentStateCopyWithImpl<$Res, $Val extends ChatComponentState>
? _value.scrollController
: scrollController // ignore: cast_nullable_to_non_nullable
as AutoScrollController,
localUser: null == localUser
localUser: freezed == localUser
? _value.localUser
: localUser // ignore: cast_nullable_to_non_nullable
as User,
as User?,
remoteUsers: null == remoteUsers
? _value.remoteUsers
: remoteUsers // ignore: cast_nullable_to_non_nullable
as IMap<Typed<FixedEncodedString43>, User>,
historicalRemoteUsers: null == historicalRemoteUsers
? _value.historicalRemoteUsers
: historicalRemoteUsers // ignore: cast_nullable_to_non_nullable
as IMap<Typed<FixedEncodedString43>, User>,
unknownUsers: null == unknownUsers
? _value.unknownUsers
: unknownUsers // ignore: cast_nullable_to_non_nullable
as IMap<Typed<FixedEncodedString43>, User>,
messageWindow: null == messageWindow
? _value.messageWindow
: messageWindow // ignore: cast_nullable_to_non_nullable
@ -119,8 +136,10 @@ abstract class _$$ChatComponentStateImplCopyWith<$Res>
$Res call(
{GlobalKey<ChatState> chatKey,
AutoScrollController scrollController,
User localUser,
User? localUser,
IMap<Typed<FixedEncodedString43>, User> remoteUsers,
IMap<Typed<FixedEncodedString43>, User> historicalRemoteUsers,
IMap<Typed<FixedEncodedString43>, User> unknownUsers,
AsyncValue<WindowState<Message>> messageWindow,
String title});
@ -141,8 +160,10 @@ class __$$ChatComponentStateImplCopyWithImpl<$Res>
$Res call({
Object? chatKey = null,
Object? scrollController = null,
Object? localUser = null,
Object? localUser = freezed,
Object? remoteUsers = null,
Object? historicalRemoteUsers = null,
Object? unknownUsers = null,
Object? messageWindow = null,
Object? title = null,
}) {
@ -155,14 +176,22 @@ class __$$ChatComponentStateImplCopyWithImpl<$Res>
? _value.scrollController
: scrollController // ignore: cast_nullable_to_non_nullable
as AutoScrollController,
localUser: null == localUser
localUser: freezed == localUser
? _value.localUser
: localUser // ignore: cast_nullable_to_non_nullable
as User,
as User?,
remoteUsers: null == remoteUsers
? _value.remoteUsers
: remoteUsers // ignore: cast_nullable_to_non_nullable
as IMap<Typed<FixedEncodedString43>, User>,
historicalRemoteUsers: null == historicalRemoteUsers
? _value.historicalRemoteUsers
: historicalRemoteUsers // ignore: cast_nullable_to_non_nullable
as IMap<Typed<FixedEncodedString43>, User>,
unknownUsers: null == unknownUsers
? _value.unknownUsers
: unknownUsers // ignore: cast_nullable_to_non_nullable
as IMap<Typed<FixedEncodedString43>, User>,
messageWindow: null == messageWindow
? _value.messageWindow
: messageWindow // ignore: cast_nullable_to_non_nullable
@ -183,6 +212,8 @@ class _$ChatComponentStateImpl implements _ChatComponentState {
required this.scrollController,
required this.localUser,
required this.remoteUsers,
required this.historicalRemoteUsers,
required this.unknownUsers,
required this.messageWindow,
required this.title});
@ -194,10 +225,16 @@ class _$ChatComponentStateImpl implements _ChatComponentState {
final AutoScrollController scrollController;
// Local user
@override
final User localUser;
// Remote users
final User? localUser;
// Active remote users
@override
final IMap<Typed<FixedEncodedString43>, User> remoteUsers;
// Historical remote users
@override
final IMap<Typed<FixedEncodedString43>, User> historicalRemoteUsers;
// Unknown users
@override
final IMap<Typed<FixedEncodedString43>, User> unknownUsers;
// Messages state
@override
final AsyncValue<WindowState<Message>> messageWindow;
@ -207,7 +244,7 @@ class _$ChatComponentStateImpl implements _ChatComponentState {
@override
String toString() {
return 'ChatComponentState(chatKey: $chatKey, scrollController: $scrollController, localUser: $localUser, remoteUsers: $remoteUsers, messageWindow: $messageWindow, title: $title)';
return 'ChatComponentState(chatKey: $chatKey, scrollController: $scrollController, localUser: $localUser, remoteUsers: $remoteUsers, historicalRemoteUsers: $historicalRemoteUsers, unknownUsers: $unknownUsers, messageWindow: $messageWindow, title: $title)';
}
@override
@ -222,14 +259,26 @@ class _$ChatComponentStateImpl implements _ChatComponentState {
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, chatKey, scrollController,
localUser, remoteUsers, messageWindow, title);
int get hashCode => Object.hash(
runtimeType,
chatKey,
scrollController,
localUser,
remoteUsers,
historicalRemoteUsers,
unknownUsers,
messageWindow,
title);
@JsonKey(ignore: true)
@override
@ -243,8 +292,11 @@ abstract class _ChatComponentState implements ChatComponentState {
const factory _ChatComponentState(
{required final GlobalKey<ChatState> chatKey,
required final AutoScrollController scrollController,
required final User localUser,
required final User? localUser,
required final IMap<Typed<FixedEncodedString43>, User> remoteUsers,
required final IMap<Typed<FixedEncodedString43>, User>
historicalRemoteUsers,
required final IMap<Typed<FixedEncodedString43>, User> unknownUsers,
required final AsyncValue<WindowState<Message>> messageWindow,
required final String title}) = _$ChatComponentStateImpl;
@ -253,9 +305,13 @@ abstract class _ChatComponentState implements ChatComponentState {
@override // ScrollController for the chat
AutoScrollController get scrollController;
@override // Local user
User get localUser;
@override // Remote users
User? get localUser;
@override // Active remote users
IMap<Typed<FixedEncodedString43>, User> get remoteUsers;
@override // Historical remote users
IMap<Typed<FixedEncodedString43>, User> get historicalRemoteUsers;
@override // Unknown users
IMap<Typed<FixedEncodedString43>, User> get unknownUsers;
@override // Messages state
AsyncValue<WindowState<Message>> get messageWindow;
@override // Title of the chat

View File

@ -8,7 +8,6 @@ import 'package:flutter_chat_types/flutter_chat_types.dart' as types;
import 'package:flutter_chat_ui/flutter_chat_ui.dart';
import 'package:veilid_support/veilid_support.dart';
import '../../account_manager/account_manager.dart';
import '../../conversation/conversation.dart';
import '../../theme/theme.dart';
import '../chat.dart';
@ -147,6 +146,11 @@ class ChatComponentWidget extends StatelessWidget {
final chatComponentCubit = context.watch<ChatComponentCubit>();
final chatComponentState = chatComponentCubit.state;
final localUser = chatComponentState.localUser;
if (localUser == null) {
return waitingPage();
}
final messageWindow = chatComponentState.messageWindow.asData?.value;
if (messageWindow == null) {
return chatComponentState.messageWindow.buildNotData();
@ -269,7 +273,7 @@ class ChatComponentWidget extends StatelessWidget {
_handleSendPressed(chatComponentCubit, pt),
//showUserAvatars: false,
//showUserNames: true,
user: chatComponentState.localUser,
user: localUser,
emptyState: const EmptyChatWidget())),
),
),

View File

@ -69,7 +69,7 @@ class ContactInvitationListCubit
final contactRequestWriter = await crcs.generateKeyPair();
final activeAccountInfo =
_locator<ActiveAccountInfoCubit>().state.unlockedAccountInfo!;
_locator<AccountInfoCubit>().state.unlockedAccountInfo!;
final profile = _locator<AccountRecordCubit>().state.asData!.value.profile;
final idcs = await activeAccountInfo.identityCryptoSystem;
@ -247,7 +247,8 @@ class ContactInvitationListCubit
await (await pool.openRecordRead(contactRequestInboxKey,
debugName: 'ContactInvitationListCubit::validateInvitation::'
'ContactRequestInbox',
parent: _accountRecordKey))
parent: pool.getParentRecordKey(contactRequestInboxKey) ??
_accountRecordKey))
.maybeDeleteScope(!isSelf, (contactRequestInbox) async {
//
final contactRequest = await contactRequestInbox

View File

@ -23,7 +23,7 @@ class ContactRequestInboxCubit
final pool = DHTRecordPool.instance;
final unlockedAccountInfo =
locator<ActiveAccountInfoCubit>().state.unlockedAccountInfo!;
locator<AccountInfoCubit>().state.unlockedAccountInfo!;
final accountRecordKey = unlockedAccountInfo.accountRecordKey;
final writerSecret = contactInvitationRecord.writerSecret.toVeilid();

View File

@ -31,7 +31,7 @@ class ValidContactInvitation {
final pool = DHTRecordPool.instance;
try {
final unlockedAccountInfo =
_locator<ActiveAccountInfoCubit>().state.unlockedAccountInfo!;
_locator<AccountInfoCubit>().state.unlockedAccountInfo!;
final accountRecordKey = unlockedAccountInfo.accountRecordKey;
final identityPublicKey = unlockedAccountInfo.identityPublicKey;
@ -43,7 +43,8 @@ class ValidContactInvitation {
return (await pool.openRecordWrite(_contactRequestInboxKey, _writer,
debugName: 'ValidContactInvitation::accept::'
'ContactRequestInbox',
parent: accountRecordKey))
parent: pool.getParentRecordKey(_contactRequestInboxKey) ??
accountRecordKey))
// ignore: prefer_expression_function_bodies
.maybeDeleteScope(!isSelf, (contactRequestInbox) async {
// Create local conversation key for this
@ -96,7 +97,7 @@ class ValidContactInvitation {
final pool = DHTRecordPool.instance;
final unlockedAccountInfo =
_locator<ActiveAccountInfoCubit>().state.unlockedAccountInfo!;
_locator<AccountInfoCubit>().state.unlockedAccountInfo!;
final accountRecordKey = unlockedAccountInfo.accountRecordKey;
final identityPublicKey = unlockedAccountInfo.identityPublicKey;

View File

@ -74,7 +74,7 @@ class InvitationDialogState extends State<InvitationDialog> {
Future<void> _onAccept() async {
final navigator = Navigator.of(context);
final activeAccountInfo = widget._locator<UnlockedAccountInfo>();
final accountInfo = widget._locator<AccountInfoCubit>().state;
final contactList = widget._locator<ContactListCubit>();
setState(() {
@ -86,7 +86,7 @@ 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 = activeAccountInfo.identityPublicKey ==
final isSelf = accountInfo.unlockedAccountInfo!.identityPublicKey ==
acceptedContact.remoteIdentity.currentInstance.publicKey;
if (!isSelf) {
await contactList.createContact(

View File

@ -4,6 +4,7 @@ import 'package:awesome_extensions/awesome_extensions.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_translate/flutter_translate.dart';
import 'package:provider/provider.dart';
import 'package:veilid_support/veilid_support.dart';
import '../../theme/theme.dart';
@ -11,29 +12,23 @@ import '../../tools/tools.dart';
import 'invitation_dialog.dart';
class PasteInvitationDialog extends StatefulWidget {
const PasteInvitationDialog({required this.modalContext, super.key});
const PasteInvitationDialog({required Locator locator, super.key})
: _locator = locator;
@override
PasteInvitationDialogState createState() => PasteInvitationDialogState();
static Future<void> show(BuildContext context) async {
final modalContext = context;
final locator = context.read;
await showPopControlDialog<void>(
context: context,
builder: (context) => StyledDialog(
title: translate('paste_invitation_dialog.title'),
child: PasteInvitationDialog(modalContext: modalContext)));
child: PasteInvitationDialog(locator: locator)));
}
final BuildContext modalContext;
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties
.add(DiagnosticsProperty<BuildContext>('modalContext', modalContext));
}
final Locator _locator;
}
class PasteInvitationDialogState extends State<PasteInvitationDialog> {
@ -138,7 +133,7 @@ class PasteInvitationDialogState extends State<PasteInvitationDialog> {
// ignore: prefer_expression_function_bodies
Widget build(BuildContext context) {
return InvitationDialog(
modalContext: widget.modalContext,
locator: widget._locator,
onValidationCancelled: onValidationCancelled,
onValidationSuccess: onValidationSuccess,
onValidationFailed: onValidationFailed,

View File

@ -9,6 +9,7 @@ import 'package:flutter_translate/flutter_translate.dart';
import 'package:image/image.dart' as img;
import 'package:mobile_scanner/mobile_scanner.dart';
import 'package:pasteboard/pasteboard.dart';
import 'package:provider/provider.dart';
import 'package:zxing2/qrcode.dart';
import '../../theme/theme.dart';
@ -102,28 +103,22 @@ class ScannerOverlay extends CustomPainter {
}
class ScanInvitationDialog extends StatefulWidget {
const ScanInvitationDialog({required this.modalContext, super.key});
const ScanInvitationDialog({required Locator locator, super.key})
: _locator = locator;
@override
ScanInvitationDialogState createState() => ScanInvitationDialogState();
static Future<void> show(BuildContext context) async {
final modalContext = context;
final locator = context.read;
await showPopControlDialog<void>(
context: context,
builder: (context) => StyledDialog(
title: translate('scan_invitation_dialog.title'),
child: ScanInvitationDialog(modalContext: modalContext)));
child: ScanInvitationDialog(locator: locator)));
}
final BuildContext modalContext;
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties
.add(DiagnosticsProperty<BuildContext>('modalContext', modalContext));
}
final Locator _locator;
}
class ScanInvitationDialogState extends State<ScanInvitationDialog> {
@ -396,7 +391,7 @@ class ScanInvitationDialogState extends State<ScanInvitationDialog> {
// ignore: prefer_expression_function_bodies
Widget build(BuildContext context) {
return InvitationDialog(
modalContext: widget.modalContext,
locator: widget._locator,
onValidationCancelled: onValidationCancelled,
onValidationSuccess: onValidationSuccess,
onValidationFailed: onValidationFailed,

View File

@ -46,7 +46,7 @@ class ConversationCubit extends Cubit<AsyncValue<ConversationState>> {
_remoteConversationRecordKey = remoteConversationRecordKey,
super(const AsyncValue.loading()) {
final unlockedAccountInfo =
_locator<ActiveAccountInfoCubit>().state.unlockedAccountInfo!;
_locator<AccountInfoCubit>().state.unlockedAccountInfo!;
_accountRecordKey = unlockedAccountInfo.accountRecordKey;
_identityWriter = unlockedAccountInfo.identityWriter;
@ -76,7 +76,8 @@ class ConversationCubit extends Cubit<AsyncValue<ConversationState>> {
final crypto = await _cachedConversationCrypto();
final record = await pool.openRecordRead(_remoteConversationRecordKey,
debugName: 'ConversationCubit::RemoteConversation',
parent: _accountRecordKey,
parent: pool.getParentRecordKey(_remoteConversationRecordKey) ??
_accountRecordKey,
crypto: crypto);
return record;
});
@ -117,7 +118,7 @@ class ConversationCubit extends Cubit<AsyncValue<ConversationState>> {
final crypto = await _cachedConversationCrypto();
final account = _locator<AccountRecordCubit>().state.asData!.value;
final unlockedAccountInfo =
_locator<ActiveAccountInfoCubit>().state.unlockedAccountInfo!;
_locator<AccountInfoCubit>().state.unlockedAccountInfo!;
final accountRecordKey = unlockedAccountInfo.accountRecordKey;
final writer = unlockedAccountInfo.identityWriter;
@ -359,7 +360,7 @@ class ConversationCubit extends Cubit<AsyncValue<ConversationState>> {
return conversationCrypto;
}
final unlockedAccountInfo =
_locator<ActiveAccountInfoCubit>().state.unlockedAccountInfo!;
_locator<AccountInfoCubit>().state.unlockedAccountInfo!;
conversationCrypto = await unlockedAccountInfo
.makeConversationCrypto(_remoteIdentityPublicKey);
_conversationCrypto = conversationCrypto;
@ -368,6 +369,8 @@ class ConversationCubit extends Cubit<AsyncValue<ConversationState>> {
////////////////////////////////////////////////////////////////////////////
// Fields
TypedKey get remoteIdentityPublicKey => _remoteIdentityPublicKey;
final Locator _locator;
late final TypedKey _accountRecordKey;
late final KeyPair _identityWriter;

View File

@ -0,0 +1,37 @@
import 'dart:async';
import 'package:async_tools/async_tools.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:veilid_support/veilid_support.dart';
import '../../account_manager/account_manager.dart';
class ActiveAccountPageControllerWrapper {
ActiveAccountPageControllerWrapper(Locator locator, int initialPage) {
pageController = PageController(initialPage: initialPage, keepPage: false);
final activeLocalAccountCubit = locator<ActiveLocalAccountCubit>();
_subscription =
activeLocalAccountCubit.stream.listen((activeLocalAccountRecordKey) {
singleFuture(this, () async {
final localAccounts = locator<LocalAccountsCubit>().state;
final activeIndex = localAccounts.indexWhere(
(x) => x.superIdentity.recordKey == activeLocalAccountRecordKey);
if (pageController.page == activeIndex) {
return;
}
await pageController.animateToPage(activeIndex,
duration: const Duration(milliseconds: 250),
curve: Curves.fastOutSlowIn);
});
});
}
void dispose() {
unawaited(_subscription.cancel());
}
late PageController pageController;
late StreamSubscription<TypedKey?> _subscription;
}

View File

@ -1,5 +1,6 @@
import 'package:async_tools/async_tools.dart';
import 'package:awesome_extensions/awesome_extensions.dart';
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/flutter_svg.dart';
@ -102,15 +103,12 @@ class _DrawerMenuState extends State<DrawerMenu> {
}
Widget _getAccountList(
{required TypedKey? activeLocalAccount,
{required IList<LocalAccount> localAccounts,
required TypedKey? activeLocalAccount,
required AccountRecordsBlocMapState accountRecords}) {
final theme = Theme.of(context);
final scaleScheme = theme.extension<ScaleScheme>()!;
final accountRepo = AccountRepository.instance;
final localAccounts = accountRepo.getLocalAccounts();
//final userLogins = accountRepo.getUserLogins();
final loggedInAccounts = <Widget>[];
final loggedOutAccounts = <Widget>[];
@ -234,8 +232,9 @@ class _DrawerMenuState extends State<DrawerMenu> {
final theme = Theme.of(context);
final scale = theme.extension<ScaleScheme>()!;
//final textTheme = theme.textTheme;
final localAccounts = context.watch<LocalAccountsCubit>().state;
final accountRecords = context.watch<AccountRecordsBlocMapCubit>().state;
final activeLocalAccount = context.watch<ActiveAccountInfoCubit>().state;
final activeLocalAccount = context.watch<ActiveLocalAccountCubit>().state;
final gradient = LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
@ -276,8 +275,8 @@ class _DrawerMenuState extends State<DrawerMenu> {
])),
const Spacer(),
_getAccountList(
activeLocalAccount:
activeLocalAccount.unlockedAccountInfo?.superIdentityRecordKey,
localAccounts: localAccounts,
activeLocalAccount: activeLocalAccount,
accountRecords: accountRecords),
_getBottomButtons(),
const Spacer(),

View File

@ -1,7 +1,8 @@
export 'active_account_page_controller_wrapper.dart';
export 'drawer_menu/drawer_menu.dart';
export 'home_account_invalid.dart';
export 'home_account_locked.dart';
export 'home_account_missing.dart';
export 'home_account_ready/home_account_ready.dart';
export 'home_no_active.dart';
export 'home_shell.dart';
export 'home_screen.dart';

View File

@ -5,6 +5,7 @@ import 'package:flutter/rendering.dart';
import 'package:flutter_animate/flutter_animate.dart';
import 'package:flutter_translate/flutter_translate.dart';
import 'package:preload_page_view/preload_page_view.dart';
import 'package:provider/provider.dart';
import 'package:stylish_bottom_bar/stylish_bottom_bar.dart';
import '../../../../chat/chat.dart';
@ -117,7 +118,7 @@ class MainPagerState extends State<MainPager> with TickerProviderStateMixin {
style: TextStyle(fontSize: 24),
),
content: ScanInvitationDialog(
modalContext: context,
locator: context.read,
));
});
}

View File

@ -14,24 +14,24 @@ import '../../contact_invitation/contact_invitation.dart';
import '../../contacts/contacts.dart';
import '../../conversation/conversation.dart';
import '../../proto/proto.dart' as proto;
import '../../router/router.dart';
import '../../theme/theme.dart';
import '../../tools/tools.dart';
import 'active_account_page_controller_wrapper.dart';
import 'drawer_menu/drawer_menu.dart';
import 'home_account_invalid.dart';
import 'home_account_locked.dart';
import 'home_account_missing.dart';
import 'home_account_ready/home_account_ready.dart';
import 'home_no_active.dart';
class HomeShell extends StatefulWidget {
const HomeShell({required this.child, super.key});
class HomeScreen extends StatefulWidget {
const HomeScreen({super.key});
@override
HomeShellState createState() => HomeShellState();
final Widget child;
HomeScreenState createState() => HomeScreenState();
}
class HomeShellState extends State<HomeShell> {
class HomeScreenState extends State<HomeScreen> {
@override
void initState() {
super.initState();
@ -84,8 +84,22 @@ class HomeShellState extends State<HomeShell> {
});
}
Widget _buildActiveAccount(BuildContext context) {
final accountRecordKey = context.select<ActiveAccountInfoCubit, TypedKey>(
Widget _buildAccountReadyDeviceSpecific(BuildContext context) {
final hasActiveChat = context.watch<ActiveChatCubit>().state != null;
if (responsiveVisibility(
context: context,
tablet: false,
tabletLandscape: false,
desktop: false)) {
if (hasActiveChat) {
return const HomeAccountReadyChat();
}
}
return const HomeAccountReadyMain();
}
Widget _buildUnlockedAccount(BuildContext context) {
final accountRecordKey = context.select<AccountInfoCubit, TypedKey>(
(c) => c.state.unlockedAccountInfo!.accountRecordKey);
final contactListRecordPointer =
context.select<AccountRecordCubit, OwnedDHTRecordPointer?>(
@ -124,8 +138,9 @@ class HomeShellState extends State<HomeShell> {
)),
// Chat Cubits
BlocProvider(
create: (context) => ActiveChatCubit(null,
routerCubit: context.read<RouterCubit>())),
create: (context) => ActiveChatCubit(
null,
)),
BlocProvider(
create: (context) => ChatListCubit(
locator: context.read,
@ -146,27 +161,21 @@ class HomeShellState extends State<HomeShell> {
WaitingInvitationsBlocMapState>(
listener: _invitationStatusListener,
)
], child: widget.child));
], child: Builder(builder: _buildAccountReadyDeviceSpecific)));
}
Widget _buildWithLogin(BuildContext context) {
Widget _buildAccount(BuildContext context) {
// Get active account info status
final (
accountInfoStatus,
accountInfoActive,
superIdentityRecordKey
) = context
.select<ActiveAccountInfoCubit, (AccountInfoStatus, bool, TypedKey?)>(
(c) => (
c.state.status,
c.state.active,
c.state.unlockedAccountInfo?.superIdentityRecordKey
));
if (!accountInfoActive) {
// If no logged in user is active, show the loading panel
return const HomeNoActive();
}
.select<AccountInfoCubit, (AccountInfoStatus, bool, TypedKey?)>((c) => (
c.state.status,
c.state.active,
c.state.unlockedAccountInfo?.superIdentityRecordKey
));
switch (accountInfoStatus) {
case AccountInfoStatus.noAccount:
@ -175,7 +184,7 @@ class HomeShellState extends State<HomeShell> {
return const HomeAccountInvalid();
case AccountInfoStatus.accountLocked:
return const HomeAccountLocked();
case AccountInfoStatus.accountReady:
case AccountInfoStatus.accountUnlocked:
// Get the current active account record cubit
final activeAccountRecordCubit =
@ -190,10 +199,50 @@ class HomeShellState extends State<HomeShell> {
return MultiBlocProvider(providers: [
BlocProvider<AccountRecordCubit>.value(
value: activeAccountRecordCubit),
], child: Builder(builder: _buildActiveAccount));
], child: Builder(builder: _buildUnlockedAccount));
}
}
Widget _buildAccountPageView(BuildContext context) {
final localAccounts = context.watch<LocalAccountsCubit>().state;
final activeLocalAccountCubit = context.read<ActiveLocalAccountCubit>();
final activeIndex = localAccounts.indexWhere(
(x) => x.superIdentity.recordKey == activeLocalAccountCubit.state);
if (activeIndex == -1) {
return const HomeNoActive();
}
return Provider<ActiveAccountPageControllerWrapper>(
lazy: false,
create: (context) =>
ActiveAccountPageControllerWrapper(context.read, activeIndex),
dispose: (context, value) {
value.dispose();
},
child: Builder(
builder: (context) => PageView.builder(
itemCount: localAccounts.length,
onPageChanged: (idx) {
singleFuture(this, () async {
await AccountRepository.instance.switchToAccount(
localAccounts[idx].superIdentity.recordKey);
});
},
controller: context
.read<ActiveAccountPageControllerWrapper>()
.pageController,
itemBuilder: (context, index) {
final localAccount = localAccounts[index];
return BlocProvider<AccountInfoCubit>(
key: ValueKey(localAccount.superIdentity.recordKey),
create: (context) => AccountInfoCubit(
AccountRepository.instance,
localAccount.superIdentity.recordKey),
child: Builder(builder: _buildAccount));
})));
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
@ -219,7 +268,7 @@ class HomeShellState extends State<HomeShell> {
color: scale.primaryScale.activeElementBackground),
child: Provider<ZoomDrawerController>.value(
value: _zoomDrawerController,
child: Builder(builder: _buildWithLogin))),
child: Builder(builder: _buildAccountPageView))),
borderRadius: 24,
showShadow: true,
angle: 0,
@ -239,50 +288,3 @@ class HomeShellState extends State<HomeShell> {
final _singleInvitationStatusProcessor =
SingleStateProcessor<WaitingInvitationsBlocMapState>();
}
// class HomeAccountReadyShell extends StatefulWidget {
// factory HomeAccountReadyShell(
// {required BuildContext context, required Widget child, Key? key}) {
// // These must exist in order for the account to
// // be considered 'ready' for this widget subtree
// final unlockedAccountInfo = context.watch<UnlockedAccountInfo>();
// final routerCubit = context.read<RouterCubit>();
// return HomeAccountReadyShell._(
// unlockedAccountInfo: unlockedAccountInfo,
// routerCubit: routerCubit,
// key: key,
// child: child);
// }
// const HomeAccountReadyShell._(
// {required this.unlockedAccountInfo,
// required this.routerCubit,
// required this.child,
// super.key});
// @override
// HomeAccountReadyShellState createState() => HomeAccountReadyShellState();
// final Widget child;
// final UnlockedAccountInfo unlockedAccountInfo;
// final RouterCubit routerCubit;
// @override
// void debugFillProperties(DiagnosticPropertiesBuilder properties) {
// super.debugFillProperties(properties);
// properties
// ..add(DiagnosticsProperty<UnlockedAccountInfo>(
// 'unlockedAccountInfo', unlockedAccountInfo))
// ..add(DiagnosticsProperty<RouterCubit>('routerCubit', routerCubit));
// }
// }
// class HomeAccountReadyShellState extends State<HomeAccountReadyShell> {
// final SingleStateProcessor<WaitingInvitationsBlocMapState>
// _singleInvitationStatusProcessor = SingleStateProcessor();
// @override
// void initState() {
// super.initState();
// }
// }

View File

@ -20,13 +20,12 @@ part 'router_cubit.freezed.dart';
part 'router_cubit.g.dart';
final _rootNavKey = GlobalKey<NavigatorState>(debugLabel: 'rootNavKey');
final _homeNavKey = GlobalKey<NavigatorState>(debugLabel: 'homeNavKey');
@freezed
class RouterState with _$RouterState {
const factory RouterState(
{required bool hasAnyAccount,
required bool hasActiveChat}) = _RouterState;
const factory RouterState({
required bool hasAnyAccount,
}) = _RouterState;
factory RouterState.fromJson(dynamic json) =>
_$RouterStateFromJson(json as Map<String, dynamic>);
@ -36,7 +35,6 @@ class RouterCubit extends Cubit<RouterState> {
RouterCubit(AccountRepository accountRepository)
: super(RouterState(
hasAnyAccount: accountRepository.getLocalAccounts().isNotEmpty,
hasActiveChat: false,
)) {
// Subscribe to repository streams
_accountRepositorySubscription = accountRepository.stream.listen((event) {
@ -52,10 +50,6 @@ class RouterCubit extends Cubit<RouterState> {
});
}
void setHasActiveChat(bool active) {
emit(state.copyWith(hasActiveChat: active));
}
@override
Future<void> close() async {
await _accountRepositorySubscription.cancel();
@ -64,19 +58,9 @@ class RouterCubit extends Cubit<RouterState> {
/// Our application routes
List<RouteBase> get routes => [
ShellRoute(
navigatorKey: _homeNavKey,
builder: (context, state, child) => HomeShell(child: child),
routes: [
GoRoute(
path: '/',
builder: (context, state) => const HomeAccountReadyMain(),
),
GoRoute(
path: '/chat',
builder: (context, state) => const HomeAccountReadyChat(),
),
],
GoRoute(
path: '/',
builder: (context, state) => const HomeScreen(),
),
GoRoute(
path: '/edit_account',
@ -116,31 +100,6 @@ class RouterCubit extends Cubit<RouterState> {
if (!state.hasAnyAccount) {
return '/new_account';
}
if (responsiveVisibility(
context: context,
tablet: false,
tabletLandscape: false,
desktop: false)) {
if (state.hasActiveChat) {
return '/chat';
}
}
return null;
case '/chat':
if (!state.hasAnyAccount) {
return '/new_account';
}
if (responsiveVisibility(
context: context,
tablet: false,
tabletLandscape: false,
desktop: false)) {
if (!state.hasActiveChat) {
return '/';
}
} else {
return '/';
}
return null;
case '/new_account':
return null;

View File

@ -21,7 +21,6 @@ RouterState _$RouterStateFromJson(Map<String, dynamic> json) {
/// @nodoc
mixin _$RouterState {
bool get hasAnyAccount => throw _privateConstructorUsedError;
bool get hasActiveChat => throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
@ -35,7 +34,7 @@ abstract class $RouterStateCopyWith<$Res> {
RouterState value, $Res Function(RouterState) then) =
_$RouterStateCopyWithImpl<$Res, RouterState>;
@useResult
$Res call({bool hasAnyAccount, bool hasActiveChat});
$Res call({bool hasAnyAccount});
}
/// @nodoc
@ -52,17 +51,12 @@ class _$RouterStateCopyWithImpl<$Res, $Val extends RouterState>
@override
$Res call({
Object? hasAnyAccount = null,
Object? hasActiveChat = null,
}) {
return _then(_value.copyWith(
hasAnyAccount: null == hasAnyAccount
? _value.hasAnyAccount
: hasAnyAccount // ignore: cast_nullable_to_non_nullable
as bool,
hasActiveChat: null == hasActiveChat
? _value.hasActiveChat
: hasActiveChat // ignore: cast_nullable_to_non_nullable
as bool,
) as $Val);
}
}
@ -75,7 +69,7 @@ abstract class _$$RouterStateImplCopyWith<$Res>
__$$RouterStateImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({bool hasAnyAccount, bool hasActiveChat});
$Res call({bool hasAnyAccount});
}
/// @nodoc
@ -90,17 +84,12 @@ class __$$RouterStateImplCopyWithImpl<$Res>
@override
$Res call({
Object? hasAnyAccount = null,
Object? hasActiveChat = null,
}) {
return _then(_$RouterStateImpl(
hasAnyAccount: null == hasAnyAccount
? _value.hasAnyAccount
: hasAnyAccount // ignore: cast_nullable_to_non_nullable
as bool,
hasActiveChat: null == hasActiveChat
? _value.hasActiveChat
: hasActiveChat // ignore: cast_nullable_to_non_nullable
as bool,
));
}
}
@ -108,20 +97,17 @@ class __$$RouterStateImplCopyWithImpl<$Res>
/// @nodoc
@JsonSerializable()
class _$RouterStateImpl with DiagnosticableTreeMixin implements _RouterState {
const _$RouterStateImpl(
{required this.hasAnyAccount, required this.hasActiveChat});
const _$RouterStateImpl({required this.hasAnyAccount});
factory _$RouterStateImpl.fromJson(Map<String, dynamic> json) =>
_$$RouterStateImplFromJson(json);
@override
final bool hasAnyAccount;
@override
final bool hasActiveChat;
@override
String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) {
return 'RouterState(hasAnyAccount: $hasAnyAccount, hasActiveChat: $hasActiveChat)';
return 'RouterState(hasAnyAccount: $hasAnyAccount)';
}
@override
@ -129,8 +115,7 @@ class _$RouterStateImpl with DiagnosticableTreeMixin implements _RouterState {
super.debugFillProperties(properties);
properties
..add(DiagnosticsProperty('type', 'RouterState'))
..add(DiagnosticsProperty('hasAnyAccount', hasAnyAccount))
..add(DiagnosticsProperty('hasActiveChat', hasActiveChat));
..add(DiagnosticsProperty('hasAnyAccount', hasAnyAccount));
}
@override
@ -139,14 +124,12 @@ class _$RouterStateImpl with DiagnosticableTreeMixin implements _RouterState {
(other.runtimeType == runtimeType &&
other is _$RouterStateImpl &&
(identical(other.hasAnyAccount, hasAnyAccount) ||
other.hasAnyAccount == hasAnyAccount) &&
(identical(other.hasActiveChat, hasActiveChat) ||
other.hasActiveChat == hasActiveChat));
other.hasAnyAccount == hasAnyAccount));
}
@JsonKey(ignore: true)
@override
int get hashCode => Object.hash(runtimeType, hasAnyAccount, hasActiveChat);
int get hashCode => Object.hash(runtimeType, hasAnyAccount);
@JsonKey(ignore: true)
@override
@ -163,9 +146,8 @@ class _$RouterStateImpl with DiagnosticableTreeMixin implements _RouterState {
}
abstract class _RouterState implements RouterState {
const factory _RouterState(
{required final bool hasAnyAccount,
required final bool hasActiveChat}) = _$RouterStateImpl;
const factory _RouterState({required final bool hasAnyAccount}) =
_$RouterStateImpl;
factory _RouterState.fromJson(Map<String, dynamic> json) =
_$RouterStateImpl.fromJson;
@ -173,8 +155,6 @@ abstract class _RouterState implements RouterState {
@override
bool get hasAnyAccount;
@override
bool get hasActiveChat;
@override
@JsonKey(ignore: true)
_$$RouterStateImplCopyWith<_$RouterStateImpl> get copyWith =>
throw _privateConstructorUsedError;

View File

@ -9,11 +9,9 @@ part of 'router_cubit.dart';
_$RouterStateImpl _$$RouterStateImplFromJson(Map<String, dynamic> json) =>
_$RouterStateImpl(
hasAnyAccount: json['has_any_account'] as bool,
hasActiveChat: json['has_active_chat'] as bool,
);
Map<String, dynamic> _$$RouterStateImplToJson(_$RouterStateImpl instance) =>
<String, dynamic>{
'has_any_account': instance.hasAnyAccount,
'has_active_chat': instance.hasActiveChat,
};