mirror of
https://gitlab.com/veilid/veilidchat.git
synced 2025-01-23 14:01:06 -05:00
active conversations cubit and blocmapcubit
This commit is contained in:
parent
aa376a449d
commit
43dbf26cc0
@ -115,40 +115,41 @@ class ChatComponentState extends State<ChatComponent> {
|
|||||||
|
|
||||||
final activeChatCubit = context.watch<ActiveChatCubit>();
|
final activeChatCubit = context.watch<ActiveChatCubit>();
|
||||||
final contactListCubit = context.watch<ContactListCubit>();
|
final contactListCubit = context.watch<ContactListCubit>();
|
||||||
|
final activeAccountInfo = context.watch<ActiveAccountInfo>();
|
||||||
|
|
||||||
final activeChatContactKey = activeChatCubit.state;
|
final activeChatContactKey = activeChatCubit.state;
|
||||||
if (activeChatContactKey == null) {
|
if (activeChatContactKey == null) {
|
||||||
return const NoConversationWidget();
|
return const NoConversationWidget();
|
||||||
}
|
}
|
||||||
return contactListCubit.state.builder((context, contactList) {
|
return contactListCubit.state.builder((context, contactList) {
|
||||||
|
|
||||||
// Get active chat contact profile
|
// Get active chat contact profile
|
||||||
final activeChatContactIdx = contactList.indexWhere(
|
final activeChatContactIdx = contactList.indexWhere(
|
||||||
(c) => activeChatContactKey == c.remoteConversationRecordKey);
|
(c) => activeChatContactKey == c.remoteConversationRecordKey);
|
||||||
late final proto.Contact activeChatContact;
|
late final proto.Contact activeChatContact;
|
||||||
if (activeChatContactIdx == -1) {
|
if (activeChatContactIdx == -1) {
|
||||||
activeChatCubit.setActiveChat(null);
|
|
||||||
return const NoConversationWidget();
|
return const NoConversationWidget();
|
||||||
} else {
|
} else {
|
||||||
activeChatContact = contactList[activeChatContactIdx];
|
activeChatContact = contactList[activeChatContactIdx];
|
||||||
}
|
}
|
||||||
final contactName = activeChatContact.editedProfile.name;
|
final contactName = activeChatContact.editedProfile.name;
|
||||||
|
|
||||||
// Make a messages cubit for this conversation
|
// final protoMessages =
|
||||||
xxx
|
// ref.watch(activeConversationMessagesProvider).asData?.value;
|
||||||
|
// if (protoMessages == null) {
|
||||||
|
// return waitingPage(context);
|
||||||
|
// }
|
||||||
|
// final messages = <types.Message>[];
|
||||||
|
// for (final protoMessage in protoMessages) {
|
||||||
|
// final message = protoMessageToMessage(protoMessage);
|
||||||
|
// messages.insert(0, message);
|
||||||
|
// }
|
||||||
|
|
||||||
final protoMessages =
|
return BlocProvider(
|
||||||
ref.watch(activeConversationMessagesProvider).asData?.value;
|
create: (context) => MessagesCubit(
|
||||||
if (protoMessages == null) {
|
activeAccountInfo: activeAccountInfo,
|
||||||
return waitingPage(context);
|
remoteIdentityPublicKey: activeChatContact.identityPublicKey, localConversationRecordKey: activeChatContact.localConversationRecordKey, localMessagesRecordKey: activeChatContact.,
|
||||||
}
|
),
|
||||||
final messages = <types.Message>[];
|
child: DefaultTextStyle(
|
||||||
for (final protoMessage in protoMessages) {
|
|
||||||
final message = protoMessageToMessage(protoMessage);
|
|
||||||
messages.insert(0, message);
|
|
||||||
}
|
|
||||||
|
|
||||||
return DefaultTextStyle(
|
|
||||||
style: textTheme.bodySmall!,
|
style: textTheme.bodySmall!,
|
||||||
child: Align(
|
child: Align(
|
||||||
alignment: AlignmentDirectional.centerEnd,
|
alignment: AlignmentDirectional.centerEnd,
|
||||||
@ -204,7 +205,7 @@ class ChatComponentState extends State<ChatComponent> {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
));
|
)));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
24
lib/chat_list/cubits/active_conversations_cubit.dart
Normal file
24
lib/chat_list/cubits/active_conversations_cubit.dart
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import 'package:veilid_support/veilid_support.dart';
|
||||||
|
|
||||||
|
import '../../account_manager/account_manager.dart';
|
||||||
|
import '../../contacts/contacts.dart';
|
||||||
|
import '../../proto/proto.dart' as proto;
|
||||||
|
import '../../tools/tools.dart';
|
||||||
|
|
||||||
|
class ActiveConversationsCubit extends BlocMapCubit<TypedKey,
|
||||||
|
AsyncValue<ConversationState>, ConversationCubit> {
|
||||||
|
ActiveConversationsCubit({required ActiveAccountInfo activeAccountInfo})
|
||||||
|
: _activeAccountInfo = activeAccountInfo;
|
||||||
|
|
||||||
|
Future<void> addConversation({required proto.Contact contact}) async =>
|
||||||
|
add(() => MapEntry(
|
||||||
|
contact.remoteConversationRecordKey,
|
||||||
|
ConversationCubit(
|
||||||
|
activeAccountInfo: _activeAccountInfo,
|
||||||
|
remoteIdentityPublicKey: contact.identityPublicKey,
|
||||||
|
localConversationRecordKey: contact.localConversationRecordKey,
|
||||||
|
remoteConversationRecordKey: contact.remoteConversationRecordKey,
|
||||||
|
)));
|
||||||
|
|
||||||
|
final ActiveAccountInfo _activeAccountInfo;
|
||||||
|
}
|
@ -1 +1,2 @@
|
|||||||
|
export 'active_conversations_cubit.dart';
|
||||||
export 'chat_list_cubit.dart';
|
export 'chat_list_cubit.dart';
|
||||||
|
@ -24,6 +24,8 @@ class ChatSingleContactItemWidget extends StatelessWidget {
|
|||||||
final scale = theme.extension<ScaleScheme>()!;
|
final scale = theme.extension<ScaleScheme>()!;
|
||||||
|
|
||||||
final activeChatCubit = context.watch<ActiveChatCubit>();
|
final activeChatCubit = context.watch<ActiveChatCubit>();
|
||||||
|
final activeConversationsCubit = context.watch<ActiveConversationsCubit>();
|
||||||
|
|
||||||
final remoteConversationRecordKey =
|
final remoteConversationRecordKey =
|
||||||
proto.TypedKeyProto.fromProto(_contact.remoteConversationRecordKey);
|
proto.TypedKeyProto.fromProto(_contact.remoteConversationRecordKey);
|
||||||
final selected = activeChatCubit.state == remoteConversationRecordKey;
|
final selected = activeChatCubit.state == remoteConversationRecordKey;
|
||||||
@ -67,6 +69,8 @@ class ChatSingleContactItemWidget extends StatelessWidget {
|
|||||||
// component is not dragged.
|
// component is not dragged.
|
||||||
child: ListTile(
|
child: ListTile(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
|
xxx deal with async
|
||||||
|
activeConversationsCubit.addConversation(contact: _contact);
|
||||||
activeChatCubit.setActiveChat(remoteConversationRecordKey);
|
activeChatCubit.setActiveChat(remoteConversationRecordKey);
|
||||||
},
|
},
|
||||||
title: Text(_contact.editedProfile.name),
|
title: Text(_contact.editedProfile.name),
|
||||||
|
@ -9,6 +9,7 @@ import '../../../account_manager/account_manager.dart';
|
|||||||
import '../../../chat/chat.dart';
|
import '../../../chat/chat.dart';
|
||||||
import '../../../chat_list/chat_list.dart';
|
import '../../../chat_list/chat_list.dart';
|
||||||
import '../../../contact_invitation/contact_invitation.dart';
|
import '../../../contact_invitation/contact_invitation.dart';
|
||||||
|
import '../../../contacts/contacts.dart';
|
||||||
import '../../../theme/theme.dart';
|
import '../../../theme/theme.dart';
|
||||||
import '../../../tools/tools.dart';
|
import '../../../tools/tools.dart';
|
||||||
import 'main_pager/main_pager.dart';
|
import 'main_pager/main_pager.dart';
|
||||||
@ -114,10 +115,17 @@ class HomeAccountReadyState extends State<HomeAccountReady>
|
|||||||
create: (context) => ContactInvitationListCubit(
|
create: (context) => ContactInvitationListCubit(
|
||||||
activeAccountInfo: activeAccountInfo,
|
activeAccountInfo: activeAccountInfo,
|
||||||
account: accountData.value)),
|
account: accountData.value)),
|
||||||
|
BlocProvider(
|
||||||
|
create: (context) => ContactListCubit(
|
||||||
|
activeAccountInfo: activeAccountInfo,
|
||||||
|
account: accountData.value)),
|
||||||
BlocProvider(
|
BlocProvider(
|
||||||
create: (context) => ChatListCubit(
|
create: (context) => ChatListCubit(
|
||||||
activeAccountInfo: activeAccountInfo,
|
activeAccountInfo: activeAccountInfo,
|
||||||
account: accountData.value)),
|
account: accountData.value)),
|
||||||
|
BlocProvider(
|
||||||
|
create: (context) => ActiveConversationsCubit(
|
||||||
|
activeAccountInfo: activeAccountInfo)),
|
||||||
BlocProvider(create: (context) => ActiveChatCubit(null))
|
BlocProvider(create: (context) => ActiveChatCubit(null))
|
||||||
],
|
],
|
||||||
child: responsiveVisibility(
|
child: responsiveVisibility(
|
||||||
|
96
lib/tools/bloc_map_cubit.dart
Normal file
96
lib/tools/bloc_map_cubit.dart
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:bloc/bloc.dart';
|
||||||
|
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||||
|
import 'package:veilid_support/veilid_support.dart';
|
||||||
|
|
||||||
|
typedef BlocMapState<K, S> = IMap<K, S>;
|
||||||
|
|
||||||
|
class _ItemEntry<S, B> {
|
||||||
|
_ItemEntry({required this.bloc, required this.subscription});
|
||||||
|
final B bloc;
|
||||||
|
final StreamSubscription<S> subscription;
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class BlocMapCubit<K, S, B extends BlocBase<S>>
|
||||||
|
extends Cubit<BlocMapState<K, S>> {
|
||||||
|
BlocMapCubit()
|
||||||
|
: _entries = {},
|
||||||
|
_tagLock = AsyncTagLock(),
|
||||||
|
super(IMap<K, S>());
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> close() async {
|
||||||
|
await _entries.values.map((e) => e.subscription.cancel()).wait;
|
||||||
|
await _entries.values.map((e) => e.bloc.close()).wait;
|
||||||
|
await super.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> add(MapEntry<K, B> Function() create) {
|
||||||
|
// Create new element
|
||||||
|
final newElement = create();
|
||||||
|
final key = newElement.key;
|
||||||
|
final bloc = newElement.value;
|
||||||
|
|
||||||
|
return _tagLock.protect(key, closure: () async {
|
||||||
|
// Remove entry with the same key if it exists
|
||||||
|
await _internalRemove(key);
|
||||||
|
|
||||||
|
// Add entry with this key
|
||||||
|
_entries[key] = _ItemEntry(
|
||||||
|
bloc: bloc,
|
||||||
|
subscription: bloc.stream.listen((data) {
|
||||||
|
// Add sub-cubit's state to the map state
|
||||||
|
emit(state.add(key, data));
|
||||||
|
}));
|
||||||
|
|
||||||
|
emit(state.add(key, bloc.state));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _internalRemove(K key) async {
|
||||||
|
final sub = _entries.remove(key);
|
||||||
|
if (sub != null) {
|
||||||
|
await sub.subscription.cancel();
|
||||||
|
await sub.bloc.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> remove(K key) => _tagLock.protect(key, closure: () async {
|
||||||
|
await _internalRemove(key);
|
||||||
|
emit(state.remove(key));
|
||||||
|
});
|
||||||
|
|
||||||
|
R operate<R>(K key, {required R Function(B bloc) closure}) {
|
||||||
|
final bloc = _entries[key]!.bloc;
|
||||||
|
return closure(bloc);
|
||||||
|
}
|
||||||
|
|
||||||
|
R? tryOperate<R>(K key, {required R Function(B bloc) closure}) {
|
||||||
|
final entry = _entries[key];
|
||||||
|
if (entry == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return closure(entry.bloc);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<R> operateAsync<R>(K key,
|
||||||
|
{required Future<R> Function(B bloc) closure}) =>
|
||||||
|
_tagLock.protect(key, closure: () async {
|
||||||
|
final bloc = _entries[key]!.bloc;
|
||||||
|
return closure(bloc);
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<R?> tryOperateAsync<R>(K key,
|
||||||
|
{required Future<R> Function(B bloc) closure}) =>
|
||||||
|
_tagLock.protect(key, closure: () async {
|
||||||
|
final entry = _entries[key];
|
||||||
|
if (entry == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return closure(entry.bloc);
|
||||||
|
});
|
||||||
|
|
||||||
|
final Map<K, _ItemEntry<S, B>> _entries;
|
||||||
|
final AsyncTagLock<K> _tagLock;
|
||||||
|
}
|
@ -13,13 +13,13 @@ abstract class StreamWrapperCubit<State> extends Cubit<AsyncValue<State>> {
|
|||||||
onError: (Object error, StackTrace stackTrace) {
|
onError: (Object error, StackTrace stackTrace) {
|
||||||
emit(AsyncValue.error(error, stackTrace));
|
emit(AsyncValue.error(error, stackTrace));
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> close() async {
|
Future<void> close() async {
|
||||||
await _subscription.cancel();
|
await _subscription.cancel();
|
||||||
await super.close();
|
await super.close();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
late final StreamSubscription<State> _subscription;
|
late final StreamSubscription<State> _subscription;
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
export 'animations.dart';
|
export 'animations.dart';
|
||||||
|
export 'cubit_map.dart';
|
||||||
export 'enter_password.dart';
|
export 'enter_password.dart';
|
||||||
export 'enter_pin.dart';
|
export 'enter_pin.dart';
|
||||||
export 'loggy.dart';
|
export 'loggy.dart';
|
||||||
|
@ -39,6 +39,15 @@ class AsyncTagLock<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<R> protect<R>(T tag, {required Future<R> Function() closure}) async {
|
||||||
|
await lockTag(tag);
|
||||||
|
try {
|
||||||
|
return await closure();
|
||||||
|
} finally {
|
||||||
|
unlockTag(tag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
final Mutex _tableLock;
|
final Mutex _tableLock;
|
||||||
final Map<T, _AsyncTagLockEntry> _locks;
|
final Map<T, _AsyncTagLockEntry> _locks;
|
||||||
|
Loading…
Reference in New Issue
Block a user