mirror of
https://gitlab.com/veilid/veilidchat.git
synced 2024-10-01 06:55:46 -04:00
chat refactor
This commit is contained in:
parent
4a8958a868
commit
03a6a781a6
@ -1 +1,2 @@
|
||||
export 'cubits/cubits.dart';
|
||||
export 'views/views.dart';
|
||||
|
10
lib/chat/cubits/active_chat_cubit.dart
Normal file
10
lib/chat/cubits/active_chat_cubit.dart
Normal file
@ -0,0 +1,10 @@
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:veilid_support/veilid_support.dart';
|
||||
|
||||
class ActiveChatCubit extends Cubit<TypedKey?> {
|
||||
ActiveChatCubit(super.initialState);
|
||||
|
||||
void setActiveChat(TypedKey? activeChat) {
|
||||
emit(activeChat);
|
||||
}
|
||||
}
|
1
lib/chat/cubits/cubits.dart
Normal file
1
lib/chat/cubits/cubits.dart
Normal file
@ -0,0 +1 @@
|
||||
export 'active_chat_cubit.dart';
|
@ -0,0 +1,2 @@
|
||||
export 'cubits/cubits.dart';
|
||||
export 'views/views.dart';
|
72
lib/chat_list/cubits/chat_list_cubit.dart
Normal file
72
lib/chat_list/cubits/chat_list_cubit.dart
Normal file
@ -0,0 +1,72 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:veilid_support/veilid_support.dart';
|
||||
|
||||
import '../../account_manager/account_manager.dart';
|
||||
import '../../proto/proto.dart' as proto;
|
||||
|
||||
//////////////////////////////////////////////////
|
||||
|
||||
//////////////////////////////////////////////////
|
||||
// Mutable state for per-account chat list
|
||||
|
||||
class ChatListCubit extends DHTShortArrayCubit<proto.Chat> {
|
||||
ChatListCubit({
|
||||
required ActiveAccountInfo activeAccountInfo,
|
||||
required proto.Account account,
|
||||
}) : super(
|
||||
open: () => _open(activeAccountInfo, account),
|
||||
decodeElement: proto.Chat.fromBuffer);
|
||||
|
||||
static Future<DHTShortArray> _open(
|
||||
ActiveAccountInfo activeAccountInfo, proto.Account account) async {
|
||||
final accountRecordKey =
|
||||
activeAccountInfo.userLogin.accountRecordInfo.accountRecord.recordKey;
|
||||
|
||||
final chatListRecordKey =
|
||||
proto.OwnedDHTRecordPointerProto.fromProto(account.chatList);
|
||||
|
||||
final dhtRecord = await DHTShortArray.openOwned(chatListRecordKey,
|
||||
parent: accountRecordKey);
|
||||
|
||||
return dhtRecord;
|
||||
}
|
||||
|
||||
/// Create a new chat (singleton for single contact chats)
|
||||
Future<void> getOrCreateChatSingleContact({
|
||||
required TypedKey remoteConversationRecordKey,
|
||||
}) async {
|
||||
// Create conversation type Chat
|
||||
final chat = proto.Chat()
|
||||
..type = proto.ChatType.SINGLE_CONTACT
|
||||
..remoteConversationKey = remoteConversationRecordKey.toProto();
|
||||
|
||||
// Add Chat to account's list
|
||||
// if this fails, don't keep retrying, user can try again later
|
||||
if (await shortArray.tryAddItem(chat.writeToBuffer()) == false) {
|
||||
throw Exception('Failed to add chat');
|
||||
}
|
||||
}
|
||||
|
||||
/// Delete a chat
|
||||
Future<void> deleteChat(
|
||||
{required TypedKey remoteConversationRecordKey}) async {
|
||||
// Create conversation type Chat
|
||||
final remoteConversationKey = remoteConversationRecordKey.toProto();
|
||||
|
||||
// Remove Chat from account's list
|
||||
// if this fails, don't keep retrying, user can try again later
|
||||
|
||||
for (var i = 0; i < shortArray.length; i++) {
|
||||
final cbuf = await shortArray.getItem(i);
|
||||
if (cbuf == null) {
|
||||
throw Exception('Failed to get chat');
|
||||
}
|
||||
final c = proto.Chat.fromBuffer(cbuf);
|
||||
if (c.remoteConversationKey == remoteConversationKey) {
|
||||
await shortArray.tryRemoveItem(i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
1
lib/chat_list/cubits/cubits.dart
Normal file
1
lib/chat_list/cubits/cubits.dart
Normal file
@ -0,0 +1 @@
|
||||
export 'chat_list_cubit.dart';
|
@ -1,21 +1,21 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_slidable/flutter_slidable.dart';
|
||||
import 'package:flutter_translate/flutter_translate.dart';
|
||||
import '../proto/proto.dart' as proto;
|
||||
import '../providers/account.dart';
|
||||
import '../providers/chat.dart';
|
||||
import '../theme/theme.dart';
|
||||
import '../../proto/proto.dart' as proto;
|
||||
import '../../theme/theme.dart';
|
||||
|
||||
class ChatSingleContactItemWidget extends ConsumerWidget {
|
||||
const ChatSingleContactItemWidget({required this.contact, super.key});
|
||||
class ChatSingleContactItemWidget extends StatelessWidget {
|
||||
const ChatSingleContactItemWidget({required proto.Contact contact, super.key})
|
||||
: _contact = contact;
|
||||
|
||||
final proto.Contact contact;
|
||||
final proto.Contact _contact;
|
||||
|
||||
@override
|
||||
// ignore: prefer_expression_function_bodies
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
Widget build(
|
||||
BuildContext context,
|
||||
) {
|
||||
final theme = Theme.of(context);
|
||||
//final textTheme = theme.textTheme;
|
||||
final scale = theme.extension<ScaleScheme>()!;
|
||||
|
@ -1,2 +1,3 @@
|
||||
export 'cubits/cubits.dart';
|
||||
export 'models/models.dart';
|
||||
export 'views/views.dart';
|
||||
|
104
lib/contacts/cubits/contact_list_cubit.dart
Normal file
104
lib/contacts/cubits/contact_list_cubit.dart
Normal file
@ -0,0 +1,104 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:veilid_support/veilid_support.dart';
|
||||
|
||||
import '../../account_manager/account_manager.dart';
|
||||
import '../../proto/proto.dart' as proto;
|
||||
import '../../tools/tools.dart';
|
||||
|
||||
//////////////////////////////////////////////////
|
||||
// Mutable state for per-account contacts
|
||||
|
||||
class ContactListCubit extends DHTShortArrayCubit<proto.Contact> {
|
||||
ContactListCubit({
|
||||
required ActiveAccountInfo activeAccountInfo,
|
||||
required proto.Account account,
|
||||
}) : _activeAccountInfo = activeAccountInfo,
|
||||
super(
|
||||
open: () => _open(activeAccountInfo, account),
|
||||
decodeElement: proto.Contact.fromBuffer);
|
||||
|
||||
static Future<DHTShortArray> _open(
|
||||
ActiveAccountInfo activeAccountInfo, proto.Account account) async {
|
||||
final accountRecordKey =
|
||||
activeAccountInfo.userLogin.accountRecordInfo.accountRecord.recordKey;
|
||||
|
||||
final contactListRecordKey =
|
||||
proto.OwnedDHTRecordPointerProto.fromProto(account.contactList);
|
||||
|
||||
final dhtRecord = await DHTShortArray.openOwned(contactListRecordKey,
|
||||
parent: accountRecordKey);
|
||||
|
||||
return dhtRecord;
|
||||
}
|
||||
|
||||
Future<void> createContact({
|
||||
required proto.Profile remoteProfile,
|
||||
required IdentityMaster remoteIdentity,
|
||||
required TypedKey remoteConversationRecordKey,
|
||||
required TypedKey localConversationRecordKey,
|
||||
}) async {
|
||||
// Create Contact
|
||||
final contact = proto.Contact()
|
||||
..editedProfile = remoteProfile
|
||||
..remoteProfile = remoteProfile
|
||||
..identityMasterJson = jsonEncode(remoteIdentity.toJson())
|
||||
..identityPublicKey = TypedKey(
|
||||
kind: remoteIdentity.identityRecordKey.kind,
|
||||
value: remoteIdentity.identityPublicKey)
|
||||
.toProto()
|
||||
..remoteConversationRecordKey = remoteConversationRecordKey.toProto()
|
||||
..localConversationRecordKey = localConversationRecordKey.toProto()
|
||||
..showAvailability = false;
|
||||
|
||||
// Add Contact to account's list
|
||||
// if this fails, don't keep retrying, user can try again later
|
||||
if (await shortArray.tryAddItem(contact.writeToBuffer()) == false) {
|
||||
throw Exception('Failed to add contact record');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> deleteContact({required proto.Contact contact}) async {
|
||||
final pool = DHTRecordPool.instance;
|
||||
final accountRecordKey =
|
||||
_activeAccountInfo.userLogin.accountRecordInfo.accountRecord.recordKey;
|
||||
final localConversationKey =
|
||||
proto.TypedKeyProto.fromProto(contact.localConversationRecordKey);
|
||||
final remoteConversationKey =
|
||||
proto.TypedKeyProto.fromProto(contact.remoteConversationRecordKey);
|
||||
|
||||
// Remove Contact from account's list
|
||||
for (var i = 0; i < shortArray.length; i++) {
|
||||
final item =
|
||||
await shortArray.getItemProtobuf(proto.Contact.fromBuffer, i);
|
||||
if (item == null) {
|
||||
throw Exception('Failed to get contact');
|
||||
}
|
||||
if (item.remoteConversationRecordKey ==
|
||||
contact.remoteConversationRecordKey) {
|
||||
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))
|
||||
.delete();
|
||||
}
|
||||
} on Exception catch (e) {
|
||||
log.debug('error removing remote conversation record key: $e', e);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
final ActiveAccountInfo _activeAccountInfo;
|
||||
}
|
1
lib/contacts/cubits/cubits.dart
Normal file
1
lib/contacts/cubits/cubits.dart
Normal file
@ -0,0 +1 @@
|
||||
export 'contact_list_cubit.dart';
|
@ -1,132 +0,0 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
import '../../proto/proto.dart' as proto;
|
||||
|
||||
import '../../../packages/veilid_support/veilid_support.dart';
|
||||
import '../../tools/tools.dart';
|
||||
import '../../old_to_refactor/providers/account.dart';
|
||||
import '../../old_to_refactor/providers/chat.dart';
|
||||
|
||||
part '../../old_to_refactor/providers/contact.g.dart';
|
||||
|
||||
Future<void> createContact({
|
||||
required ActiveAccountInfo activeAccountInfo,
|
||||
required proto.Profile profile,
|
||||
required IdentityMaster remoteIdentity,
|
||||
required TypedKey remoteConversationRecordKey,
|
||||
required TypedKey localConversationRecordKey,
|
||||
}) async {
|
||||
final accountRecordKey =
|
||||
activeAccountInfo.userLogin.accountRecordInfo.accountRecord.recordKey;
|
||||
|
||||
// Create Contact
|
||||
final contact = proto.Contact()
|
||||
..editedProfile = profile
|
||||
..remoteProfile = profile
|
||||
..identityMasterJson = jsonEncode(remoteIdentity.toJson())
|
||||
..identityPublicKey = TypedKey(
|
||||
kind: remoteIdentity.identityRecordKey.kind,
|
||||
value: remoteIdentity.identityPublicKey)
|
||||
.toProto()
|
||||
..remoteConversationRecordKey = remoteConversationRecordKey.toProto()
|
||||
..localConversationRecordKey = localConversationRecordKey.toProto()
|
||||
..showAvailability = false;
|
||||
|
||||
// Add Contact to account's list
|
||||
// if this fails, don't keep retrying, user can try again later
|
||||
await (await DHTShortArray.openOwned(
|
||||
proto.OwnedDHTRecordPointerProto.fromProto(
|
||||
activeAccountInfo.account.contactList),
|
||||
parent: accountRecordKey))
|
||||
.scope((contactList) async {
|
||||
if (await contactList.tryAddItem(contact.writeToBuffer()) == false) {
|
||||
throw Exception('Failed to add contact');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> deleteContact(
|
||||
{required ActiveAccountInfo activeAccountInfo,
|
||||
required proto.Contact contact}) async {
|
||||
final pool = await DHTRecordPool.instance();
|
||||
final accountRecordKey =
|
||||
activeAccountInfo.userLogin.accountRecordInfo.accountRecord.recordKey;
|
||||
final localConversationKey =
|
||||
proto.TypedKeyProto.fromProto(contact.localConversationRecordKey);
|
||||
final remoteConversationKey =
|
||||
proto.TypedKeyProto.fromProto(contact.remoteConversationRecordKey);
|
||||
|
||||
// Remove any chats for this contact
|
||||
await deleteChat(
|
||||
activeAccountInfo: activeAccountInfo,
|
||||
remoteConversationRecordKey: remoteConversationKey);
|
||||
|
||||
// Remove Contact from account's list
|
||||
await (await DHTShortArray.openOwned(
|
||||
proto.OwnedDHTRecordPointerProto.fromProto(
|
||||
activeAccountInfo.account.contactList),
|
||||
parent: accountRecordKey))
|
||||
.scope((contactList) async {
|
||||
for (var i = 0; i < contactList.length; i++) {
|
||||
final item =
|
||||
await contactList.getItemProtobuf(proto.Contact.fromBuffer, i);
|
||||
if (item == null) {
|
||||
throw Exception('Failed to get contact');
|
||||
}
|
||||
if (item.remoteConversationRecordKey ==
|
||||
contact.remoteConversationRecordKey) {
|
||||
await contactList.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))
|
||||
.delete();
|
||||
}
|
||||
} on Exception catch (e) {
|
||||
log.debug('error removing remote conversation record key: $e', e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Get the active account contact list
|
||||
@riverpod
|
||||
Future<IList<proto.Contact>?> fetchContactList(FetchContactListRef ref) async {
|
||||
// See if we've logged into this account or if it is locked
|
||||
final activeAccountInfo = await ref.watch(fetchActiveAccountProvider.future);
|
||||
if (activeAccountInfo == null) {
|
||||
return null;
|
||||
}
|
||||
final accountRecordKey =
|
||||
activeAccountInfo.userLogin.accountRecordInfo.accountRecord.recordKey;
|
||||
|
||||
// Decode the contact list from the DHT
|
||||
IList<proto.Contact> out = const IListConst([]);
|
||||
await (await DHTShortArray.openOwned(
|
||||
proto.OwnedDHTRecordPointerProto.fromProto(
|
||||
activeAccountInfo.account.contactList),
|
||||
parent: accountRecordKey))
|
||||
.scope((cList) async {
|
||||
for (var i = 0; i < cList.length; i++) {
|
||||
final cir = await cList.getItem(i);
|
||||
if (cir == null) {
|
||||
throw Exception('Failed to get contact');
|
||||
}
|
||||
out = out.add(proto.Contact.fromBuffer(cir));
|
||||
}
|
||||
});
|
||||
|
||||
return out;
|
||||
}
|
@ -1,10 +1,13 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_animate/flutter_animate.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_slidable/flutter_slidable.dart';
|
||||
import 'package:flutter_translate/flutter_translate.dart';
|
||||
import '../../chat_list/chat_list.dart';
|
||||
import '../../proto/proto.dart' as proto;
|
||||
import '../../theme/theme.dart';
|
||||
import '../contacts.dart';
|
||||
|
||||
class ContactItemWidget extends StatelessWidget {
|
||||
const ContactItemWidget({required this.contact, super.key});
|
||||
@ -38,16 +41,15 @@ class ContactItemWidget extends StatelessWidget {
|
||||
children: [
|
||||
SlidableAction(
|
||||
onPressed: (context) async {
|
||||
final activeAccountInfo =
|
||||
await ref.read(fetchActiveAccountProvider.future);
|
||||
if (activeAccountInfo != null) {
|
||||
await deleteContact(
|
||||
activeAccountInfo: activeAccountInfo,
|
||||
contact: contact);
|
||||
ref
|
||||
..invalidate(fetchContactListProvider)
|
||||
..invalidate(fetchChatListProvider);
|
||||
}
|
||||
final contactListCubit = context.read<ContactListCubit>();
|
||||
final chatListCubit = context.read<ChatListCubit>();
|
||||
|
||||
// Remove any chats for this contact
|
||||
await chatListCubit.deleteChat(
|
||||
remoteConversationRecordKey: remoteConversationKey);
|
||||
|
||||
// Delete the contact itself
|
||||
await contactListCubit.deleteContact(contact: contact);
|
||||
},
|
||||
backgroundColor: scale.tertiaryScale.background,
|
||||
foregroundColor: scale.tertiaryScale.text,
|
||||
|
@ -2,16 +2,16 @@ import 'package:awesome_extensions/awesome_extensions.dart';
|
||||
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_translate/flutter_translate.dart';
|
||||
import 'package:searchable_listview/searchable_listview.dart';
|
||||
|
||||
import '../proto/proto.dart' as proto;
|
||||
import '../tools/tools.dart';
|
||||
import '../../proto/proto.dart' as proto;
|
||||
import '../../theme/theme.dart';
|
||||
import '../../tools/tools.dart';
|
||||
import 'contact_item_widget.dart';
|
||||
import 'empty_contact_list_widget.dart';
|
||||
|
||||
class ContactListWidget extends ConsumerWidget {
|
||||
class ContactListWidget extends StatelessWidget {
|
||||
const ContactListWidget({required this.contactList, super.key});
|
||||
final IList<proto.Contact> contactList;
|
||||
|
||||
@ -22,7 +22,7 @@ class ContactListWidget extends ConsumerWidget {
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
//final textTheme = theme.textTheme;
|
||||
final scale = theme.extension<ScaleScheme>()!;
|
||||
|
@ -1,8 +1,6 @@
|
||||
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:veilid_support/veilid_support.dart';
|
||||
|
||||
import '../../account_manager/account_manager.dart';
|
||||
import '../../theme/theme.dart';
|
||||
@ -39,8 +37,7 @@ class HomePageState extends State<HomePage> with TickerProviderStateMixin {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Widget buildWithLogin(BuildContext context, IList<LocalAccount> localAccounts,
|
||||
Typed<FixedEncodedString43>? activeUserLogin) {
|
||||
Widget buildWithLogin(BuildContext context) {
|
||||
final activeUserLogin = context.watch<ActiveUserLoginCubit>().state;
|
||||
|
||||
if (activeUserLogin == null) {
|
||||
@ -64,7 +61,7 @@ class HomePageState extends State<HomePage> with TickerProviderStateMixin {
|
||||
child: BlocProvider(
|
||||
create: (context) => AccountRecordCubit(
|
||||
record: accountInfo.activeAccountInfo!.accountRecord),
|
||||
child: HomeAccountReady()));
|
||||
child: const HomeAccountReady()));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6,6 +6,8 @@ import 'package:flutter_translate/flutter_translate.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import '../../../account_manager/account_manager.dart';
|
||||
import '../../../chat/chat.dart';
|
||||
import '../../../chat_list/chat_list.dart';
|
||||
import '../../../contact_invitation/contact_invitation.dart';
|
||||
import '../../../theme/theme.dart';
|
||||
import '../../../tools/tools.dart';
|
||||
@ -106,9 +108,18 @@ class HomeAccountReadyState extends State<HomeAccountReady>
|
||||
return waitingPage(context);
|
||||
}
|
||||
|
||||
return BlocProvider(
|
||||
return MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider(
|
||||
create: (context) => ContactInvitationListCubit(
|
||||
activeAccountInfo: activeAccountInfo, account: accountData.value),
|
||||
activeAccountInfo: activeAccountInfo,
|
||||
account: accountData.value)),
|
||||
BlocProvider(
|
||||
create: (context) => ChatListCubit(
|
||||
activeAccountInfo: activeAccountInfo,
|
||||
account: accountData.value)),
|
||||
BlocProvider(create: (context) => ActiveChatCubit(null))
|
||||
],
|
||||
child: responsiveVisibility(
|
||||
context: context,
|
||||
phone: false,
|
||||
|
@ -1,12 +1,11 @@
|
||||
import 'package:awesome_extensions/awesome_extensions.dart';
|
||||
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_translate/flutter_translate.dart';
|
||||
import 'package:veilid_support/veilid_support.dart';
|
||||
|
||||
import '../../../../contact_invitation/contact_invitation.dart';
|
||||
import '../../../../contacts/contacts.dart';
|
||||
import '../../../../theme/theme.dart';
|
||||
|
||||
class AccountPage extends StatefulWidget {
|
||||
|
@ -27,14 +27,9 @@ class ChatsPageState extends State<ChatsPage> {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
/// We have an active, unlocked, user login
|
||||
Widget buildChatList(
|
||||
BuildContext context,
|
||||
IList<LocalAccount> localAccounts,
|
||||
TypedKey activeUserLogin,
|
||||
proto.Account account,
|
||||
@override
|
||||
// ignore: prefer_expression_function_bodies
|
||||
) {
|
||||
Widget build(BuildContext context) {
|
||||
final contactList = ref.watch(fetchContactListProvider).asData?.value ??
|
||||
const IListConst([]);
|
||||
final chatList =
|
||||
@ -48,44 +43,3 @@ class ChatsPageState extends State<ChatsPage> {
|
||||
if (chatList.isEmpty) const EmptyChatListWidget().expanded(),
|
||||
]);
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: prefer_expression_function_bodies
|
||||
Widget build(BuildContext context) {
|
||||
final localAccountsV = ref.watch(localAccountsProvider);
|
||||
final loginsV = ref.watch(loginsProvider);
|
||||
|
||||
if (!localAccountsV.hasValue || !loginsV.hasValue) {
|
||||
return waitingPage(context);
|
||||
}
|
||||
final localAccounts = localAccountsV.requireValue;
|
||||
final logins = loginsV.requireValue;
|
||||
|
||||
final activeUserLogin = logins.activeUserLogin;
|
||||
if (activeUserLogin == null) {
|
||||
// If no logged in user is active show a placeholder
|
||||
return waitingPage(context);
|
||||
}
|
||||
final accountV = ref
|
||||
.watch(fetchAccountProvider(accountMasterRecordKey: activeUserLogin));
|
||||
if (!accountV.hasValue) {
|
||||
return waitingPage(context);
|
||||
}
|
||||
final account = accountV.requireValue;
|
||||
switch (account.status) {
|
||||
case AccountInfoStatus.noAccount:
|
||||
return waitingPage(context);
|
||||
case AccountInfoStatus.accountInvalid:
|
||||
return waitingPage(context);
|
||||
case AccountInfoStatus.accountLocked:
|
||||
return waitingPage(context);
|
||||
case AccountInfoStatus.accountReady:
|
||||
return buildChatList(
|
||||
context,
|
||||
localAccounts,
|
||||
activeUserLogin,
|
||||
account.account!,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,118 +0,0 @@
|
||||
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
import '../../proto/proto.dart' as proto;
|
||||
|
||||
import '../../../packages/veilid_support/veilid_support.dart';
|
||||
import 'account.dart';
|
||||
|
||||
part 'chat.g.dart';
|
||||
|
||||
/// Create a new chat (singleton for single contact chats)
|
||||
Future<void> getOrCreateChatSingleContact({
|
||||
required ActiveAccountInfo activeAccountInfo,
|
||||
required TypedKey remoteConversationRecordKey,
|
||||
}) async {
|
||||
final accountRecordKey =
|
||||
activeAccountInfo.userLogin.accountRecordInfo.accountRecord.recordKey;
|
||||
|
||||
// Create conversation type Chat
|
||||
final chat = proto.Chat()
|
||||
..type = proto.ChatType.SINGLE_CONTACT
|
||||
..remoteConversationKey = remoteConversationRecordKey.toProto();
|
||||
|
||||
// Add Chat to account's list
|
||||
// if this fails, don't keep retrying, user can try again later
|
||||
await (await DHTShortArray.openOwned(
|
||||
proto.OwnedDHTRecordPointerProto.fromProto(
|
||||
activeAccountInfo.account.chatList),
|
||||
parent: accountRecordKey))
|
||||
.scope((chatList) async {
|
||||
for (var i = 0; i < chatList.length; i++) {
|
||||
final cbuf = await chatList.getItem(i);
|
||||
if (cbuf == null) {
|
||||
throw Exception('Failed to get chat');
|
||||
}
|
||||
final c = proto.Chat.fromBuffer(cbuf);
|
||||
if (c == chat) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (await chatList.tryAddItem(chat.writeToBuffer()) == false) {
|
||||
throw Exception('Failed to add chat');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Delete a chat
|
||||
Future<void> deleteChat(
|
||||
{required ActiveAccountInfo activeAccountInfo,
|
||||
required TypedKey remoteConversationRecordKey}) async {
|
||||
final accountRecordKey =
|
||||
activeAccountInfo.userLogin.accountRecordInfo.accountRecord.recordKey;
|
||||
|
||||
// Create conversation type Chat
|
||||
final remoteConversationKey = remoteConversationRecordKey.toProto();
|
||||
|
||||
// Add Chat to account's list
|
||||
// if this fails, don't keep retrying, user can try again later
|
||||
await (await DHTShortArray.openOwned(
|
||||
proto.OwnedDHTRecordPointerProto.fromProto(
|
||||
activeAccountInfo.account.chatList),
|
||||
parent: accountRecordKey))
|
||||
.scope((chatList) async {
|
||||
for (var i = 0; i < chatList.length; i++) {
|
||||
final cbuf = await chatList.getItem(i);
|
||||
if (cbuf == null) {
|
||||
throw Exception('Failed to get chat');
|
||||
}
|
||||
final c = proto.Chat.fromBuffer(cbuf);
|
||||
if (c.remoteConversationKey == remoteConversationKey) {
|
||||
await chatList.tryRemoveItem(i);
|
||||
|
||||
if (activeChatState.state == remoteConversationRecordKey) {
|
||||
activeChatState.state = null;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Get the active account contact list
|
||||
@riverpod
|
||||
Future<IList<proto.Chat>?> fetchChatList(FetchChatListRef ref) async {
|
||||
// See if we've logged into this account or if it is locked
|
||||
final activeAccountInfo = await ref.watch(fetchActiveAccountProvider.future);
|
||||
if (activeAccountInfo == null) {
|
||||
return null;
|
||||
}
|
||||
final accountRecordKey =
|
||||
activeAccountInfo.userLogin.accountRecordInfo.accountRecord.recordKey;
|
||||
|
||||
// Decode the chat list from the DHT
|
||||
IList<proto.Chat> out = const IListConst([]);
|
||||
await (await DHTShortArray.openOwned(
|
||||
proto.OwnedDHTRecordPointerProto.fromProto(
|
||||
activeAccountInfo.account.chatList),
|
||||
parent: accountRecordKey))
|
||||
.scope((cList) async {
|
||||
for (var i = 0; i < cList.length; i++) {
|
||||
final cir = await cList.getItem(i);
|
||||
if (cir == null) {
|
||||
throw Exception('Failed to get chat');
|
||||
}
|
||||
out = out.add(proto.Chat.fromBuffer(cir));
|
||||
}
|
||||
});
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
// The selected chat
|
||||
final activeChatState = StateController<TypedKey?>(null);
|
||||
final activeChatStateProvider =
|
||||
StateNotifierProvider<StateController<TypedKey?>, TypedKey?>(
|
||||
(ref) => activeChatState);
|
Loading…
Reference in New Issue
Block a user