mirror of
https://gitlab.com/veilid/veilidchat.git
synced 2025-01-11 07:39:32 -05:00
busy handling
This commit is contained in:
parent
43b01c7555
commit
c6f017b0d1
@ -1,6 +1,7 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:async_tools/async_tools.dart';
|
import 'package:async_tools/async_tools.dart';
|
||||||
|
import 'package:bloc_tools/bloc_tools.dart';
|
||||||
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:veilid_support/veilid_support.dart';
|
import 'package:veilid_support/veilid_support.dart';
|
||||||
@ -61,9 +62,10 @@ class MessagesCubit extends Cubit<AsyncValue<IList<proto.Message>>> {
|
|||||||
await super.close();
|
await super.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
void updateLocalMessagesState(AsyncValue<IList<proto.Message>> avmessages) {
|
void updateLocalMessagesState(
|
||||||
|
BlocBusyState<AsyncValue<IList<proto.Message>>> avmessages) {
|
||||||
// Updated local messages from online just update the state immediately
|
// Updated local messages from online just update the state immediately
|
||||||
emit(avmessages);
|
emit(avmessages.state);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _updateRemoteMessagesStateAsync(_MessageQueueEntry entry) async {
|
Future<void> _updateRemoteMessagesStateAsync(_MessageQueueEntry entry) async {
|
||||||
@ -97,16 +99,17 @@ class MessagesCubit extends Cubit<AsyncValue<IList<proto.Message>>> {
|
|||||||
// Insert at this position
|
// Insert at this position
|
||||||
if (!skip) {
|
if (!skip) {
|
||||||
// Insert into dht backing array
|
// Insert into dht backing array
|
||||||
await _localMessagesCubit!.shortArray
|
await _localMessagesCubit!.operate((shortArray) =>
|
||||||
.tryInsertItem(pos, newMessage.writeToBuffer());
|
shortArray.tryInsertItem(pos, newMessage.writeToBuffer()));
|
||||||
// Insert into local copy as well for this operation
|
// Insert into local copy as well for this operation
|
||||||
localMessages = localMessages.insert(pos, newMessage);
|
localMessages = localMessages.insert(pos, newMessage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void updateRemoteMessagesState(AsyncValue<IList<proto.Message>> avmessages) {
|
void updateRemoteMessagesState(
|
||||||
final remoteMessages = avmessages.data?.value;
|
BlocBusyState<AsyncValue<IList<proto.Message>>> avmessages) {
|
||||||
|
final remoteMessages = avmessages.state.data?.value;
|
||||||
if (remoteMessages == null) {
|
if (remoteMessages == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -171,7 +174,8 @@ class MessagesCubit extends Cubit<AsyncValue<IList<proto.Message>>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> addMessage({required proto.Message message}) async {
|
Future<void> addMessage({required proto.Message message}) async {
|
||||||
await _localMessagesCubit!.shortArray.tryAddItem(message.writeToBuffer());
|
await _localMessagesCubit!.operate(
|
||||||
|
(shortArray) => shortArray.tryAddItem(message.writeToBuffer()));
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<DHTRecordCrypto> getMessagesCrypto() async {
|
Future<DHTRecordCrypto> getMessagesCrypto() async {
|
||||||
|
@ -48,7 +48,8 @@ class ChatComponent extends StatelessWidget {
|
|||||||
if (accountRecordInfo == null) {
|
if (accountRecordInfo == null) {
|
||||||
return debugPage('should always have an account record here');
|
return debugPage('should always have an account record here');
|
||||||
}
|
}
|
||||||
final contactList = context.watch<ContactListCubit>().state.data?.value;
|
final contactList =
|
||||||
|
context.watch<ContactListCubit>().state.state.data?.value;
|
||||||
if (contactList == null) {
|
if (contactList == null) {
|
||||||
return debugPage('should always have a contact list here');
|
return debugPage('should always have a contact list here');
|
||||||
}
|
}
|
||||||
|
@ -36,7 +36,9 @@ typedef ActiveConversationsBlocMapState
|
|||||||
// Automatically follows the state of a ChatListCubit.
|
// Automatically follows the state of a ChatListCubit.
|
||||||
class ActiveConversationsBlocMapCubit extends BlocMapCubit<TypedKey,
|
class ActiveConversationsBlocMapCubit extends BlocMapCubit<TypedKey,
|
||||||
AsyncValue<ActiveConversationState>, ActiveConversationCubit>
|
AsyncValue<ActiveConversationState>, ActiveConversationCubit>
|
||||||
with StateFollower<AsyncValue<IList<proto.Chat>>, TypedKey, proto.Chat> {
|
with
|
||||||
|
StateFollower<BlocBusyState<AsyncValue<IList<proto.Chat>>>, TypedKey,
|
||||||
|
proto.Chat> {
|
||||||
ActiveConversationsBlocMapCubit(
|
ActiveConversationsBlocMapCubit(
|
||||||
{required ActiveAccountInfo activeAccountInfo,
|
{required ActiveAccountInfo activeAccountInfo,
|
||||||
required ContactListCubit contactListCubit})
|
required ContactListCubit contactListCubit})
|
||||||
@ -73,8 +75,9 @@ class ActiveConversationsBlocMapCubit extends BlocMapCubit<TypedKey,
|
|||||||
/// StateFollower /////////////////////////
|
/// StateFollower /////////////////////////
|
||||||
|
|
||||||
@override
|
@override
|
||||||
IMap<TypedKey, proto.Chat> getStateMap(AsyncValue<IList<proto.Chat>> state) {
|
IMap<TypedKey, proto.Chat> getStateMap(
|
||||||
final stateValue = state.data?.value;
|
BlocBusyState<AsyncValue<IList<proto.Chat>>> state) {
|
||||||
|
final stateValue = state.state.data?.value;
|
||||||
if (stateValue == null) {
|
if (stateValue == null) {
|
||||||
return IMap();
|
return IMap();
|
||||||
}
|
}
|
||||||
@ -88,7 +91,7 @@ class ActiveConversationsBlocMapCubit extends BlocMapCubit<TypedKey,
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> updateState(TypedKey key, proto.Chat value) async {
|
Future<void> updateState(TypedKey key, proto.Chat value) async {
|
||||||
final contactList = _contactListCubit.state.data?.value;
|
final contactList = _contactListCubit.state.state.data?.value;
|
||||||
if (contactList == null) {
|
if (contactList == null) {
|
||||||
await addState(key, const AsyncValue.loading());
|
await addState(key, const AsyncValue.loading());
|
||||||
return;
|
return;
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:bloc/bloc.dart';
|
|
||||||
import 'package:bloc_tools/bloc_tools.dart';
|
|
||||||
import 'package:veilid_support/veilid_support.dart';
|
import 'package:veilid_support/veilid_support.dart';
|
||||||
|
|
||||||
import '../../account_manager/account_manager.dart';
|
import '../../account_manager/account_manager.dart';
|
||||||
@ -44,7 +42,9 @@ class ChatListCubit extends DHTShortArrayCubit<proto.Chat> {
|
|||||||
|
|
||||||
// Add Chat to account's list
|
// Add Chat to account's list
|
||||||
// if this fails, don't keep retrying, user can try again later
|
// if this fails, don't keep retrying, user can try again later
|
||||||
if (await shortArray.tryAddItem(chat.writeToBuffer()) == false) {
|
final added = await operate(
|
||||||
|
(shortArray) => shortArray.tryAddItem(chat.writeToBuffer()));
|
||||||
|
if (!added) {
|
||||||
throw Exception('Failed to add chat');
|
throw Exception('Failed to add chat');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -57,17 +57,18 @@ class ChatListCubit extends DHTShortArrayCubit<proto.Chat> {
|
|||||||
|
|
||||||
// Remove Chat from account's list
|
// Remove Chat from account's list
|
||||||
// if this fails, don't keep retrying, user can try again later
|
// if this fails, don't keep retrying, user can try again later
|
||||||
|
await operate((shortArray) async {
|
||||||
for (var i = 0; i < shortArray.length; i++) {
|
for (var i = 0; i < shortArray.length; i++) {
|
||||||
final cbuf = await shortArray.getItem(i);
|
final cbuf = await shortArray.getItem(i);
|
||||||
if (cbuf == null) {
|
if (cbuf == null) {
|
||||||
throw Exception('Failed to get chat');
|
throw Exception('Failed to get chat');
|
||||||
|
}
|
||||||
|
final c = proto.Chat.fromBuffer(cbuf);
|
||||||
|
if (c.remoteConversationKey == remoteConversationKey) {
|
||||||
|
await shortArray.tryRemoveItem(i);
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
final c = proto.Chat.fromBuffer(cbuf);
|
});
|
||||||
if (c.remoteConversationKey == remoteConversationKey) {
|
|
||||||
await shortArray.tryRemoveItem(i);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,10 +10,15 @@ import '../../theme/theme.dart';
|
|||||||
import '../chat_list.dart';
|
import '../chat_list.dart';
|
||||||
|
|
||||||
class ChatSingleContactItemWidget extends StatelessWidget {
|
class ChatSingleContactItemWidget extends StatelessWidget {
|
||||||
const ChatSingleContactItemWidget({required proto.Contact contact, super.key})
|
const ChatSingleContactItemWidget({
|
||||||
: _contact = contact;
|
required proto.Contact contact,
|
||||||
|
required bool disabled,
|
||||||
|
super.key,
|
||||||
|
}) : _contact = contact,
|
||||||
|
_disabled = disabled;
|
||||||
|
|
||||||
final proto.Contact _contact;
|
final proto.Contact _contact;
|
||||||
|
final bool _disabled;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
// ignore: prefer_expression_function_bodies
|
// ignore: prefer_expression_function_bodies
|
||||||
@ -43,12 +48,14 @@ class ChatSingleContactItemWidget extends StatelessWidget {
|
|||||||
motion: const DrawerMotion(),
|
motion: const DrawerMotion(),
|
||||||
children: [
|
children: [
|
||||||
SlidableAction(
|
SlidableAction(
|
||||||
onPressed: (context) async {
|
onPressed: _disabled
|
||||||
final chatListCubit = context.read<ChatListCubit>();
|
? null
|
||||||
await chatListCubit.deleteChat(
|
: (context) async {
|
||||||
remoteConversationRecordKey:
|
final chatListCubit = context.read<ChatListCubit>();
|
||||||
remoteConversationRecordKey);
|
await chatListCubit.deleteChat(
|
||||||
},
|
remoteConversationRecordKey:
|
||||||
|
remoteConversationRecordKey);
|
||||||
|
},
|
||||||
backgroundColor: scale.tertiaryScale.background,
|
backgroundColor: scale.tertiaryScale.background,
|
||||||
foregroundColor: scale.tertiaryScale.text,
|
foregroundColor: scale.tertiaryScale.text,
|
||||||
icon: Icons.delete,
|
icon: Icons.delete,
|
||||||
@ -67,11 +74,14 @@ class ChatSingleContactItemWidget extends StatelessWidget {
|
|||||||
// The child of the Slidable is what the user sees when the
|
// The child of the Slidable is what the user sees when the
|
||||||
// component is not dragged.
|
// component is not dragged.
|
||||||
child: ListTile(
|
child: ListTile(
|
||||||
onTap: () {
|
onTap: _disabled
|
||||||
singleFuture(activeChatCubit, () async {
|
? null
|
||||||
activeChatCubit.setActiveChat(remoteConversationRecordKey);
|
: () {
|
||||||
});
|
singleFuture(activeChatCubit, () async {
|
||||||
},
|
activeChatCubit
|
||||||
|
.setActiveChat(remoteConversationRecordKey);
|
||||||
|
});
|
||||||
|
},
|
||||||
title: Text(_contact.editedProfile.name),
|
title: Text(_contact.editedProfile.name),
|
||||||
|
|
||||||
/// xxx show last message here
|
/// xxx show last message here
|
||||||
|
@ -45,7 +45,8 @@ class ChatSingleContactListWidget extends StatelessWidget {
|
|||||||
return const Text('...');
|
return const Text('...');
|
||||||
}
|
}
|
||||||
return ChatSingleContactItemWidget(
|
return ChatSingleContactItemWidget(
|
||||||
contact: contact);
|
contact: contact,
|
||||||
|
disabled: contactListV.busy);
|
||||||
},
|
},
|
||||||
filter: (value) {
|
filter: (value) {
|
||||||
final lowerValue = value.toLowerCase();
|
final lowerValue = value.toLowerCase();
|
||||||
|
@ -138,9 +138,11 @@ class ContactInvitationListCubit
|
|||||||
|
|
||||||
// Add ContactInvitationRecord to account's list
|
// Add ContactInvitationRecord to account's list
|
||||||
// if this fails, don't keep retrying, user can try again later
|
// if this fails, don't keep retrying, user can try again later
|
||||||
if (await shortArray.tryAddItem(cinvrec.writeToBuffer()) == false) {
|
await operate((shortArray) async {
|
||||||
throw Exception('Failed to add contact invitation record');
|
if (await shortArray.tryAddItem(cinvrec.writeToBuffer()) == false) {
|
||||||
}
|
throw Exception('Failed to add contact invitation record');
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -155,31 +157,34 @@ class ContactInvitationListCubit
|
|||||||
_activeAccountInfo.userLogin.accountRecordInfo.accountRecord.recordKey;
|
_activeAccountInfo.userLogin.accountRecordInfo.accountRecord.recordKey;
|
||||||
|
|
||||||
// Remove ContactInvitationRecord from account's list
|
// Remove ContactInvitationRecord from account's list
|
||||||
for (var i = 0; i < shortArray.length; i++) {
|
await operate((shortArray) async {
|
||||||
final item = await shortArray.getItemProtobuf(
|
for (var i = 0; i < shortArray.length; i++) {
|
||||||
proto.ContactInvitationRecord.fromBuffer, i);
|
final item = await shortArray.getItemProtobuf(
|
||||||
if (item == null) {
|
proto.ContactInvitationRecord.fromBuffer, i);
|
||||||
throw Exception('Failed to get contact invitation record');
|
if (item == null) {
|
||||||
}
|
throw Exception('Failed to get contact invitation record');
|
||||||
if (item.contactRequestInbox.recordKey.toVeilid() ==
|
}
|
||||||
contactRequestInboxRecordKey) {
|
if (item.contactRequestInbox.recordKey.toVeilid() ==
|
||||||
await shortArray.tryRemoveItem(i);
|
contactRequestInboxRecordKey) {
|
||||||
|
await shortArray.tryRemoveItem(i);
|
||||||
await (await pool.openOwned(item.contactRequestInbox.toVeilid(),
|
|
||||||
parent: accountRecordKey))
|
await (await pool.openOwned(item.contactRequestInbox.toVeilid(),
|
||||||
.scope((contactRequestInbox) async {
|
parent: accountRecordKey))
|
||||||
// Wipe out old invitation so it shows up as invalid
|
.scope((contactRequestInbox) async {
|
||||||
await contactRequestInbox.tryWriteBytes(Uint8List(0));
|
// Wipe out old invitation so it shows up as invalid
|
||||||
await contactRequestInbox.delete();
|
await contactRequestInbox.tryWriteBytes(Uint8List(0));
|
||||||
});
|
await contactRequestInbox.delete();
|
||||||
if (!accepted) {
|
});
|
||||||
await (await pool.openRead(item.localConversationRecordKey.toVeilid(),
|
if (!accepted) {
|
||||||
parent: accountRecordKey))
|
await (await pool.openRead(
|
||||||
.delete();
|
item.localConversationRecordKey.toVeilid(),
|
||||||
|
parent: accountRecordKey))
|
||||||
|
.delete();
|
||||||
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<ValidContactInvitation?> validateInvitation(
|
Future<ValidContactInvitation?> validateInvitation(
|
||||||
@ -205,7 +210,7 @@ class ContactInvitationListCubit
|
|||||||
// inbox with our list of extant invitations
|
// inbox with our list of extant invitations
|
||||||
// If we're chatting to ourselves,
|
// If we're chatting to ourselves,
|
||||||
// we are validating an invitation we have created
|
// we are validating an invitation we have created
|
||||||
final isSelf = state.data!.value.indexWhere((cir) =>
|
final isSelf = state.state.data!.value.indexWhere((cir) =>
|
||||||
cir.contactRequestInbox.recordKey.toVeilid() ==
|
cir.contactRequestInbox.recordKey.toVeilid() ==
|
||||||
contactRequestInboxKey) !=
|
contactRequestInboxKey) !=
|
||||||
-1;
|
-1;
|
||||||
|
@ -16,8 +16,10 @@ typedef WaitingInvitationsBlocMapState
|
|||||||
class WaitingInvitationsBlocMapCubit extends BlocMapCubit<TypedKey,
|
class WaitingInvitationsBlocMapCubit extends BlocMapCubit<TypedKey,
|
||||||
AsyncValue<InvitationStatus>, WaitingInvitationCubit>
|
AsyncValue<InvitationStatus>, WaitingInvitationCubit>
|
||||||
with
|
with
|
||||||
StateFollower<AsyncValue<IList<proto.ContactInvitationRecord>>,
|
StateFollower<
|
||||||
TypedKey, proto.ContactInvitationRecord> {
|
BlocBusyState<AsyncValue<IList<proto.ContactInvitationRecord>>>,
|
||||||
|
TypedKey,
|
||||||
|
proto.ContactInvitationRecord> {
|
||||||
WaitingInvitationsBlocMapCubit(
|
WaitingInvitationsBlocMapCubit(
|
||||||
{required this.activeAccountInfo, required this.account});
|
{required this.activeAccountInfo, required this.account});
|
||||||
|
|
||||||
@ -37,8 +39,8 @@ class WaitingInvitationsBlocMapCubit extends BlocMapCubit<TypedKey,
|
|||||||
/// StateFollower /////////////////////////
|
/// StateFollower /////////////////////////
|
||||||
@override
|
@override
|
||||||
IMap<TypedKey, proto.ContactInvitationRecord> getStateMap(
|
IMap<TypedKey, proto.ContactInvitationRecord> getStateMap(
|
||||||
AsyncValue<IList<proto.ContactInvitationRecord>> state) {
|
BlocBusyState<AsyncValue<IList<proto.ContactInvitationRecord>>> state) {
|
||||||
final stateValue = state.data?.value;
|
final stateValue = state.state.data?.value;
|
||||||
if (stateValue == null) {
|
if (stateValue == null) {
|
||||||
return IMap();
|
return IMap();
|
||||||
}
|
}
|
||||||
|
@ -9,15 +9,20 @@ import '../contact_invitation.dart';
|
|||||||
|
|
||||||
class ContactInvitationItemWidget extends StatelessWidget {
|
class ContactInvitationItemWidget extends StatelessWidget {
|
||||||
const ContactInvitationItemWidget(
|
const ContactInvitationItemWidget(
|
||||||
{required this.contactInvitationRecord, super.key});
|
{required this.contactInvitationRecord,
|
||||||
|
required this.disabled,
|
||||||
|
super.key});
|
||||||
|
|
||||||
final proto.ContactInvitationRecord contactInvitationRecord;
|
final proto.ContactInvitationRecord contactInvitationRecord;
|
||||||
|
final bool disabled;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||||
super.debugFillProperties(properties);
|
super.debugFillProperties(properties);
|
||||||
properties.add(DiagnosticsProperty<proto.ContactInvitationRecord>(
|
properties
|
||||||
'contactInvitationRecord', contactInvitationRecord));
|
..add(DiagnosticsProperty<proto.ContactInvitationRecord>(
|
||||||
|
'contactInvitationRecord', contactInvitationRecord))
|
||||||
|
..add(DiagnosticsProperty<bool>('disabled', disabled));
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -10,10 +10,12 @@ import 'contact_invitation_item_widget.dart';
|
|||||||
class ContactInvitationListWidget extends StatefulWidget {
|
class ContactInvitationListWidget extends StatefulWidget {
|
||||||
const ContactInvitationListWidget({
|
const ContactInvitationListWidget({
|
||||||
required this.contactInvitationRecordList,
|
required this.contactInvitationRecordList,
|
||||||
|
required this.disabled,
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
final IList<proto.ContactInvitationRecord> contactInvitationRecordList;
|
final IList<proto.ContactInvitationRecord> contactInvitationRecordList;
|
||||||
|
final bool disabled;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
ContactInvitationListWidgetState createState() =>
|
ContactInvitationListWidgetState createState() =>
|
||||||
@ -21,8 +23,10 @@ class ContactInvitationListWidget extends StatefulWidget {
|
|||||||
@override
|
@override
|
||||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||||
super.debugFillProperties(properties);
|
super.debugFillProperties(properties);
|
||||||
properties.add(IterableProperty<proto.ContactInvitationRecord>(
|
properties
|
||||||
'contactInvitationRecordList', contactInvitationRecordList));
|
..add(IterableProperty<proto.ContactInvitationRecord>(
|
||||||
|
'contactInvitationRecordList', contactInvitationRecordList))
|
||||||
|
..add(DiagnosticsProperty<bool>('disabled', disabled));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,6 +67,7 @@ class ContactInvitationListWidgetState
|
|||||||
return ContactInvitationItemWidget(
|
return ContactInvitationItemWidget(
|
||||||
contactInvitationRecord:
|
contactInvitationRecord:
|
||||||
widget.contactInvitationRecordList[index],
|
widget.contactInvitationRecordList[index],
|
||||||
|
disabled: widget.disabled,
|
||||||
key: ObjectKey(widget.contactInvitationRecordList[index]))
|
key: ObjectKey(widget.contactInvitationRecordList[index]))
|
||||||
.paddingLTRB(4, 2, 4, 2);
|
.paddingLTRB(4, 2, 4, 2);
|
||||||
},
|
},
|
||||||
|
@ -53,9 +53,11 @@ class ContactListCubit extends DHTShortArrayCubit<proto.Contact> {
|
|||||||
|
|
||||||
// Add Contact to account's list
|
// Add Contact to account's list
|
||||||
// if this fails, don't keep retrying, user can try again later
|
// if this fails, don't keep retrying, user can try again later
|
||||||
if (await shortArray.tryAddItem(contact.writeToBuffer()) == false) {
|
await operate((shortArray) async {
|
||||||
throw Exception('Failed to add contact record');
|
if (await shortArray.tryAddItem(contact.writeToBuffer()) == false) {
|
||||||
}
|
throw Exception('Failed to add contact record');
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> deleteContact({required proto.Contact contact}) async {
|
Future<void> deleteContact({required proto.Contact contact}) async {
|
||||||
@ -67,34 +69,36 @@ class ContactListCubit extends DHTShortArrayCubit<proto.Contact> {
|
|||||||
contact.remoteConversationRecordKey.toVeilid();
|
contact.remoteConversationRecordKey.toVeilid();
|
||||||
|
|
||||||
// Remove Contact from account's list
|
// Remove Contact from account's list
|
||||||
for (var i = 0; i < shortArray.length; i++) {
|
await operate((shortArray) async {
|
||||||
final item =
|
for (var i = 0; i < shortArray.length; i++) {
|
||||||
await shortArray.getItemProtobuf(proto.Contact.fromBuffer, i);
|
final item =
|
||||||
if (item == null) {
|
await shortArray.getItemProtobuf(proto.Contact.fromBuffer, i);
|
||||||
throw Exception('Failed to get contact');
|
if (item == null) {
|
||||||
|
throw Exception('Failed to get contact');
|
||||||
|
}
|
||||||
|
if (item.remoteConversationRecordKey ==
|
||||||
|
contact.remoteConversationRecordKey) {
|
||||||
|
await shortArray.tryRemoveItem(i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (item.remoteConversationRecordKey ==
|
try {
|
||||||
contact.remoteConversationRecordKey) {
|
await (await pool.openRead(localConversationKey,
|
||||||
await shortArray.tryRemoveItem(i);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
await (await pool.openRead(localConversationKey,
|
|
||||||
parent: accountRecordKey))
|
|
||||||
.delete();
|
|
||||||
} on Exception catch (e) {
|
|
||||||
log.debug('error removing local conversation record key: $e', e);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
if (localConversationKey != remoteConversationKey) {
|
|
||||||
await (await pool.openRead(remoteConversationKey,
|
|
||||||
parent: accountRecordKey))
|
parent: accountRecordKey))
|
||||||
.delete();
|
.delete();
|
||||||
|
} on Exception catch (e) {
|
||||||
|
log.debug('error removing local conversation record key: $e', e);
|
||||||
}
|
}
|
||||||
} on Exception catch (e) {
|
try {
|
||||||
log.debug('error removing remote conversation record key: $e', e);
|
if (localConversationKey != remoteConversationKey) {
|
||||||
}
|
await (await pool.openRead(remoteConversationKey,
|
||||||
|
parent: accountRecordKey))
|
||||||
|
.delete();
|
||||||
|
}
|
||||||
|
} on Exception catch (e) {
|
||||||
|
log.debug('error removing remote conversation record key: $e', e);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
|
@ -11,9 +11,11 @@ import '../../theme/theme.dart';
|
|||||||
import '../contacts.dart';
|
import '../contacts.dart';
|
||||||
|
|
||||||
class ContactItemWidget extends StatelessWidget {
|
class ContactItemWidget extends StatelessWidget {
|
||||||
const ContactItemWidget({required this.contact, super.key});
|
const ContactItemWidget(
|
||||||
|
{required this.contact, required this.disabled, super.key});
|
||||||
|
|
||||||
final proto.Contact contact;
|
final proto.Contact contact;
|
||||||
|
final bool disabled;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
// ignore: prefer_expression_function_bodies
|
// ignore: prefer_expression_function_bodies
|
||||||
@ -41,17 +43,22 @@ class ContactItemWidget extends StatelessWidget {
|
|||||||
motion: const DrawerMotion(),
|
motion: const DrawerMotion(),
|
||||||
children: [
|
children: [
|
||||||
SlidableAction(
|
SlidableAction(
|
||||||
onPressed: (context) async {
|
onPressed: disabled || context.read<ChatListCubit>().isBusy
|
||||||
final contactListCubit = context.read<ContactListCubit>();
|
? null
|
||||||
final chatListCubit = context.read<ChatListCubit>();
|
: (context) async {
|
||||||
|
final contactListCubit =
|
||||||
|
context.read<ContactListCubit>();
|
||||||
|
final chatListCubit = context.read<ChatListCubit>();
|
||||||
|
|
||||||
// Remove any chats for this contact
|
// Remove any chats for this contact
|
||||||
await chatListCubit.deleteChat(
|
await chatListCubit.deleteChat(
|
||||||
remoteConversationRecordKey: remoteConversationKey);
|
remoteConversationRecordKey:
|
||||||
|
remoteConversationKey);
|
||||||
|
|
||||||
// Delete the contact itself
|
// Delete the contact itself
|
||||||
await contactListCubit.deleteContact(contact: contact);
|
await contactListCubit.deleteContact(
|
||||||
},
|
contact: contact);
|
||||||
|
},
|
||||||
backgroundColor: scale.tertiaryScale.background,
|
backgroundColor: scale.tertiaryScale.background,
|
||||||
foregroundColor: scale.tertiaryScale.text,
|
foregroundColor: scale.tertiaryScale.text,
|
||||||
icon: Icons.delete,
|
icon: Icons.delete,
|
||||||
@ -70,17 +77,21 @@ class ContactItemWidget extends StatelessWidget {
|
|||||||
// The child of the Slidable is what the user sees when the
|
// The child of the Slidable is what the user sees when the
|
||||||
// component is not dragged.
|
// component is not dragged.
|
||||||
child: ListTile(
|
child: ListTile(
|
||||||
onTap: () async {
|
onTap: disabled || context.read<ChatListCubit>().isBusy
|
||||||
// Start a chat
|
? null
|
||||||
final chatListCubit = context.read<ChatListCubit>();
|
: () async {
|
||||||
await chatListCubit.getOrCreateChatSingleContact(
|
// Start a chat
|
||||||
remoteConversationRecordKey: remoteConversationKey);
|
final chatListCubit = context.read<ChatListCubit>();
|
||||||
// Click over to chats
|
await chatListCubit.getOrCreateChatSingleContact(
|
||||||
if (context.mounted) {
|
remoteConversationRecordKey: remoteConversationKey);
|
||||||
await MainPager.of(context)?.pageController.animateToPage(1,
|
// Click over to chats
|
||||||
duration: 250.ms, curve: Curves.easeInOut);
|
if (context.mounted) {
|
||||||
}
|
await MainPager.of(context)
|
||||||
},
|
?.pageController
|
||||||
|
.animateToPage(1,
|
||||||
|
duration: 250.ms, curve: Curves.easeInOut);
|
||||||
|
}
|
||||||
|
},
|
||||||
title: Text(contact.editedProfile.name),
|
title: Text(contact.editedProfile.name),
|
||||||
subtitle: (contact.editedProfile.pronouns.isNotEmpty)
|
subtitle: (contact.editedProfile.pronouns.isNotEmpty)
|
||||||
? Text(contact.editedProfile.pronouns)
|
? Text(contact.editedProfile.pronouns)
|
||||||
|
@ -12,13 +12,17 @@ import 'contact_item_widget.dart';
|
|||||||
import 'empty_contact_list_widget.dart';
|
import 'empty_contact_list_widget.dart';
|
||||||
|
|
||||||
class ContactListWidget extends StatelessWidget {
|
class ContactListWidget extends StatelessWidget {
|
||||||
const ContactListWidget({required this.contactList, super.key});
|
const ContactListWidget(
|
||||||
|
{required this.contactList, required this.disabled, super.key});
|
||||||
final IList<proto.Contact> contactList;
|
final IList<proto.Contact> contactList;
|
||||||
|
final bool disabled;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||||
super.debugFillProperties(properties);
|
super.debugFillProperties(properties);
|
||||||
properties.add(IterableProperty<proto.Contact>('contactList', contactList));
|
properties
|
||||||
|
..add(IterableProperty<proto.Contact>('contactList', contactList))
|
||||||
|
..add(DiagnosticsProperty<bool>('disabled', disabled));
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -36,7 +40,8 @@ class ContactListWidget extends StatelessWidget {
|
|||||||
? const EmptyContactListWidget()
|
? const EmptyContactListWidget()
|
||||||
: SearchableList<proto.Contact>(
|
: SearchableList<proto.Contact>(
|
||||||
initialList: contactList.toList(),
|
initialList: contactList.toList(),
|
||||||
builder: (l, i, c) => ContactItemWidget(contact: c),
|
builder: (l, i, c) =>
|
||||||
|
ContactItemWidget(contact: c, disabled: disabled),
|
||||||
filter: (value) {
|
filter: (value) {
|
||||||
final lowerValue = value.toLowerCase();
|
final lowerValue = value.toLowerCase();
|
||||||
return contactList
|
return contactList
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import 'package:async_tools/async_tools.dart';
|
import 'package:async_tools/async_tools.dart';
|
||||||
|
import 'package:bloc_tools/bloc_tools.dart';
|
||||||
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@ -75,8 +76,7 @@ class HomeAccountReadyShellState extends State<HomeAccountReadyShell> {
|
|||||||
// Process all accepted or rejected invitations
|
// Process all accepted or rejected invitations
|
||||||
void _invitationStatusListener(
|
void _invitationStatusListener(
|
||||||
BuildContext context, WaitingInvitationsBlocMapState state) {
|
BuildContext context, WaitingInvitationsBlocMapState state) {
|
||||||
_singleInvitationStatusProcessor.updateState(state,
|
_singleInvitationStatusProcessor.updateState(state, (newState) async {
|
||||||
closure: (newState) async {
|
|
||||||
final contactListCubit = context.read<ContactListCubit>();
|
final contactListCubit = context.read<ContactListCubit>();
|
||||||
final contactInvitationListCubit =
|
final contactInvitationListCubit =
|
||||||
context.read<ContactInvitationListCubit>();
|
context.read<ContactInvitationListCubit>();
|
||||||
@ -146,7 +146,8 @@ class HomeAccountReadyShellState extends State<HomeAccountReadyShell> {
|
|||||||
activeAccountInfo: widget.activeAccountInfo,
|
activeAccountInfo: widget.activeAccountInfo,
|
||||||
contactListCubit: context.read<ContactListCubit>())
|
contactListCubit: context.read<ContactListCubit>())
|
||||||
..follow(
|
..follow(
|
||||||
initialInputState: const AsyncValue.loading(),
|
initialInputState:
|
||||||
|
const BlocBusyState(AsyncValue.loading()),
|
||||||
stream: context.read<ChatListCubit>().stream)),
|
stream: context.read<ChatListCubit>().stream)),
|
||||||
BlocProvider(
|
BlocProvider(
|
||||||
create: (context) =>
|
create: (context) =>
|
||||||
@ -167,7 +168,8 @@ class HomeAccountReadyShellState extends State<HomeAccountReadyShell> {
|
|||||||
activeAccountInfo: widget.activeAccountInfo,
|
activeAccountInfo: widget.activeAccountInfo,
|
||||||
account: account)
|
account: account)
|
||||||
..follow(
|
..follow(
|
||||||
initialInputState: const AsyncValue.loading(),
|
initialInputState:
|
||||||
|
const BlocBusyState(AsyncValue.loading()),
|
||||||
stream: context
|
stream: context
|
||||||
.read<ContactInvitationListCubit>()
|
.read<ContactInvitationListCubit>()
|
||||||
.stream))
|
.stream))
|
||||||
|
@ -38,11 +38,14 @@ class AccountPageState extends State<AccountPage> {
|
|||||||
final textTheme = theme.textTheme;
|
final textTheme = theme.textTheme;
|
||||||
final scale = theme.extension<ScaleScheme>()!;
|
final scale = theme.extension<ScaleScheme>()!;
|
||||||
|
|
||||||
|
final cilState = context.watch<ContactInvitationListCubit>().state;
|
||||||
|
final cilBusy = cilState.busy;
|
||||||
final contactInvitationRecordList =
|
final contactInvitationRecordList =
|
||||||
context.watch<ContactInvitationListCubit>().state.data?.value ??
|
cilState.state.data?.value ?? const IListConst([]);
|
||||||
const IListConst([]);
|
|
||||||
final contactList = context.watch<ContactListCubit>().state.data?.value ??
|
final ciState = context.watch<ContactListCubit>().state;
|
||||||
const IListConst([]);
|
final ciBusy = ciState.busy;
|
||||||
|
final contactList = ciState.state.data?.value ?? const IListConst([]);
|
||||||
|
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
child: Column(children: <Widget>[
|
child: Column(children: <Widget>[
|
||||||
@ -66,10 +69,11 @@ class AccountPageState extends State<AccountPage> {
|
|||||||
initiallyExpanded: true,
|
initiallyExpanded: true,
|
||||||
children: [
|
children: [
|
||||||
ContactInvitationListWidget(
|
ContactInvitationListWidget(
|
||||||
contactInvitationRecordList: contactInvitationRecordList)
|
contactInvitationRecordList: contactInvitationRecordList,
|
||||||
|
disabled: cilBusy)
|
||||||
],
|
],
|
||||||
).paddingLTRB(8, 0, 8, 8),
|
).paddingLTRB(8, 0, 8, 8),
|
||||||
ContactListWidget(contactList: contactList).expanded(),
|
ContactListWidget(contactList: contactList, disabled: ciBusy).expanded(),
|
||||||
]));
|
]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import 'package:async_tools/async_tools.dart';
|
import 'package:async_tools/async_tools.dart';
|
||||||
import 'package:awesome_extensions/awesome_extensions.dart';
|
import 'package:awesome_extensions/awesome_extensions.dart';
|
||||||
|
import 'package:bloc_tools/bloc_tools.dart';
|
||||||
import 'package:blurry_modal_progress_hud/blurry_modal_progress_hud.dart';
|
import 'package:blurry_modal_progress_hud/blurry_modal_progress_hud.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
@ -77,6 +78,17 @@ extension AsyncValueBuilderExt<T> on AsyncValue<T> {
|
|||||||
data: (d) => debugPage('AsyncValue should not be data here'));
|
data: (d) => debugPage('AsyncValue should not be data here'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension BusyAsyncValueBuilderExt<T> on BlocBusyState<AsyncValue<T>> {
|
||||||
|
Widget builder(Widget Function(BuildContext, T) builder) =>
|
||||||
|
AbsorbPointer(absorbing: busy, child: state.builder(builder));
|
||||||
|
Widget buildNotData(
|
||||||
|
{Widget Function()? loading,
|
||||||
|
Widget Function(Object, StackTrace?)? error}) =>
|
||||||
|
AbsorbPointer(
|
||||||
|
absorbing: busy,
|
||||||
|
child: state.buildNotData(loading: loading, error: error));
|
||||||
|
}
|
||||||
|
|
||||||
class AsyncBlocBuilder<B extends StateStreamable<AsyncValue<S>>, S>
|
class AsyncBlocBuilder<B extends StateStreamable<AsyncValue<S>>, S>
|
||||||
extends BlocBuilder<B, AsyncValue<S>> {
|
extends BlocBuilder<B, AsyncValue<S>> {
|
||||||
AsyncBlocBuilder({
|
AsyncBlocBuilder({
|
||||||
|
@ -6,3 +6,4 @@ export 'src/async_value.dart';
|
|||||||
export 'src/serial_future.dart';
|
export 'src/serial_future.dart';
|
||||||
export 'src/single_future.dart';
|
export 'src/single_future.dart';
|
||||||
export 'src/single_state_processor.dart';
|
export 'src/single_state_processor.dart';
|
||||||
|
export 'src/single_stateless_processor.dart';
|
||||||
|
@ -14,8 +14,7 @@ import '../async_tools.dart';
|
|||||||
class SingleStateProcessor<State> {
|
class SingleStateProcessor<State> {
|
||||||
SingleStateProcessor();
|
SingleStateProcessor();
|
||||||
|
|
||||||
void updateState(State newInputState,
|
void updateState(State newInputState, Future<void> Function(State) closure) {
|
||||||
{required Future<void> Function(State) closure}) {
|
|
||||||
// Use a singlefuture here to ensure we get dont lose any updates
|
// Use a singlefuture here to ensure we get dont lose any updates
|
||||||
// If the input stream gives us an update while we are
|
// If the input stream gives us an update while we are
|
||||||
// still processing the last update, the most recent input state will
|
// still processing the last update, the most recent input state will
|
||||||
|
47
packages/async_tools/lib/src/single_stateless_processor.dart
Normal file
47
packages/async_tools/lib/src/single_stateless_processor.dart
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import '../async_tools.dart';
|
||||||
|
|
||||||
|
// Process a single stateless update at a time ensuring each request
|
||||||
|
// gets processed asynchronously, and continuously while update is requested.
|
||||||
|
//
|
||||||
|
// This is useful for processing updates asynchronously without waiting
|
||||||
|
// from a synchronous execution context
|
||||||
|
class SingleStatelessProcessor {
|
||||||
|
SingleStatelessProcessor();
|
||||||
|
|
||||||
|
void update(Future<void> Function() closure) {
|
||||||
|
singleFuture(this, () async {
|
||||||
|
do {
|
||||||
|
_more = false;
|
||||||
|
await closure();
|
||||||
|
|
||||||
|
// See if another update was requested
|
||||||
|
} while (_more);
|
||||||
|
}, onBusy: () {
|
||||||
|
// Keep this state until we process again
|
||||||
|
_more = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Like update, but with a busy wrapper that clears once the updating is finished
|
||||||
|
void busyUpdate<T, S>(
|
||||||
|
Future<void> Function(Future<void> Function(void Function(S))) busy,
|
||||||
|
Future<void> Function(void Function(S)) closure) {
|
||||||
|
singleFuture(
|
||||||
|
this,
|
||||||
|
() async => busy((emit) async {
|
||||||
|
do {
|
||||||
|
_more = false;
|
||||||
|
await closure(emit);
|
||||||
|
|
||||||
|
// See if another update was requested
|
||||||
|
} while (_more);
|
||||||
|
}), onBusy: () {
|
||||||
|
// Keep this state until we process again
|
||||||
|
_more = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _more = false;
|
||||||
|
}
|
@ -10,7 +10,7 @@ class AsyncTransformerCubit<T, S> extends Cubit<AsyncValue<T>> {
|
|||||||
_subscription = input.stream.listen(_asyncTransform);
|
_subscription = input.stream.listen(_asyncTransform);
|
||||||
}
|
}
|
||||||
void _asyncTransform(AsyncValue<S> newInputState) {
|
void _asyncTransform(AsyncValue<S> newInputState) {
|
||||||
_singleStateProcessor.updateState(newInputState, closure: (newState) async {
|
_singleStateProcessor.updateState(newInputState, (newState) async {
|
||||||
// Emit the transformed state
|
// Emit the transformed state
|
||||||
try {
|
try {
|
||||||
if (newState is AsyncLoading<S>) {
|
if (newState is AsyncLoading<S>) {
|
||||||
|
@ -17,7 +17,7 @@ class BlocBusyState<S> extends Equatable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
mixin BlocBusyWrapper<S> on BlocBase<BlocBusyState<S>> {
|
mixin BlocBusyWrapper<S> on BlocBase<BlocBusyState<S>> {
|
||||||
Future<T> busy<T>(Future<T> Function(void Function(S) emit) closure) async =>
|
Future<T> busyValue<T>(Future<T> Function(void Function(S) emit) closure) =>
|
||||||
_mutex.protect(() async {
|
_mutex.protect(() async {
|
||||||
void busyemit(S state) {
|
void busyemit(S state) {
|
||||||
changedState = state;
|
changedState = state;
|
||||||
@ -41,6 +41,27 @@ mixin BlocBusyWrapper<S> on BlocBase<BlocBusyState<S>> {
|
|||||||
return out;
|
return out;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Future<void> busy(Future<void> Function(void Function(S) emit) closure) =>
|
||||||
|
_mutex.protect(() async {
|
||||||
|
void busyemit(S state) {
|
||||||
|
changedState = state;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Turn on busy state
|
||||||
|
emit(BlocBusyState._busy(state.state));
|
||||||
|
|
||||||
|
// Run the closure
|
||||||
|
await closure(busyemit);
|
||||||
|
|
||||||
|
// If the closure did one or more 'busy emits' then
|
||||||
|
// take the most recent one and emit it for real
|
||||||
|
final finalState = changedState;
|
||||||
|
if (finalState != null && finalState != state.state) {
|
||||||
|
emit(BlocBusyState._busy(finalState));
|
||||||
|
} else {
|
||||||
|
emit(BlocBusyState._busy(state.state));
|
||||||
|
}
|
||||||
|
});
|
||||||
void changeState(S state) {
|
void changeState(S state) {
|
||||||
if (_mutex.isLocked) {
|
if (_mutex.isLocked) {
|
||||||
changedState = state;
|
changedState = state;
|
||||||
@ -49,6 +70,8 @@ mixin BlocBusyWrapper<S> on BlocBase<BlocBusyState<S>> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool get isBusy => _mutex.isLocked;
|
||||||
|
|
||||||
final Mutex _mutex = Mutex();
|
final Mutex _mutex = Mutex();
|
||||||
S? changedState;
|
S? changedState;
|
||||||
}
|
}
|
||||||
|
@ -31,7 +31,7 @@ abstract mixin class StateFollower<S extends Object, K, V> {
|
|||||||
|
|
||||||
void _updateFollow(S newInputState) {
|
void _updateFollow(S newInputState) {
|
||||||
_singleStateProcessor.updateState(getStateMap(newInputState),
|
_singleStateProcessor.updateState(getStateMap(newInputState),
|
||||||
closure: (newStateMap) async {
|
(newStateMap) async {
|
||||||
for (final k in _lastInputStateMap.keys) {
|
for (final k in _lastInputStateMap.keys) {
|
||||||
if (!newStateMap.containsKey(k)) {
|
if (!newStateMap.containsKey(k)) {
|
||||||
// deleted
|
// deleted
|
||||||
|
@ -4,18 +4,19 @@ import 'package:async_tools/async_tools.dart';
|
|||||||
import 'package:bloc/bloc.dart';
|
import 'package:bloc/bloc.dart';
|
||||||
import 'package:bloc_tools/bloc_tools.dart';
|
import 'package:bloc_tools/bloc_tools.dart';
|
||||||
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||||
|
import 'package:mutex/mutex.dart';
|
||||||
|
|
||||||
import '../../veilid_support.dart';
|
import '../../veilid_support.dart';
|
||||||
|
|
||||||
class DHTShortArrayCubit<T> extends Cubit<BlocBusyState<AsyncValue<IList<T>>>>
|
typedef DHTShortArrayState<T> = AsyncValue<IList<T>>;
|
||||||
with BlocBusyWrapper<AsyncValue<IList<T>>> {
|
typedef DHTShortArrayBusyState<T> = BlocBusyState<DHTShortArrayState<T>>;
|
||||||
|
|
||||||
|
class DHTShortArrayCubit<T> extends Cubit<DHTShortArrayBusyState<T>>
|
||||||
|
with BlocBusyWrapper<DHTShortArrayState<T>> {
|
||||||
DHTShortArrayCubit({
|
DHTShortArrayCubit({
|
||||||
required Future<DHTShortArray> Function() open,
|
required Future<DHTShortArray> Function() open,
|
||||||
required T Function(List<int> data) decodeElement,
|
required T Function(List<int> data) decodeElement,
|
||||||
}) : _decodeElement = decodeElement,
|
}) : _decodeElement = decodeElement,
|
||||||
_wantsUpdate = false,
|
|
||||||
_isUpdating = false,
|
|
||||||
_wantsCloseRecord = false,
|
|
||||||
super(const BlocBusyState(AsyncValue.loading())) {
|
super(const BlocBusyState(AsyncValue.loading())) {
|
||||||
Future.delayed(Duration.zero, () async {
|
Future.delayed(Duration.zero, () async {
|
||||||
// Open DHT record
|
// Open DHT record
|
||||||
@ -33,9 +34,6 @@ class DHTShortArrayCubit<T> extends Cubit<BlocBusyState<AsyncValue<IList<T>>>>
|
|||||||
required T Function(List<int> data) decodeElement,
|
required T Function(List<int> data) decodeElement,
|
||||||
}) : _shortArray = shortArray,
|
}) : _shortArray = shortArray,
|
||||||
_decodeElement = decodeElement,
|
_decodeElement = decodeElement,
|
||||||
_wantsUpdate = false,
|
|
||||||
_isUpdating = false,
|
|
||||||
_wantsCloseRecord = false,
|
|
||||||
super(const BlocBusyState(AsyncValue.loading())) {
|
super(const BlocBusyState(AsyncValue.loading())) {
|
||||||
// Make initial state update
|
// Make initial state update
|
||||||
_update();
|
_update();
|
||||||
@ -59,37 +57,21 @@ class DHTShortArrayCubit<T> extends Cubit<BlocBusyState<AsyncValue<IList<T>>>>
|
|||||||
|
|
||||||
void _update() {
|
void _update() {
|
||||||
// Run at most one background update process
|
// Run at most one background update process
|
||||||
|
// Because this is async, we could get an update while we're
|
||||||
xxx convert to singleFuture with onBusy that sets wantsupdate
|
// still processing the last one
|
||||||
|
_sspUpdate.busyUpdate<T, AsyncValue<IList<T>>>(busy, (emit) async {
|
||||||
_wantsUpdate = true;
|
|
||||||
if (_isUpdating) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_isUpdating = true;
|
|
||||||
Future.delayed(Duration.zero, () async {
|
|
||||||
// Keep updating until we don't want to update any more
|
|
||||||
// Because this is async, we could get an update while we're
|
|
||||||
// still processing the last one
|
|
||||||
try {
|
try {
|
||||||
do {
|
final initialState = await _getElementsInner();
|
||||||
_wantsUpdate = false;
|
emit(AsyncValue.data(initialState));
|
||||||
try {
|
} on Exception catch (e) {
|
||||||
final initialState = await _getElements();
|
emit(AsyncValue.error(e));
|
||||||
emit(AsyncValue.data(initialState));
|
|
||||||
} on Exception catch (e) {
|
|
||||||
emit(AsyncValue.error(e));
|
|
||||||
}
|
|
||||||
} while (_wantsUpdate);
|
|
||||||
} finally {
|
|
||||||
// Note that this update future has finished
|
|
||||||
_isUpdating = false;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get and decode the entire short array
|
// Get and decode the entire short array
|
||||||
Future<IList<T>> _getElements() async {
|
Future<IList<T>> _getElementsInner() async {
|
||||||
|
assert(isBusy, 'should only be called from a busy state');
|
||||||
var out = IList<T>();
|
var out = IList<T>();
|
||||||
for (var i = 0; i < _shortArray.length; i++) {
|
for (var i = 0; i < _shortArray.length; i++) {
|
||||||
// Get the element bytes (throw if fails, array state is invalid)
|
// Get the element bytes (throw if fails, array state is invalid)
|
||||||
@ -112,12 +94,13 @@ xxx convert to singleFuture with onBusy that sets wantsupdate
|
|||||||
await super.close();
|
await super.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
DHTShortArray get shortArray => _shortArray;
|
Future<R> operate<R>(Future<R> Function(DHTShortArray) closure) async =>
|
||||||
|
_operateMutex.protect(() async => closure(_shortArray));
|
||||||
|
|
||||||
|
final _operateMutex = Mutex();
|
||||||
late final DHTShortArray _shortArray;
|
late final DHTShortArray _shortArray;
|
||||||
final T Function(List<int> data) _decodeElement;
|
final T Function(List<int> data) _decodeElement;
|
||||||
StreamSubscription<void>? _subscription;
|
StreamSubscription<void>? _subscription;
|
||||||
bool _wantsUpdate;
|
bool _wantsCloseRecord = false;
|
||||||
bool _isUpdating;
|
final _sspUpdate = SingleStatelessProcessor();
|
||||||
bool _wantsCloseRecord;
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user