You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
325 lines
12 KiB
325 lines
12 KiB
2 years ago
|
part of '../neon_spreed.dart';
|
||
|
|
||
|
class SpreedRoomPage extends StatefulWidget {
|
||
|
const SpreedRoomPage({
|
||
|
required this.bloc,
|
||
|
super.key,
|
||
|
});
|
||
|
|
||
|
final SpreedRoomBloc bloc;
|
||
|
|
||
|
@override
|
||
|
State<SpreedRoomPage> createState() => _SpreedRoomPageState();
|
||
|
}
|
||
|
|
||
|
class _SpreedRoomPageState extends State<SpreedRoomPage> {
|
||
|
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<void> openCall(final spreed.Room room) async {
|
||
|
try {
|
||
|
final client = NeonProvider.of<AccountsBloc>(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<void>(
|
||
|
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<AccountsBloc>(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<String, dynamic> : 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;
|
||
|
}
|
||
|
}
|
||
|
}
|