part of '../neon_spreed.dart'; class SpreedRoomPage extends StatefulWidget { const SpreedRoomPage({ required this.bloc, super.key, }); final SpreedRoomBloc bloc; @override State createState() => _SpreedRoomPageState(); } class _SpreedRoomPageState extends State { final defaultChatTheme = const chat_ui.DefaultChatTheme(); late final chatTheme = chat_ui.DefaultChatTheme( backgroundColor: Theme.of(context).colorScheme.background, primaryColor: Theme.of(context).colorScheme.onBackground, inputBackgroundColor: Theme.of(context).colorScheme.primary, inputTextColor: Theme.of(context).colorScheme.onPrimary, inputTextCursorColor: Theme.of(context).colorScheme.onPrimary, receivedMessageBodyTextStyle: defaultChatTheme.receivedMessageBodyTextStyle.copyWith( color: Theme.of(context).colorScheme.onPrimary, ), sentMessageBodyTextStyle: defaultChatTheme.sentMessageBodyTextStyle.copyWith( color: Theme.of(context).colorScheme.onPrimary, ), unreadHeaderTheme: chat_ui.UnreadHeaderTheme( color: Theme.of(context).colorScheme.background, textStyle: TextStyle( color: Theme.of(context).colorScheme.primary, fontWeight: FontWeight.bold, ), ), ); final inputOptions = const chat_ui.InputOptions( sendButtonVisibilityMode: chat_ui.SendButtonVisibilityMode.always, ); late final user = chat_types.User( id: widget.bloc.account.username, ); void onSendPressed(final chat_types.PartialText partialText) { widget.bloc.sendMessage(partialText.text); } Future openCall(final spreed.Room room) async { try { final client = NeonProvider.of(context).activeAccount.value!.client; final settings = (await client.spreed.signaling.getSettings(token: widget.bloc.roomToken)).body.ocs.data; final bloc = SpreedCallBloc( settings, client, widget.bloc.roomToken, room.sessionId, ); if (!mounted) { return; } await Navigator.of(context).push( MaterialPageRoute( builder: (final context) => SpreedCallPage( bloc: bloc, ), ), ); await bloc.leaveCall(); bloc.dispose(); } catch (e, s) { debugPrint(e.toString()); debugPrint(s.toString()); if (mounted) { NeonError.showSnackbar(context, e); } } } @override void initState() { super.initState(); widget.bloc.errors.listen((final error) { NeonError.showSnackbar(context, error); }); } @override Widget build(final BuildContext context) => StreamBuilder( stream: widget.bloc.allLoaded, builder: (final context, final allLoadedSnapshot) => ResultBuilder( stream: widget.bloc.room, builder: (final context, final room) => StreamBuilder( stream: widget.bloc.lastCommonReadMessageId, builder: (final context, final lastCommonReadMessageIdSnapshot) => StreamBuilder( stream: widget.bloc.sendingMessage, builder: (final context, final sendingMessageSnapshot) => ResultBuilder( stream: widget.bloc.messages, builder: (final context, final messages) { final roomType = room.hasData ? spreed.RoomType.fromValue(room.requireData.type) : null; return Scaffold( resizeToAvoidBottomInset: false, appBar: AppBar( titleSpacing: 0, title: Row( children: [ if (room.hasData) ...[ if (roomType!.isSingleUser) ...[ SpreedRoomIcon( roomType: roomType, roomName: room.requireData.name, backgroundColor: Theme.of(context).colorScheme.onPrimary, foregroundColor: Theme.of(context).colorScheme.primary, ), const SizedBox( width: 10, ), ], Flexible( child: Text(room.requireData.displayName), ), ], if (room.error != null) ...[ const SizedBox( width: 8, ), Icon( Icons.error_outline, size: 30, color: Theme.of(context).colorScheme.onPrimary, ), ], if (room.isLoading) ...[ const SizedBox( width: 8, ), Expanded( child: NeonLinearProgressIndicator( color: Theme.of(context).appBarTheme.foregroundColor, ), ), ], ], ), actions: [ if (room.hasData && room.requireData.readOnly == 0) ...[ if (room.requireData.hasCall) ...[ SpreedCallButton( type: SpreedCallButtonType.joinCall, onPressed: () async { await openCall(room.requireData); }, ), ] else if (room.requireData.canStartCall) ...[ SpreedCallButton( type: SpreedCallButtonType.startCall, onPressed: () async { await openCall(room.requireData); }, ), ], ], ], ), body: chat_ui.Chat( useTopSafeAreaInset: false, showUserNames: true, showUserAvatars: !(roomType?.isSingleUser ?? true), theme: chatTheme, inputOptions: inputOptions, scrollToUnreadOptions: chat_ui.ScrollToUnreadOptions( lastReadMessageId: room.data?.lastReadMessage.toString(), scrollOnOpen: true, scrollDelay: Duration.zero, ), avatarBuilder: (final username) => NeonUserAvatar( username: username, account: NeonProvider.of(context).activeAccount.value!, ), textMessageBuilder: ( final message, { required final messageWidth, required final showName, }) { final matchers = [ if (message.metadata != null) ...[ NeonRichObject( parameters: message.metadata!, ), ], ]; return chat_ui.TextMessage( emojiEnlargementBehavior: chat_ui.EmojiEnlargementBehavior.multi, hideBackgroundOnEmojiMessages: true, message: message, showName: showName, usePreviewData: true, options: chat_ui.TextMessageOptions( matchers: matchers, ), ); }, systemMessageBuilder: (final message) { final matchers = [ if (message.metadata != null) ...[ NeonRichObject( parameters: message.metadata!, ), ], ]; return chat_ui.SystemMessage( message: message.text, options: chat_ui.TextMessageOptions( matchers: matchers, ), ); }, customBottomWidget: Column( children: [ NeonError( messages.error, onRetry: () async { await widget.bloc.refresh(); }, ), if (messages.isLoading) ...[ const NeonLinearProgressIndicator( margin: EdgeInsets.symmetric(horizontal: 10, vertical: 5), ), ], if ((room.data?.readOnly ?? 0) == 0) ...[ chat_ui.Input( onSendPressed: onSendPressed, options: inputOptions, ), ], ], ), user: user, onEndReached: () async { await widget.bloc.loadMoreMessages(); }, onSendPressed: onSendPressed, isLastPage: allLoadedSnapshot.data ?? false, messages: [ if (sendingMessageSnapshot.hasData) ...[ chat_types.TextMessage( id: 'sending', author: user, text: sendingMessageSnapshot.data!, showStatus: true, status: chat_types.Status.sending, ), ], if (messages.hasData) ...[ ...messages.requireData .map( (final message) => _spreedMessageToChatMessage( message, lastCommonReadMessageId: lastCommonReadMessageIdSnapshot.data, ), ) .whereNotNull(), ], ], ), ); }, ), ), ), ), ); chat_types.Message? _spreedMessageToChatMessage( final spreed.ChatMessageInterface message, { final int? lastCommonReadMessageId, }) { final id = message.id.toString(); final author = chat_types.User( id: message.actorId, firstName: message.actorDisplayName, imageUrl: message.actorId, ); final createdAt = message.timestamp * 1000; // TODO: Doesn't work yet in the UI. See https://github.com/flyerhq/flutter_chat_ui/pull/256 final repliedMessage = message is spreed.ChatMessageWithParent && message.parent != null ? _spreedMessageToChatMessage(message.parent!) : null; final status = lastCommonReadMessageId != null && lastCommonReadMessageId >= message.id ? chat_types.Status.seen : chat_types.Status.sent; final metadata = message.messageParameters is Map ? message.messageParameters as Map : null; switch (spreed.MessageType.values.byName(message.messageType)) { case spreed.MessageType.comment: return chat_types.TextMessage( id: id, author: author, createdAt: createdAt, repliedMessage: repliedMessage, text: message.message, showStatus: true, status: status, metadata: metadata, ); case spreed.MessageType.command: case spreed.MessageType.system: return chat_types.SystemMessage( id: id, createdAt: createdAt, text: message.message, metadata: metadata, ); default: return null; } } }