messages rendering
This commit is contained in:
parent
8b9cb7cf9c
commit
d1f070e5c8
8 changed files with 387 additions and 122 deletions
18
lib/controllers/current_room_controller.dart
Normal file
18
lib/controllers/current_room_controller.dart
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
import "package:flutter_riverpod/flutter_riverpod.dart";
|
||||||
|
import "package:nexus/controllers/spaces_controller.dart";
|
||||||
|
import "package:nexus/helpers/extension_helper.dart";
|
||||||
|
import "package:nexus/models/full_room.dart";
|
||||||
|
|
||||||
|
class CurrentRoomController extends AsyncNotifier<FullRoom> {
|
||||||
|
@override
|
||||||
|
Future<FullRoom> build() async => (await ref.watch(
|
||||||
|
SpacesController.provider.future,
|
||||||
|
))[0].children[0].roomData.fullRoom;
|
||||||
|
|
||||||
|
void set(FullRoom room) => state = AsyncValue.data(room);
|
||||||
|
|
||||||
|
static final provider =
|
||||||
|
AsyncNotifierProvider<CurrentRoomController, FullRoom>(
|
||||||
|
CurrentRoomController.new,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -1,27 +1,132 @@
|
||||||
import "package:flutter_chat_core/flutter_chat_core.dart";
|
import "package:flutter_chat_core/flutter_chat_core.dart";
|
||||||
|
import "package:flutter_chat_core/flutter_chat_core.dart" as chat;
|
||||||
import "package:flutter_riverpod/flutter_riverpod.dart";
|
import "package:flutter_riverpod/flutter_riverpod.dart";
|
||||||
|
import "package:matrix/matrix.dart";
|
||||||
|
|
||||||
class RoomChatController extends Notifier<ChatController> {
|
class RoomChatController extends AsyncNotifier<ChatController> {
|
||||||
RoomChatController(this.roomId);
|
RoomChatController(this.room);
|
||||||
final String roomId;
|
final Room room;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
InMemoryChatController build() => InMemoryChatController();
|
Future<ChatController> build() async {
|
||||||
|
final timeline = await room.getTimeline();
|
||||||
|
|
||||||
// void setRoom(Room room) => state = (await ref.watch(ClientController.provider.future));
|
final controller = InMemoryChatController(
|
||||||
|
messages: (await Future.wait(
|
||||||
|
timeline.events.map(toMessage),
|
||||||
|
)).toList().reversed.nonNulls.toList(),
|
||||||
|
);
|
||||||
|
return controller;
|
||||||
|
}
|
||||||
|
|
||||||
void send(String message) {
|
Future<void> insertMessage(Message message) async {
|
||||||
state.insertMessage(
|
final controller = await future;
|
||||||
|
return controller.insertMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> updateMessage(Message message, Message newMessage) async {
|
||||||
|
final controller = await future;
|
||||||
|
return controller.updateMessage(message, newMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Message?> toMessage(Event event) async {
|
||||||
|
final replyId = event.relationshipType == RelationshipTypes.reply
|
||||||
|
? event.relationshipEventId
|
||||||
|
: null;
|
||||||
|
final metadata = {
|
||||||
|
"eventType": event.type,
|
||||||
|
"displayName": event.senderFromMemoryOrFallback.displayName,
|
||||||
|
};
|
||||||
|
return event.redacted
|
||||||
|
? Message.text(
|
||||||
|
metadata: metadata,
|
||||||
|
id: event.eventId,
|
||||||
|
authorId: event.senderId,
|
||||||
|
text: "~~This message has been redacted.~~",
|
||||||
|
deletedAt: event.redactedBecause?.originServerTs,
|
||||||
|
)
|
||||||
|
: switch (event.type) {
|
||||||
|
EventTypes.Message => switch (event.messageType) {
|
||||||
|
MessageTypes.Image => Message.image(
|
||||||
|
metadata: metadata,
|
||||||
|
id: event.eventId,
|
||||||
|
authorId: event.senderId,
|
||||||
|
source: (await event.getAttachmentUri()).toString(),
|
||||||
|
replyToMessageId: replyId,
|
||||||
|
deliveredAt: event.originServerTs,
|
||||||
|
),
|
||||||
|
MessageTypes.Audio => Message.audio(
|
||||||
|
metadata: metadata,
|
||||||
|
id: event.eventId,
|
||||||
|
authorId: event.senderId,
|
||||||
|
text: event.body,
|
||||||
|
replyToMessageId: replyId,
|
||||||
|
source: (await event.getAttachmentUri()).toString(),
|
||||||
|
deliveredAt: event.originServerTs,
|
||||||
|
duration: Duration(hours: 1),
|
||||||
|
),
|
||||||
|
MessageTypes.File => Message.file(
|
||||||
|
name: event.content["filename"].toString(),
|
||||||
|
metadata: metadata,
|
||||||
|
id: event.eventId,
|
||||||
|
authorId: event.senderId,
|
||||||
|
source: (await event.getAttachmentUri()).toString(),
|
||||||
|
replyToMessageId: replyId,
|
||||||
|
deliveredAt: event.originServerTs,
|
||||||
|
),
|
||||||
|
_ => Message.text(
|
||||||
|
metadata: metadata,
|
||||||
|
id: event.eventId,
|
||||||
|
authorId: event.senderId,
|
||||||
|
text: event.body,
|
||||||
|
replyToMessageId: replyId,
|
||||||
|
deliveredAt: event.originServerTs,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
EventTypes.RoomMember => Message.system(
|
||||||
|
metadata: metadata,
|
||||||
|
id: event.eventId,
|
||||||
|
authorId: event.senderId,
|
||||||
|
text:
|
||||||
|
"${event.senderFromMemoryOrFallback.calcDisplayname()} joined the room.",
|
||||||
|
),
|
||||||
|
EventTypes.Redaction => null,
|
||||||
|
_ => Message.unsupported(
|
||||||
|
metadata: metadata,
|
||||||
|
id: event.eventId,
|
||||||
|
authorId: event.senderId,
|
||||||
|
replyToMessageId: replyId,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> send(String message) async {
|
||||||
|
insertMessage(
|
||||||
Message.text(
|
Message.text(
|
||||||
id: DateTime.now().millisecondsSinceEpoch.toString(),
|
id: DateTime.now().millisecondsSinceEpoch.toString(),
|
||||||
authorId: "foo",
|
authorId: room.client.userID!,
|
||||||
text: message,
|
text: message,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
await room.sendTextEvent(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
static final provider =
|
Future<chat.User> resolveUser(String id) async {
|
||||||
NotifierProvider.family<RoomChatController, ChatController, String>(
|
final user = await room.client.getUserProfile(id);
|
||||||
|
return chat.User(
|
||||||
|
id: id,
|
||||||
|
name: user.displayname,
|
||||||
|
imageSource: (await user.avatarUrl?.getThumbnailUri(
|
||||||
|
room.client,
|
||||||
|
width: 24,
|
||||||
|
height: 24,
|
||||||
|
))?.toString(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static final provider = AsyncNotifierProvider.family
|
||||||
|
.autoDispose<RoomChatController, ChatController, Room>(
|
||||||
RoomChatController.new,
|
RoomChatController.new,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,16 +20,20 @@ extension BetterWhen<T> on AsyncValue<T> {
|
||||||
|
|
||||||
extension GetFullRoom on Room {
|
extension GetFullRoom on Room {
|
||||||
Future<FullRoom> get fullRoom async {
|
Future<FullRoom> get fullRoom async {
|
||||||
final thumb = await avatar?.getThumbnailUri(client, width: 24, height: 24);
|
|
||||||
return FullRoom(
|
return FullRoom(
|
||||||
roomData: this,
|
roomData: this,
|
||||||
title: getLocalizedDisplayname(),
|
title: getLocalizedDisplayname(),
|
||||||
avatar: thumb == null
|
avatar: await avatar?.asImage(client),
|
||||||
? null
|
);
|
||||||
: Image.network(
|
}
|
||||||
thumb.toString(),
|
}
|
||||||
headers: {"authorization": "Bearer ${client.accessToken}"},
|
|
||||||
),
|
extension GetImage on Uri {
|
||||||
|
Future<Image?> asImage(Client client) async {
|
||||||
|
final thumb = await getThumbnailUri(client, width: 24, height: 24);
|
||||||
|
return Image.network(
|
||||||
|
thumb.toString(),
|
||||||
|
headers: {"authorization": "Bearer ${client.accessToken}"},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
import "dart:io";
|
|
||||||
import "package:flutter_riverpod/flutter_riverpod.dart";
|
import "package:flutter_riverpod/flutter_riverpod.dart";
|
||||||
import "package:nexus/widgets/room_chat.dart";
|
import "package:nexus/widgets/room_chat.dart";
|
||||||
import "package:nexus/widgets/sidebar.dart";
|
import "package:nexus/widgets/sidebar.dart";
|
||||||
|
import "package:scaled_app/scaled_app.dart";
|
||||||
import "package:window_manager/window_manager.dart";
|
import "package:window_manager/window_manager.dart";
|
||||||
import "package:flutter/material.dart";
|
import "package:flutter/material.dart";
|
||||||
import "package:dynamic_system_colors/dynamic_system_colors.dart";
|
import "package:dynamic_system_colors/dynamic_system_colors.dart";
|
||||||
import "package:window_size/window_size.dart";
|
import "package:window_size/window_size.dart";
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
ScaledWidgetsFlutterBinding.ensureInitialized(scaleFactor: (_) => 1.4);
|
||||||
|
|
||||||
await windowManager.ensureInitialized();
|
await windowManager.ensureInitialized();
|
||||||
await windowManager.waitUntilReadyToShow(
|
await windowManager.waitUntilReadyToShow(
|
||||||
|
|
@ -48,28 +48,7 @@ class App extends StatelessWidget {
|
||||||
builder: (context) => Row(
|
builder: (context) => Row(
|
||||||
children: [
|
children: [
|
||||||
if (isDesktop) Sidebar(),
|
if (isDesktop) Sidebar(),
|
||||||
Expanded(
|
Expanded(child: RoomChat(isDesktop: isDesktop)),
|
||||||
child: Scaffold(
|
|
||||||
appBar: AppBar(
|
|
||||||
leading: isDesktop
|
|
||||||
? null
|
|
||||||
: DrawerButton(
|
|
||||||
onPressed: () =>
|
|
||||||
Scaffold.of(context).openDrawer(),
|
|
||||||
),
|
|
||||||
actionsPadding: EdgeInsets.symmetric(horizontal: 8),
|
|
||||||
title: Text("Some Chat Name"),
|
|
||||||
actions: [
|
|
||||||
if (!(Platform.isAndroid || Platform.isIOS))
|
|
||||||
IconButton(
|
|
||||||
onPressed: () => exit(0),
|
|
||||||
icon: Icon(Icons.close),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
body: RoomChat(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -1,92 +1,223 @@
|
||||||
|
import "dart:io";
|
||||||
|
|
||||||
|
import "package:flutter/foundation.dart";
|
||||||
import "package:flutter/material.dart";
|
import "package:flutter/material.dart";
|
||||||
import "package:flutter_chat_core/flutter_chat_core.dart";
|
import "package:flutter_chat_core/flutter_chat_core.dart";
|
||||||
import "package:flutter_chat_ui/flutter_chat_ui.dart";
|
import "package:flutter_chat_ui/flutter_chat_ui.dart";
|
||||||
import "package:flutter_link_previewer/flutter_link_previewer.dart";
|
import "package:flutter_link_previewer/flutter_link_previewer.dart";
|
||||||
|
import "package:flyer_chat_file_message/flyer_chat_file_message.dart";
|
||||||
import "package:flyer_chat_image_message/flyer_chat_image_message.dart";
|
import "package:flyer_chat_image_message/flyer_chat_image_message.dart";
|
||||||
import "package:flyer_chat_system_message/flyer_chat_system_message.dart";
|
import "package:flyer_chat_system_message/flyer_chat_system_message.dart";
|
||||||
import "package:flyer_chat_text_message/flyer_chat_text_message.dart";
|
import "package:flyer_chat_text_message/flyer_chat_text_message.dart";
|
||||||
import "package:hooks_riverpod/hooks_riverpod.dart";
|
import "package:hooks_riverpod/hooks_riverpod.dart";
|
||||||
|
import "package:nexus/controllers/current_room_controller.dart";
|
||||||
import "package:nexus/controllers/room_chat_controller.dart";
|
import "package:nexus/controllers/room_chat_controller.dart";
|
||||||
|
import "package:nexus/helpers/extension_helper.dart";
|
||||||
import "package:nexus/helpers/launch_helper.dart";
|
import "package:nexus/helpers/launch_helper.dart";
|
||||||
|
|
||||||
class RoomChat extends HookConsumerWidget {
|
class RoomChat extends HookConsumerWidget {
|
||||||
const RoomChat({super.key});
|
final bool isDesktop;
|
||||||
|
const RoomChat({required this.isDesktop, super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final urlRegex = RegExp(r"https?://[^\s\]\(\)]+");
|
final urlRegex = RegExp(r"https?://[^\s\]\(\)]+");
|
||||||
final controller = RoomChatController.provider("1");
|
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
return Chat(
|
return ref
|
||||||
currentUserId: "foo",
|
.watch(CurrentRoomController.provider)
|
||||||
theme: ChatTheme.fromThemeData(theme).copyWith(
|
.betterWhen(
|
||||||
colors: ChatColors.fromThemeData(theme).copyWith(
|
data: (room) {
|
||||||
primary: theme.colorScheme.primaryContainer,
|
final controllerProvider = RoomChatController.provider(
|
||||||
onPrimary: theme.colorScheme.onPrimaryContainer,
|
room.roomData,
|
||||||
),
|
);
|
||||||
),
|
final headers = {
|
||||||
builders: Builders(
|
"authorization": "Bearer ${room.roomData.client.accessToken}",
|
||||||
composerBuilder: (_) => Composer(
|
};
|
||||||
sendIconColor: theme.colorScheme.primary,
|
return Scaffold(
|
||||||
sendOnEnter: true,
|
appBar: AppBar(
|
||||||
),
|
leading: isDesktop
|
||||||
textMessageBuilder:
|
? null
|
||||||
(
|
: DrawerButton(onPressed: Scaffold.of(context).openDrawer),
|
||||||
context,
|
actionsPadding: EdgeInsets.symmetric(horizontal: 8),
|
||||||
message,
|
title: Text(room.title),
|
||||||
index, {
|
actions: [
|
||||||
required bool isSentByMe,
|
if (!(Platform.isAndroid || Platform.isIOS))
|
||||||
MessageGroupStatus? groupStatus,
|
IconButton(
|
||||||
}) => FlyerChatTextMessage(
|
onPressed: () => exit(0),
|
||||||
message: message.copyWith(
|
icon: Icon(Icons.close),
|
||||||
text: message.text.replaceAllMapped(
|
),
|
||||||
urlRegex,
|
],
|
||||||
(match) => "[${match.group(0)}](${match.group(0)})",
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
index: index,
|
body: ref
|
||||||
onLinkTap: (url, _) =>
|
.watch(controllerProvider)
|
||||||
ref.watch(LaunchHelper.provider).launchUrl(Uri.parse(url)),
|
.betterWhen(
|
||||||
linksDecoration: TextDecoration.underline,
|
data: (controller) => Chat(
|
||||||
sentLinksColor: Colors.blue,
|
currentUserId: room.roomData.client.userID!,
|
||||||
receivedLinksColor: Colors.blue,
|
theme: ChatTheme.fromThemeData(theme).copyWith(
|
||||||
),
|
colors: ChatColors.fromThemeData(theme).copyWith(
|
||||||
linkPreviewBuilder: (_, message, isSentByMe) => LinkPreview(
|
primary: theme.colorScheme.primaryContainer,
|
||||||
text: urlRegex.firstMatch(message.text)?.group(0) ?? "",
|
onPrimary: theme.colorScheme.onPrimaryContainer,
|
||||||
backgroundColor: isSentByMe
|
),
|
||||||
? theme.colorScheme.inversePrimary
|
),
|
||||||
: theme.colorScheme.surfaceContainerLow,
|
builders: Builders(
|
||||||
insidePadding: EdgeInsets.symmetric(vertical: 8, horizontal: 16),
|
composerBuilder: (_) => Composer(
|
||||||
linkPreviewData: message.linkPreviewData,
|
sendIconColor: theme.colorScheme.primary,
|
||||||
onLinkPreviewDataFetched: (linkPreviewData) {
|
sendOnEnter: true,
|
||||||
ref
|
autofocus: true,
|
||||||
.watch(controller)
|
),
|
||||||
.updateMessage(
|
unsupportedMessageBuilder:
|
||||||
message,
|
(
|
||||||
message.copyWith(linkPreviewData: linkPreviewData),
|
_,
|
||||||
);
|
message,
|
||||||
|
index, {
|
||||||
|
required bool isSentByMe,
|
||||||
|
MessageGroupStatus? groupStatus,
|
||||||
|
}) => kDebugMode
|
||||||
|
? FlyerChatTextMessage(
|
||||||
|
message: TextMessage(
|
||||||
|
id: message.id,
|
||||||
|
authorId: message.authorId,
|
||||||
|
text:
|
||||||
|
"Unsupported message type: ${message.metadata?["eventType"]}",
|
||||||
|
),
|
||||||
|
receivedBackgroundColor: Colors.red,
|
||||||
|
sentBackgroundColor: Colors.red,
|
||||||
|
index: index,
|
||||||
|
)
|
||||||
|
: SizedBox.shrink(),
|
||||||
|
textMessageBuilder:
|
||||||
|
(
|
||||||
|
context,
|
||||||
|
message,
|
||||||
|
index, {
|
||||||
|
required bool isSentByMe,
|
||||||
|
MessageGroupStatus? groupStatus,
|
||||||
|
}) => Column(
|
||||||
|
crossAxisAlignment: isSentByMe
|
||||||
|
? CrossAxisAlignment.end
|
||||||
|
: CrossAxisAlignment.start,
|
||||||
|
spacing: 8,
|
||||||
|
children: [
|
||||||
|
SizedBox(height: 8),
|
||||||
|
|
||||||
|
FlyerChatTextMessage(
|
||||||
|
topWidget: Padding(
|
||||||
|
padding: EdgeInsets.only(bottom: 12),
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () => showAboutDialog(
|
||||||
|
context: context,
|
||||||
|
), // TODO: Show user profile
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
spacing: 8,
|
||||||
|
children: [
|
||||||
|
Avatar(
|
||||||
|
userId: message.authorId,
|
||||||
|
headers: headers,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
message.metadata?["displayName"] ??
|
||||||
|
message.authorId,
|
||||||
|
style: theme.textTheme.titleMedium
|
||||||
|
?.copyWith(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
message: message.copyWith(
|
||||||
|
text: message.text.replaceAllMapped(
|
||||||
|
urlRegex,
|
||||||
|
(match) =>
|
||||||
|
"[${match.group(0)}](${match.group(0)})",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
showTime: true,
|
||||||
|
index: index,
|
||||||
|
onLinkTap: (url, _) => ref
|
||||||
|
.watch(LaunchHelper.provider)
|
||||||
|
.launchUrl(Uri.parse(url)),
|
||||||
|
linksDecoration: TextDecoration.underline,
|
||||||
|
sentLinksColor: Colors.blue,
|
||||||
|
receivedLinksColor: Colors.blue,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
linkPreviewBuilder: (_, message, isSentByMe) =>
|
||||||
|
LinkPreview(
|
||||||
|
text:
|
||||||
|
urlRegex.firstMatch(message.text)?.group(0) ??
|
||||||
|
"",
|
||||||
|
backgroundColor: isSentByMe
|
||||||
|
? theme.colorScheme.inversePrimary
|
||||||
|
: theme.colorScheme.surfaceContainerLow,
|
||||||
|
insidePadding: EdgeInsets.symmetric(
|
||||||
|
vertical: 8,
|
||||||
|
horizontal: 16,
|
||||||
|
),
|
||||||
|
linkPreviewData: message.linkPreviewData,
|
||||||
|
onLinkPreviewDataFetched: (linkPreviewData) => ref
|
||||||
|
.watch(controllerProvider.notifier)
|
||||||
|
.updateMessage(
|
||||||
|
message,
|
||||||
|
message.copyWith(
|
||||||
|
linkPreviewData: linkPreviewData,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
imageMessageBuilder:
|
||||||
|
(
|
||||||
|
_,
|
||||||
|
message,
|
||||||
|
index, {
|
||||||
|
required bool isSentByMe,
|
||||||
|
MessageGroupStatus? groupStatus,
|
||||||
|
}) => FlyerChatImageMessage(
|
||||||
|
message: message,
|
||||||
|
index: index,
|
||||||
|
headers: headers,
|
||||||
|
),
|
||||||
|
fileMessageBuilder:
|
||||||
|
(
|
||||||
|
_,
|
||||||
|
message,
|
||||||
|
index, {
|
||||||
|
required bool isSentByMe,
|
||||||
|
MessageGroupStatus? groupStatus,
|
||||||
|
}) => InkWell(
|
||||||
|
onTap: () => showAboutDialog(
|
||||||
|
context: context,
|
||||||
|
), // TODO: Download
|
||||||
|
child: FlyerChatFileMessage(
|
||||||
|
message: message,
|
||||||
|
index: index,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
systemMessageBuilder:
|
||||||
|
(
|
||||||
|
_,
|
||||||
|
message,
|
||||||
|
index, {
|
||||||
|
required bool isSentByMe,
|
||||||
|
MessageGroupStatus? groupStatus,
|
||||||
|
}) => FlyerChatSystemMessage(
|
||||||
|
message: message,
|
||||||
|
index: index,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onMessageSend: ref
|
||||||
|
.watch(controllerProvider.notifier)
|
||||||
|
.send,
|
||||||
|
resolveUser: ref
|
||||||
|
.watch(controllerProvider.notifier)
|
||||||
|
.resolveUser,
|
||||||
|
chatController: controller,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
),
|
);
|
||||||
imageMessageBuilder:
|
|
||||||
(
|
|
||||||
_,
|
|
||||||
message,
|
|
||||||
index, {
|
|
||||||
required bool isSentByMe,
|
|
||||||
MessageGroupStatus? groupStatus,
|
|
||||||
}) => FlyerChatImageMessage(message: message, index: index),
|
|
||||||
systemMessageBuilder:
|
|
||||||
(
|
|
||||||
_,
|
|
||||||
message,
|
|
||||||
index, {
|
|
||||||
required bool isSentByMe,
|
|
||||||
MessageGroupStatus? groupStatus,
|
|
||||||
}) => FlyerChatSystemMessage(message: message, index: index),
|
|
||||||
),
|
|
||||||
onMessageSend: ref.watch(controller.notifier).send,
|
|
||||||
resolveUser: (id) async => User(id: id, imageSource: "foo"),
|
|
||||||
chatController: ref.watch(controller),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import "package:flutter/material.dart";
|
import "package:flutter/material.dart";
|
||||||
import "package:flutter_hooks/flutter_hooks.dart";
|
import "package:flutter_hooks/flutter_hooks.dart";
|
||||||
import "package:hooks_riverpod/hooks_riverpod.dart";
|
import "package:hooks_riverpod/hooks_riverpod.dart";
|
||||||
|
import "package:nexus/controllers/current_room_controller.dart";
|
||||||
import "package:nexus/controllers/spaces_controller.dart";
|
import "package:nexus/controllers/spaces_controller.dart";
|
||||||
import "package:nexus/helpers/extension_helper.dart";
|
import "package:nexus/helpers/extension_helper.dart";
|
||||||
import "package:nexus/widgets/avatar.dart";
|
import "package:nexus/widgets/avatar.dart";
|
||||||
|
|
@ -10,7 +11,8 @@ class Sidebar extends HookConsumerWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final index = useState(0);
|
final selectedSpace = useState(0);
|
||||||
|
final selectedRoom = useState(0);
|
||||||
return Drawer(
|
return Drawer(
|
||||||
shape: Border(),
|
shape: Border(),
|
||||||
child: Row(
|
child: Row(
|
||||||
|
|
@ -25,7 +27,7 @@ class Sidebar extends HookConsumerWidget {
|
||||||
},
|
},
|
||||||
data: (spaces) => NavigationRail(
|
data: (spaces) => NavigationRail(
|
||||||
scrollable: true,
|
scrollable: true,
|
||||||
onDestinationSelected: (value) => index.value = value,
|
onDestinationSelected: (value) => selectedSpace.value = value,
|
||||||
destinations: spaces
|
destinations: spaces
|
||||||
.map(
|
.map(
|
||||||
(space) => NavigationRailDestination(
|
(space) => NavigationRailDestination(
|
||||||
|
|
@ -35,7 +37,7 @@ class Sidebar extends HookConsumerWidget {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.toList(),
|
.toList(),
|
||||||
selectedIndex: index.value,
|
selectedIndex: selectedSpace.value,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
|
|
@ -43,7 +45,7 @@ class Sidebar extends HookConsumerWidget {
|
||||||
.watch(SpacesController.provider)
|
.watch(SpacesController.provider)
|
||||||
.betterWhen(
|
.betterWhen(
|
||||||
data: (spaces) {
|
data: (spaces) {
|
||||||
final space = spaces[index.value];
|
final space = spaces[selectedSpace.value];
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: Colors.transparent,
|
backgroundColor: Colors.transparent,
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
|
|
@ -71,7 +73,7 @@ class Sidebar extends HookConsumerWidget {
|
||||||
icon: Avatar(
|
icon: Avatar(
|
||||||
room.avatar,
|
room.avatar,
|
||||||
room.title,
|
room.title,
|
||||||
fallback: index.value == 1
|
fallback: selectedSpace.value == 1
|
||||||
? null
|
? null
|
||||||
: Icon(Icons.numbers),
|
: Icon(Icons.numbers),
|
||||||
),
|
),
|
||||||
|
|
@ -79,7 +81,15 @@ class Sidebar extends HookConsumerWidget {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.toList(),
|
.toList(),
|
||||||
selectedIndex: space.children.isEmpty ? null : 0,
|
onDestinationSelected: (value) {
|
||||||
|
selectedRoom.value = value;
|
||||||
|
ref
|
||||||
|
.watch(CurrentRoomController.provider.notifier)
|
||||||
|
.set(space.children[value]);
|
||||||
|
},
|
||||||
|
selectedIndex: space.children.isEmpty
|
||||||
|
? null
|
||||||
|
: selectedRoom.value,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
|
||||||
16
pubspec.lock
16
pubspec.lock
|
|
@ -582,6 +582,14 @@ packages:
|
||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
version: "0.0.0"
|
||||||
|
flyer_chat_file_message:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: flyer_chat_file_message
|
||||||
|
sha256: "9d3e40819ebd3a32c6821e32a54caf7675af80dd05ce679f8113277f2379ecf4"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.3.1"
|
||||||
flyer_chat_image_message:
|
flyer_chat_image_message:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|
@ -1166,6 +1174,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.28.0"
|
version: "0.28.0"
|
||||||
|
scaled_app:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: scaled_app
|
||||||
|
sha256: a2ad9f22cf2200a5ce455b59c5ea7bfb09a84acfc52452d1db54f4958c99d76a
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.3.0"
|
||||||
screen_retriever:
|
screen_retriever:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
||||||
10
pubspec.yaml
10
pubspec.yaml
|
|
@ -40,17 +40,19 @@ dependencies:
|
||||||
url: https://github.com/google/flutter-desktop-embedding
|
url: https://github.com/google/flutter-desktop-embedding
|
||||||
path: plugins/window_size
|
path: plugins/window_size
|
||||||
flutter_chat_core: ^2.0.0
|
flutter_chat_core: ^2.0.0
|
||||||
flyer_chat_image_message: ^2.0.0
|
flyer_chat_image_message: ^2.2.2
|
||||||
flyer_chat_system_message: ^2.0.0
|
flyer_chat_system_message: ^2.1.13
|
||||||
flutter_link_previewer: ^4.0.0
|
flyer_chat_text_message: ^2.5.2
|
||||||
flyer_chat_text_message: ^2.0.0
|
flyer_chat_file_message: ^2.3.1
|
||||||
flutter_chat_ui:
|
flutter_chat_ui:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/Henry-Hiles/flutter_chat_ui
|
url: https://github.com/Henry-Hiles/flutter_chat_ui
|
||||||
path: packages/flutter_chat_ui
|
path: packages/flutter_chat_ui
|
||||||
|
flutter_link_previewer: ^4.1.2
|
||||||
matrix: ^3.0.2
|
matrix: ^3.0.2
|
||||||
sqflite_common_ffi: ^2.3.6
|
sqflite_common_ffi: ^2.3.6
|
||||||
color_hash: ^1.0.1
|
color_hash: ^1.0.1
|
||||||
|
scaled_app: ^2.3.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
build_runner: ^2.4.11
|
build_runner: ^2.4.11
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue