This commit is contained in:
Henry Hiles 2025-12-07 16:31:03 -05:00
commit 63a9d2d169
No known key found for this signature in database
15 changed files with 388 additions and 299 deletions

View file

@ -18,10 +18,8 @@ class EventsController extends AsyncNotifier<GetRoomEventsResponse> {
return response; return response;
} }
Future<GetRoomEventsResponse> prev() async { Future<GetRoomEventsResponse> prev() async =>
final resp = await build(from: ref.read(FromController.provider(room))); build(from: ref.read(FromController.provider(room)));
return resp;
}
static final provider = AsyncNotifierProvider.autoDispose static final provider = AsyncNotifierProvider.autoDispose
.family<EventsController, GetRoomEventsResponse, Room>( .family<EventsController, GetRoomEventsResponse, Room>(

View file

@ -9,17 +9,15 @@ class MessageController extends AsyncNotifier<TextMessage?> {
@override @override
Future<TextMessage?> build() async { Future<TextMessage?> build() async {
final room = await ref.watch( final room = await ref.watch(SelectedRoomController.provider.future);
SelectedRoomController.provider.selectAsync((a) => a),
);
if (room == null) return null; if (room == null) return null;
final event = await room.roomData.getEventById(id); final event = await room.roomData.getEventById(id);
return (await event?.toMessage(mustBeText: true)) as TextMessage?; return (await event?.toMessage(mustBeText: true)) as TextMessage?;
} }
static final provider = static final provider = AsyncNotifierProvider.family
AsyncNotifierProvider.family<MessageController, TextMessage?, String>( .autoDispose<MessageController, TextMessage?, String>(
MessageController.new, MessageController.new,
); );
} }

View file

@ -3,6 +3,7 @@ import "package:flutter_chat_core/flutter_chat_core.dart";
import "package:flutter_chat_core/flutter_chat_core.dart" as chat; 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"; import "package:matrix/matrix.dart";
import "package:nexus/controllers/avatar_controller.dart";
import "package:nexus/controllers/events_controller.dart"; import "package:nexus/controllers/events_controller.dart";
import "package:nexus/helpers/extensions/event_to_message.dart"; import "package:nexus/helpers/extensions/event_to_message.dart";
import "package:nexus/helpers/extensions/list_to_messages.dart"; import "package:nexus/helpers/extensions/list_to_messages.dart";
@ -13,7 +14,9 @@ class RoomChatController extends AsyncNotifier<ChatController> {
@override @override
Future<ChatController> build() async { Future<ChatController> build() async {
final response = await ref.watch(EventsController.provider(room).future); final response = await ref.watch(
EventsController.provider(room).selectAsync((a) => a),
);
ref.onDispose( ref.onDispose(
room.client.onTimelineEvent.stream.listen((event) async { room.client.onTimelineEvent.stream.listen((event) async {
@ -75,10 +78,10 @@ class RoomChatController extends AsyncNotifier<ChatController> {
.watch(EventsController.provider(room).notifier) .watch(EventsController.provider(room).notifier)
.prev(); .prev();
await controller.insertAllMessages( final messages = await response.chunk.toMessages(room);
await response.chunk.toMessages(room),
index: 0, await controller.insertAllMessages(messages, index: 0);
); ref.notifyListeners();
} }
Future<void> markRead() async { Future<void> markRead() async {
@ -92,30 +95,22 @@ class RoomChatController extends AsyncNotifier<ChatController> {
Future<void> updateMessage(Message message, Message newMessage) async => Future<void> updateMessage(Message message, Message newMessage) async =>
(await future).updateMessage(message, newMessage); (await future).updateMessage(message, newMessage);
Future<void> send(Message message, {Message? replyTo}) async { Future<void> send(String message, {Message? replyTo}) async =>
final controller = await future; room.sendTextEvent(
controller.insertMessage(message); message,
if (message is TextMessage) {
await room.sendTextEvent(
message.text,
inReplyTo: replyTo == null ? null : await room.getEventById(replyTo.id), inReplyTo: replyTo == null ? null : await room.getEventById(replyTo.id),
); );
}
// TODO: Handle other types of message
}
Future<chat.User> resolveUser(String id) async { Future<chat.User> resolveUser(String id) async {
final user = await room.client.getUserProfile(id); final user = await room.client.getUserProfile(id);
return chat.User( return chat.User(
id: id, id: id,
name: user.displayname, name: user.displayname,
imageSource: (await user.avatarUrl?.getThumbnailUri( imageSource: user.avatarUrl == null
// TODO: Fix use of account avatar not room avatar ? null
room.client, : (await ref.watch(
width: 24, AvatarController.provider(user.avatarUrl!.toString()).future,
height: 24, )).toString(),
))?.toString(),
); );
} }

View file

@ -7,7 +7,9 @@ import "package:nexus/models/space.dart";
class SelectedSpaceController extends AsyncNotifier<Space> { class SelectedSpaceController extends AsyncNotifier<Space> {
@override @override
Future<Space> build() async { Future<Space> build() async {
final spaces = await ref.watch(SpacesController.provider.future); final spaces = await ref.watch(
SpacesController.provider.selectAsync((data) => data),
);
final selectedSpaceId = ref.watch( final selectedSpaceId = ref.watch(
KeyController.provider(KeyController.spaceKey), KeyController.provider(KeyController.spaceKey),
); );

View file

@ -41,7 +41,7 @@ class SpacesController extends AsyncNotifier<IList<Space>> {
title: "Home", title: "Home",
id: "home", id: "home",
children: topLevelRooms, children: topLevelRooms,
icon: Icon(Icons.home), icon: Icons.home,
), ),
Space( Space(
client: client, client: client,
@ -54,7 +54,7 @@ class SpacesController extends AsyncNotifier<IList<Space>> {
.map((room) => room.fullRoom), .map((room) => room.fullRoom),
), ),
), ),
icon: Icon(Icons.person), icon: Icons.person,
), ),
...(await Future.wait( ...(await Future.wait(
topLevelSpaces.map( topLevelSpaces.map(

View file

@ -10,9 +10,7 @@ class ThumbnailController extends AsyncNotifier<String?> {
@override @override
Future<String?> build({String? from}) async { Future<String?> build({String? from}) async {
final client = await ref.watch(ClientController.provider.future); final client = await ref.watch(ClientController.provider.future);
final uri = await Uri.tryParse(data.uri)?.getDownloadUri( final uri = await Uri.tryParse(data.uri)?.getDownloadUri(client);
client,
); // TODO: Should use thumb when c10y fixes animated thumbs
return uri.toString(); return uri.toString();
} }

View file

@ -25,7 +25,7 @@ extension EventToMessage on Event {
final editedAt = relationshipType == RelationshipTypes.edit final editedAt = relationshipType == RelationshipTypes.edit
? originServerTs ? originServerTs
: null; : null;
final body = newContent?["body"] as String? ?? this.body; final body = (newContent?["body"] ?? content["body"]).toString();
final eventId = editedAt == null final eventId = editedAt == null
? this.eventId ? this.eventId
: relationshipEventId ?? this.eventId; : relationshipEventId ?? this.eventId;

View file

@ -20,17 +20,15 @@ final class Logger extends ProviderObserver {
ProviderObserverContext context, ProviderObserverContext context,
Object? previousValue, Object? previousValue,
Object? newValue, Object? newValue,
) { ) => debugPrint("""
print('''{ Time: ${DateTime.now().toIso8601String()}
"provider": "${context.provider}", Provider: ${context.provider}
"changed": ${previousValue != newValue} Previous Value: ${previousValue is AsyncData ? previousValue.value : previousValue}
"type": ${previousValue.runtimeType} New Value: ${newValue is AsyncData ? newValue.value : newValue}
}'''); }""");
}
} }
void showError(Object error, [StackTrace? stackTrace]) { void showError(Object error, [StackTrace? stackTrace]) {
if (error.toString().contains("ParentDataWidget")) return;
if (error.toString().contains("DioException")) return; if (error.toString().contains("DioException")) return;
if (error.toString().contains("UTF-16")) return; if (error.toString().contains("UTF-16")) return;

View file

@ -15,6 +15,6 @@ abstract class Space with _$Space {
required Client client, required Client client,
Room? roomData, Room? roomData,
Uri? avatar, Uri? avatar,
Icon? icon, IconData? icon,
}) = _Space; }) = _Space;
} }

View file

@ -132,7 +132,11 @@ class LoginPage extends HookConsumerWidget {
(homeserver) => Card( (homeserver) => Card(
child: ListTile( child: ListTile(
title: Text(homeserver.name), title: Text(homeserver.name),
leading: Image.network(homeserver.iconUrl, height: 32), leading: Image.network(
homeserver.iconUrl,
errorBuilder: (_, _, _) => SizedBox.shrink(),
height: 32,
),
subtitle: Text(homeserver.description), subtitle: Text(homeserver.description),
onTap: isLoading.value onTap: isLoading.value
? null ? null

View file

@ -1,13 +1,14 @@
import "dart:io";
import "package:flutter/material.dart"; import "package:flutter/material.dart";
import "package:flutter/services.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_hooks/flutter_hooks.dart"; import "package:flutter_hooks/flutter_hooks.dart";
import "package:fluttertagger/fluttertagger.dart"; import "package:fluttertagger/fluttertagger.dart";
import "package:hooks_riverpod/hooks_riverpod.dart";
import "package:matrix/matrix.dart"; import "package:matrix/matrix.dart";
import "package:nexus/helpers/extensions/get_headers.dart"; import "package:nexus/controllers/room_chat_controller.dart";
import "package:nexus/widgets/form_text_input.dart";
class ChatBox extends HookWidget { class ChatBox extends HookConsumerWidget {
final Message? replyToMessage; final Message? replyToMessage;
final VoidCallback onDismiss; final VoidCallback onDismiss;
final Room room; final Room room;
@ -19,83 +20,142 @@ class ChatBox extends HookWidget {
}); });
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context, WidgetRef ref) {
final theme = Theme.of(context); final theme = Theme.of(context);
final controller = useRef(FlutterTaggerController()); final controller = useRef(FlutterTaggerController());
final trigger = useState<String?>(null);
Future<void> send() => ref
.watch(RoomChatController.provider(room).notifier)
.send(controller.value.text);
final node = useFocusNode(
onKeyEvent: (_, event) {
if (event is KeyDownEvent &&
event.logicalKey == LogicalKeyboardKey.enter &&
!(Platform.isAndroid || Platform.isIOS) ^
HardwareKeyboard.instance.isShiftPressed) {
send();
return KeyEventResult.handled;
}
return KeyEventResult.ignored;
},
);
final style = TextStyle( final style = TextStyle(
color: theme.colorScheme.primary, color: theme.colorScheme.primary,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
); );
return Column( return Positioned(
mainAxisAlignment: MainAxisAlignment.end, bottom: 0,
children: [ left: 0,
FlutterTagger( right: 0,
overlay: SizedBox(), child: Padding(
controller: controller.value, padding: EdgeInsetsGeometry.all(12),
onSearch: (query, triggerCharacter) { child: ClipRRect(
triggerCharacter == "#"; borderRadius: BorderRadius.all(Radius.circular(12)),
if (controller.value.tags.isEmpty) child: Container(
controller.value.addTag(id: "id", name: "name"); color: theme.colorScheme.surfaceContainerHighest,
}, padding: EdgeInsets.symmetric(horizontal: 8),
triggerCharacterAndStyles: {"@": style, "#": style}, child: // TODO: This doesn't work?
builder: (context, key) => TextFormField(controller: controller.value, key: key,autofocus: true,onFieldSubmitted: (_) { room.canSendDefaultMessages
? Row(
},) spacing: 8,
// Composer( children: [
// textEditingController: controller.value, PopupMenuButton(
// key: key, itemBuilder: (context) => [],
// sigmaY: 0, icon: Icon(Icons.add),
// sendIconColor: theme.colorScheme.primary, ),
// sendOnEnter: true, Expanded(
// topWidget: replyToMessage == null child: FlutterTagger(
// ? null overlay: SizedBox(),
// : ColoredBox( controller: controller.value,
// color: theme.colorScheme.surfaceContainer, onSearch: (query, triggerCharacter) {
// child: Padding( triggerCharacter == "#";
// padding: EdgeInsets.symmetric( if (controller.value.tags.isEmpty) {
// horizontal: 16, controller.value.addTag(
// vertical: 4, id: "id",
// ), name: "name",
// child: Row( ); // TODO: RM
// spacing: 8, }
// children: [ },
// Avatar( triggerCharacterAndStyles: {
// userId: replyToMessage!.authorId, "@": style,
// headers: room.client.headers, "#": style,
// size: 16, ":": style,
// ), },
// Text( builder: (context, key) => TextFormField(
// replyToMessage!.metadata?["displayName"] ?? maxLines: 12,
// replyToMessage!.authorId, minLines: 1,
// style: theme.textTheme.labelMedium?.copyWith( decoration: InputDecoration(
// fontWeight: FontWeight.bold, hintText: "Your message here...",
// ), border: InputBorder.none,
// ), ),
// Expanded( controller: controller.value,
// child: (replyToMessage is TextMessage) key: key,
// ? Text( autofocus: true,
// (replyToMessage as TextMessage).text, focusNode: node,
// overflow: TextOverflow.ellipsis, ),
// style: theme.textTheme.labelMedium, ),
// maxLines: 1, ),
// ) IconButton(onPressed: send, icon: Icon(Icons.send)),
// : SizedBox(), ],
// ), )
// IconButton( : Text("You don't have permission to send messages here..."),
// onPressed: onDismiss, // Composer(
// icon: Icon(Icons.close), // textEditingController: controller.value,
// iconSize: 20, // key: key,
// ), // sigmaY: 0,
// ], // sendIconColor: theme.colorScheme.primary,
// ), // sendOnEnter: true,
// ), // topWidget: replyToMessage == null
// ), // ? null
// autofocus: true, // : ColoredBox(
// ), // color: theme.colorScheme.surfaceContainer,
// child: Padding(
// padding: EdgeInsets.symmetric(
// horizontal: 16,
// vertical: 4,
// ),
// child: Row(
// spacing: 8,
// children: [
// Avatar(
// userId: replyToMessage!.authorId,
// headers: room.client.headers,
// size: 16,
// ),
// Text(
// replyToMessage!.metadata?["displayName"] ??
// replyToMessage!.authorId,
// style: theme.textTheme.labelMedium?.copyWith(
// fontWeight: FontWeight.bold,
// ),
// ),
// Expanded(
// child: (replyToMessage is TextMessage)
// ? Text(
// (replyToMessage as TextMessage).text,
// overflow: TextOverflow.ellipsis,
// style: theme.textTheme.labelMedium,
// maxLines: 1,
// )
// : SizedBox(),
// ),
// IconButton(
// onPressed: onDismiss,
// icon: Icon(Icons.close),
// iconSize: 20,
// ),
// ],
// ),
// ),
// ),
// autofocus: true,
// ),
),
), ),
], ),
); );
} }
} }

View file

@ -1,5 +1,6 @@
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:flutter_widget_from_html_core/flutter_widget_from_html_core.dart";
class SpoilerText extends HookWidget { class SpoilerText extends HookWidget {
final String text; final String text;
@ -10,18 +11,20 @@ class SpoilerText extends HookWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final revealed = useState(false); final revealed = useState(false);
return InkWell( return InlineCustomWidget(
onTap: () => revealed.value = !revealed.value, child: InkWell(
child: AnimatedContainer( onTap: () => revealed.value = !revealed.value,
duration: const Duration(milliseconds: 100), child: AnimatedContainer(
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 2), duration: const Duration(milliseconds: 100),
decoration: BoxDecoration( padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 2),
color: revealed.value ? Colors.transparent : Colors.blueGrey, decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4), color: revealed.value ? Colors.transparent : Colors.blueGrey,
), borderRadius: BorderRadius.circular(4),
child: Text( ),
text, child: Text(
style: TextStyle(color: revealed.value ? null : Colors.transparent), text,
style: TextStyle(color: revealed.value ? null : Colors.transparent),
),
), ),
), ),
); );

View file

@ -135,177 +135,197 @@ class RoomChat extends HookConsumerWidget {
body: Row( body: Row(
children: [ children: [
Expanded( Expanded(
child: ref child: Column(
.watch(controllerProvider) children: [
.betterWhen( Expanded(
data: (controller) => Chat( child: ref
currentUserId: room.roomData.client.userID!, .watch(controllerProvider)
theme: ChatTheme.fromThemeData(theme).copyWith( .betterWhen(
colors: ChatColors.fromThemeData(theme).copyWith( data: (controller) => Chat(
primary: theme.colorScheme.primaryContainer, currentUserId: room.roomData.client.userID!,
onPrimary: theme.colorScheme.onPrimaryContainer, theme: ChatTheme.fromThemeData(theme)
), .copyWith(
), colors: ChatColors.fromThemeData(theme)
onMessageSecondaryTap: .copyWith(
( primary: theme
context, .colorScheme
message, { .primaryContainer,
required details, onPrimary: theme
required index, .colorScheme
}) => context.showContextMenu( .onPrimaryContainer,
globalPosition: details.globalPosition,
children: getMessageOptions(message),
),
onMessageLongPress:
(
context,
message, {
required details,
required index,
}) => context.showContextMenu(
globalPosition: details.globalPosition,
children: getMessageOptions(message),
),
builders: Builders(
loadMoreBuilder: (_) => Loading(),
chatAnimatedListBuilder: (_, itemBuilder) =>
ChatAnimatedList(
itemBuilder: itemBuilder,
onEndReached: notifier.loadOlder,
onStartReached: notifier.markRead,
),
composerBuilder: (_) => ChatBox(
replyToMessage: replyToMessage.value,
onDismiss: () => replyToMessage.value = null,
room: room.roomData,
),
textMessageBuilder:
(
context,
message,
index, {
required bool isSentByMe,
MessageGroupStatus? groupStatus,
}) => FlyerChatTextMessage(
customWidget: Html(
message.metadata?["formatted"]
.replaceAllMapped(
RegExp(
regexLink,
caseSensitive: false,
),
(m) =>
"<a href=\"${m.group(0)!}\">${m.group(0)!}</a>",
) +
((message.editedAt != null)
? "<sub edited>(edited)</sub>"
: ""),
client: room.roomData.client,
),
topWidget: TopWidget(
message,
headers: room.roomData.client.headers,
groupStatus: groupStatus,
),
message: message,
showTime: true,
index: index,
),
linkPreviewBuilder: (_, message, isSentByMe) =>
LinkPreview(
text: message.text,
backgroundColor: isSentByMe
? theme.colorScheme.inversePrimary
: theme.colorScheme.surfaceContainerLow,
insidePadding: EdgeInsets.symmetric(
vertical: 8,
horizontal: 16,
),
linkPreviewData: message.linkPreviewData,
onLinkPreviewDataFetched:
(linkPreviewData) =>
notifier.updateMessage(
message,
message.copyWith(
linkPreviewData:
linkPreviewData,
),
), ),
),
imageMessageBuilder:
(
_,
message,
index, {
required bool isSentByMe,
MessageGroupStatus? groupStatus,
}) => FlyerChatImageMessage(
topWidget: TopWidget(
message,
headers: room.roomData.client.headers,
groupStatus: groupStatus,
alwaysShow: true,
),
message: message,
index: index,
headers: room.roomData.client.headers,
),
fileMessageBuilder:
(
_,
message,
index, {
required bool isSentByMe,
MessageGroupStatus? groupStatus,
}) => InkWell(
onTap: () => showAboutDialog(
context: context,
), // TODO: Download
child: FlyerChatFileMessage(
topWidget: TopWidget(
message,
headers: room.roomData.client.headers,
groupStatus: groupStatus,
), ),
message: message, onMessageSecondaryTap:
index: index, (
context,
message, {
required details,
required index,
}) => context.showContextMenu(
globalPosition: details.globalPosition,
children: getMessageOptions(message),
),
onMessageLongPress:
(
context,
message, {
required details,
required index,
}) => context.showContextMenu(
globalPosition: details.globalPosition,
children: getMessageOptions(message),
),
builders: Builders(
loadMoreBuilder: (_) => Loading(),
chatAnimatedListBuilder: (_, itemBuilder) =>
ChatAnimatedList(
itemBuilder: itemBuilder,
onEndReached: notifier.loadOlder,
onStartReached: notifier.markRead,
bottomPadding: 72,
),
composerBuilder: (_) => ChatBox(
replyToMessage: replyToMessage.value,
onDismiss: () =>
replyToMessage.value = null,
room: room.roomData,
), ),
textMessageBuilder:
(
context,
message,
index, {
required bool isSentByMe,
MessageGroupStatus? groupStatus,
}) => FlyerChatTextMessage(
customWidget: Html(
message.metadata?["formatted"]
.replaceAllMapped(
RegExp(
regexLink,
caseSensitive: false,
),
(m) =>
"<a href=\"${m.group(0)!}\">${m.group(0)!}</a>",
) +
((message.editedAt != null)
? "<sub edited>(edited)</sub>"
: ""),
client: room.roomData.client,
),
topWidget: TopWidget(
message,
headers:
room.roomData.client.headers,
groupStatus: groupStatus,
),
message: message,
showTime: true,
index: index,
),
linkPreviewBuilder:
(_, message, isSentByMe) => LinkPreview(
text: message.text,
backgroundColor: isSentByMe
? theme.colorScheme.inversePrimary
: theme
.colorScheme
.surfaceContainerLow,
insidePadding: EdgeInsets.symmetric(
vertical: 8,
horizontal: 16,
),
linkPreviewData:
message.linkPreviewData,
onLinkPreviewDataFetched:
(linkPreviewData) =>
notifier.updateMessage(
message,
message.copyWith(
linkPreviewData:
linkPreviewData,
),
),
),
imageMessageBuilder:
(
_,
message,
index, {
required bool isSentByMe,
MessageGroupStatus? groupStatus,
}) => FlyerChatImageMessage(
topWidget: TopWidget(
message,
headers:
room.roomData.client.headers,
groupStatus: groupStatus,
alwaysShow: true,
),
message: message,
index: index,
headers: room.roomData.client.headers,
),
fileMessageBuilder:
(
_,
message,
index, {
required bool isSentByMe,
MessageGroupStatus? groupStatus,
}) => InkWell(
onTap: () => showAboutDialog(
context: context,
), // TODO: Download
child: FlyerChatFileMessage(
topWidget: TopWidget(
message,
headers:
room.roomData.client.headers,
groupStatus: groupStatus,
),
message: message,
index: index,
),
),
systemMessageBuilder:
(
_,
message,
index, {
required bool isSentByMe,
MessageGroupStatus? groupStatus,
}) => FlyerChatSystemMessage(
message: message,
index: index,
),
unsupportedMessageBuilder:
(
_,
message,
index, {
required bool isSentByMe,
MessageGroupStatus? groupStatus,
}) => Text(
"${message.authorId} sent ${message.metadata?["eventType"]}",
style: theme.textTheme.labelSmall
?.copyWith(color: Colors.grey),
),
), ),
systemMessageBuilder: resolveUser: notifier.resolveUser,
( chatController: controller,
_, ),
message, ),
index, {
required bool isSentByMe,
MessageGroupStatus? groupStatus,
}) => FlyerChatSystemMessage(
message: message,
index: index,
),
unsupportedMessageBuilder:
(
_,
message,
index, {
required bool isSentByMe,
MessageGroupStatus? groupStatus,
}) => Text(
"${message.authorId} sent ${message.metadata?["eventType"]}",
style: theme.textTheme.labelSmall?.copyWith(
color: Colors.grey,
),
),
),
resolveUser: notifier.resolveUser,
chatController: controller,
),
), ),
],
),
), ),
if (memberListOpened.value == true && showMembersByDefault) if (memberListOpened.value == true && showMembersByDefault)
MemberList(room.roomData), MemberList(room.roomData),
], ],
), ),
endDrawer: showMembersByDefault endDrawer: showMembersByDefault
? null ? null
: MemberList(room.roomData), : MemberList(room.roomData),

View file

@ -60,7 +60,9 @@ class Sidebar extends HookConsumerWidget {
(space) => NavigationRailDestination( (space) => NavigationRailDestination(
icon: AvatarOrHash( icon: AvatarOrHash(
space.avatar, space.avatar,
fallback: space.icon, fallback: space.icon == null
? null
: Icon(space.icon),
space.title, space.title,
headers: space.client.headers, headers: space.client.headers,
hasBadge: hasBadge:
@ -126,7 +128,9 @@ class Sidebar extends HookConsumerWidget {
appBar: AppBar( appBar: AppBar(
leading: AvatarOrHash( leading: AvatarOrHash(
space.avatar, space.avatar,
fallback: space.icon, fallback: space.icon == null
? null
: Icon(space.icon),
space.title, space.title,
headers: space.client.headers, headers: space.client.headers,
), ),

View file

@ -3,8 +3,10 @@ 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_riverpod/flutter_riverpod.dart"; import "package:flutter_riverpod/flutter_riverpod.dart";
import "package:nexus/controllers/avatar_controller.dart";
import "package:nexus/controllers/message_controller.dart"; import "package:nexus/controllers/message_controller.dart";
import "package:nexus/helpers/extensions/better_when.dart"; import "package:nexus/helpers/extensions/better_when.dart";
import "package:nexus/widgets/avatar_or_hash.dart";
import "package:nexus/widgets/chat_page/quoted.dart"; import "package:nexus/widgets/chat_page/quoted.dart";
class TopWidget extends ConsumerWidget { class TopWidget extends ConsumerWidget {
@ -62,11 +64,18 @@ class TopWidget extends ConsumerWidget {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
spacing: 8, spacing: 8,
children: [ children: [
Avatar( ref
userId: replyMessage.authorId, .watch(
headers: headers, AvatarController.provider(replyMessage.authorId),
size: 16, )
), .betterWhen(
data: (avatar) => AvatarOrHash(
avatar,
replyMessage.metadata?["displayName"] ??
replyMessage.authorId,
headers: headers,
),
),
Flexible( Flexible(
child: Text( child: Text(
replyMessage.metadata?["displayName"] ?? replyMessage.metadata?["displayName"] ??