import 'dart:async'; 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/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; 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 '../../chat_list/chat_list.dart'; import '../../contacts/contacts.dart'; import '../../proto/proto.dart' as proto; import '../../theme/theme.dart'; import '../../tools/tools.dart'; import '../chat.dart'; class ChatComponent extends StatelessWidget { const ChatComponent._( {required TypedKey localUserIdentityKey, required TypedKey remoteConversationRecordKey, required IList messages, required types.User localUser, required types.User remoteUser, super.key}) : _localUserIdentityKey = localUserIdentityKey, _remoteConversationRecordKey = remoteConversationRecordKey, _messages = messages, _localUser = localUser, _remoteUser = remoteUser; final TypedKey _localUserIdentityKey; final TypedKey _remoteConversationRecordKey; final IList _messages; final types.User _localUser; final types.User _remoteUser; // Builder wrapper function that takes care of state management requirements static Widget builder( {required TypedKey remoteConversationRecordKey, Key? key}) => Builder(builder: (context) { // Get all watched dependendies final activeAccountInfo = context.watch(); final accountRecordInfo = context.watch().state.data?.value; if (accountRecordInfo == null) { return debugPage('should always have an account record here'); } final contactList = context.watch().state.data?.value; if (contactList == null) { return debugPage('should always have a contact list here'); } final avconversation = context.select?>( (x) => x.state[remoteConversationRecordKey]); if (avconversation == null) { return debugPage('should always have an active conversation here'); } final conversation = avconversation.data?.value; if (conversation == null) { return avconversation.buildNotData(); } // Make flutter_chat_ui 'User's final localUserIdentityKey = activeAccountInfo .localAccount.identityMaster .identityPublicTypedKey(); final localUser = types.User( id: localUserIdentityKey.toString(), firstName: accountRecordInfo.profile.name, ); final editedName = conversation.contact.editedProfile.name; final remoteUser = types.User( id: proto.TypedKeyProto.fromProto( conversation.contact.identityPublicKey) .toString(), firstName: editedName); // Get the messages to display // and ensure it is safe to operate() on the MessageCubit for this chat final avmessages = context.select>?>( (x) => x.state[remoteConversationRecordKey]); if (avmessages == null) { return waitingPage(); } final messages = avmessages.data?.value; if (messages == null) { return avmessages.buildNotData(); } return ChatComponent._( localUserIdentityKey: localUserIdentityKey, remoteConversationRecordKey: remoteConversationRecordKey, messages: messages, localUser: localUser, remoteUser: remoteUser, key: key); }); ///////////////////////////////////////////////////////////////////// types.Message messageToChatMessage(proto.Message message) { final isLocal = message.author == _localUserIdentityKey.toProto(); final textMessage = types.TextMessage( author: isLocal ? _localUser : _remoteUser, createdAt: (message.timestamp ~/ 1000).toInt(), id: message.timestamp.toString(), text: message.text, ); return textMessage; } Future _addMessage(BuildContext context, proto.Message message) async { if (message.text.isEmpty) { return; } await context.read().operate( _remoteConversationRecordKey, closure: (messagesCubit) => messagesCubit.addMessage(message: message)); } Future _handleSendPressed( BuildContext context, types.PartialText message) async { final protoMessage = proto.Message() ..author = _localUserIdentityKey.toProto() ..timestamp = Veilid.instance.now().toInt64() ..text = message.text; //..signature = signature; await _addMessage(context, protoMessage); } Future _handleAttachmentPressed() async { // } @override Widget build(BuildContext context) { final theme = Theme.of(context); final scale = theme.extension()!; final textTheme = Theme.of(context).textTheme; final chatTheme = makeChatTheme(scale, textTheme); // Convert protobuf messages to chat messages final chatMessages = []; for (final message in _messages) { final chatMessage = messageToChatMessage(message); chatMessages.insert(0, chatMessage); } return DefaultTextStyle( style: textTheme.bodySmall!, child: Align( alignment: AlignmentDirectional.centerEnd, child: Stack( children: [ Column( children: [ Container( height: 48, decoration: BoxDecoration( color: scale.primaryScale.subtleBorder, ), child: Row(children: [ Align( alignment: AlignmentDirectional.centerStart, child: Padding( padding: const EdgeInsetsDirectional.fromSTEB( 16, 0, 16, 0), child: Text(_remoteUser.firstName!, textAlign: TextAlign.start, style: textTheme.titleMedium), )), const Spacer(), IconButton( icon: const Icon(Icons.close), onPressed: () async { context.read().setActiveChat(null); }).paddingLTRB(16, 0, 16, 0) ]), ), Expanded( child: DecoratedBox( decoration: const BoxDecoration(), child: Chat( theme: chatTheme, messages: chatMessages, //onAttachmentPressed: _handleAttachmentPressed, //onMessageTap: _handleMessageTap, //onPreviewDataFetched: _handlePreviewDataFetched, onSendPressed: (message) { singleFuture(this, () async => _handleSendPressed(context, message)); }, //showUserAvatars: false, //showUserNames: true, user: _localUser, ), ), ), ], ), ], ), )); } }