shows room but not really

This commit is contained in:
Henry Hiles 2026-01-27 19:09:43 +00:00
commit a28bced44d
No known key found for this signature in database
23 changed files with 885 additions and 805 deletions

View file

@ -6,12 +6,14 @@ class AvatarOrHash extends StatelessWidget {
final String title;
final Widget? fallback;
final bool hasBadge;
final int badgeNumber;
final double height;
final Map<String, String> headers;
const AvatarOrHash(
this.avatar,
this.title, {
this.fallback,
this.badgeNumber = 0,
this.hasBadge = false,
this.height = 24,
required this.headers,
@ -30,6 +32,7 @@ class AvatarOrHash extends StatelessWidget {
child: Center(
child: Badge(
isLabelVisible: hasBadge,
label: badgeNumber != 0 ? Text(badgeNumber.toString()) : null,
smallSize: 12,
backgroundColor: Theme.of(context).colorScheme.primary,
child: ClipRRect(

View file

@ -5,10 +5,9 @@ import "package:flutter_chat_core/flutter_chat_core.dart";
import "package:flutter_hooks/flutter_hooks.dart";
import "package:fluttertagger/fluttertagger.dart";
import "package:hooks_riverpod/hooks_riverpod.dart";
import "package:matrix/matrix.dart";
import "package:nexus/controllers/room_chat_controller.dart";
import "package:nexus/models/relation_type.dart";
import "package:nexus/widgets/chat_page/mention_overlay.dart";
import "package:nexus/models/room.dart";
import "package:nexus/widgets/chat_page/relation_preview.dart";
class ChatBox extends HookConsumerWidget {
@ -94,7 +93,6 @@ class ChatBox extends HookConsumerWidget {
relatedMessage: relatedMessage,
relationType: relationType,
onDismiss: onDismiss,
room: room,
),
Container(
color: theme.colorScheme.surfaceContainerHighest,
@ -105,20 +103,21 @@ class ChatBox extends HookConsumerWidget {
PopupMenuButton(
itemBuilder: (context) => [],
icon: Icon(Icons.add),
enabled: room.canSendDefaultMessages,
// enabled: room.canSendDefaultMessages, TODO: Permissions check
),
Expanded(
child: FlutterTagger(
triggerStrategy: TriggerStrategy.eager,
overlay: MentionOverlay(
room,
query: query.value,
triggerCharacter: triggerCharacter.value,
addTag: ({required id, required name}) {
controller.value.addTag(id: id, name: name);
node.requestFocus();
},
),
overlay: SizedBox.shrink(),
// MentionOverlay( TODO: Fix
// room,
// query: query.value,
// triggerCharacter: triggerCharacter.value,
// addTag: ({required id, required name}) {
// controller.value.addTag(id: id, name: name);
// node.requestFocus();
// },
// ),
controller: controller.value,
onSearch: (newQuery, newTriggerCharacter) {
triggerCharacter.value = newTriggerCharacter;
@ -126,13 +125,13 @@ class ChatBox extends HookConsumerWidget {
},
triggerCharacterAndStyles: {"@": style, "#": style},
builder: (context, key) => TextFormField(
enabled: room.canSendDefaultMessages,
// enabled: room.canSendDefaultMessages,
maxLines: 12,
minLines: 1,
decoration: InputDecoration(
hintText: room.canSendDefaultMessages
? "Your message here..."
: "You don't have permission to send messages in this room...",
// hintText: room.canSendDefaultMessages
// ? "Your message here..."
// : "You don't have permission to send messages in this room...",
border: InputBorder.none,
),
controller: controller.value,
@ -143,7 +142,8 @@ class ChatBox extends HookConsumerWidget {
),
),
IconButton(
onPressed: room.canSendDefaultMessages ? send : null,
onPressed: send,
// onPressed: room.canSendDefaultMessages ? send : null,
icon: Icon(Icons.send),
),
],

View file

@ -2,21 +2,16 @@ import "package:fast_immutable_collections/fast_immutable_collections.dart";
import "package:flutter/material.dart";
import "package:flutter_riverpod/flutter_riverpod.dart";
import "package:flutter_widget_from_html_core/flutter_widget_from_html_core.dart";
import "package:matrix/matrix.dart";
import "package:nexus/controllers/thumbnail_controller.dart";
import "package:nexus/helpers/extensions/get_headers.dart";
import "package:nexus/helpers/extensions/link_to_mention.dart";
import "package:nexus/helpers/launch_helper.dart";
import "package:nexus/models/image_data.dart";
import "package:nexus/widgets/chat_page/html/mention_chip.dart";
import "package:nexus/widgets/chat_page/html/spoiler_text.dart";
import "package:nexus/widgets/chat_page/html/code_block.dart";
import "package:nexus/widgets/chat_page/html/quoted.dart";
import "package:nexus/widgets/error_dialog.dart";
class Html extends ConsumerWidget {
final String html;
final Client client;
const Html(this.html, {required this.client, super.key});
const Html(this.html, {super.key});
@override
Widget build(BuildContext context, WidgetRef ref) => HtmlWidget(
@ -38,61 +33,60 @@ class Html extends ConsumerWidget {
)
: null,
"blockquote" => Quoted(Html(element.innerHtml, client: client)),
"blockquote" => Quoted(Html(element.innerHtml)),
"a" =>
element.attributes["href"]?.parseIdentifierIntoParts() == null
element.attributes["href"]?.mention == null
? null
: InlineCustomWidget(child: MentionChip(element.text)),
"img" =>
element.attributes["src"] == null
? null
: Consumer(
builder: (_, ref, _) => ref
.watch(
ThumbnailController.provider(
ImageData(
uri: element.attributes["src"]!,
height: height,
width: width,
),
),
)
.when(
data: (uri) {
if (uri == null) return SizedBox.shrink();
return InlineCustomWidget(
child: Image.network(
uri,
headers: client.headers,
errorBuilder: (_, error, _) => Text(
"Image Failed to Load",
style: TextStyle(
color: Theme.of(context).colorScheme.error,
),
),
height: height.toDouble(),
width: width?.toDouble(),
loadingBuilder: (_, child, loadingProgress) =>
loadingProgress == null
? child
: CircularProgressIndicator(),
),
);
},
error: ErrorDialog.new,
loading: () => InlineCustomWidget(
child: SizedBox(
width: width?.toDouble(),
height: height.toDouble(),
child: CircularProgressIndicator(),
),
),
),
),
// "img" => TODO: Img support
// element.attributes["src"] == null
// ? null
// : Consumer(
// builder: (_, ref, _) => ref
// .watch(
// ThumbnailController.provider(
// ImageData(
// uri: element.attributes["src"]!,
// height: height,
// width: width,
// ),
// ),
// )
// .when(
// data: (uri) {
// if (uri == null) return SizedBox.shrink();
// return InlineCustomWidget(
// child: Image.network(
// uri,
// headers: client.headers,
// errorBuilder: (_, error, _) => Text(
// "Image Failed to Load",
// style: TextStyle(
// color: Theme.of(context).colorScheme.error,
// ),
// ),
// height: height.toDouble(),
// width: width?.toDouble(),
// loadingBuilder: (_, child, loadingProgress) =>
// loadingProgress == null
// ? child
// : CircularProgressIndicator(),
// ),
// );
// },
// error: ErrorDialog.new,
// loading: () => InlineCustomWidget(
// child: SizedBox(
// width: width?.toDouble(),
// height: height.toDouble(),
// child: CircularProgressIndicator(),
// ),
// ),
// ),
// ),
("del" ||
"h1" ||
"h2" ||

View file

@ -1,5 +1,5 @@
import "package:flutter/material.dart";
import "package:matrix/matrix.dart";
import "package:nexus/helpers/extensions/link_to_mention.dart";
class MentionChip extends StatelessWidget {
final String label;
@ -8,7 +8,7 @@ class MentionChip extends StatelessWidget {
@override
Widget build(BuildContext context) => ActionChip(
label: Text(
label.parseIdentifierIntoParts()?.primaryIdentifier ?? label,
label.mention ?? label,
style: TextStyle(
fontWeight: FontWeight.bold,
color: Theme.of(context).colorScheme.onPrimary,

View file

@ -1,22 +1,16 @@
import "package:flutter/material.dart";
import "package:flutter_chat_core/flutter_chat_core.dart";
import "package:hooks_riverpod/hooks_riverpod.dart";
import "package:matrix/matrix.dart";
import "package:nexus/controllers/avatar_controller.dart";
import "package:nexus/helpers/extensions/get_headers.dart";
import "package:nexus/models/relation_type.dart";
import "package:nexus/widgets/avatar_or_hash.dart";
class RelationPreview extends ConsumerWidget {
final Message? relatedMessage;
final RelationType relationType;
final VoidCallback onDismiss;
final Room room;
const RelationPreview({
required this.relatedMessage,
required this.relationType,
required this.onDismiss,
required this.room,
super.key,
});
@ -37,18 +31,18 @@ class RelationPreview extends ConsumerWidget {
"Editing message:",
style: TextStyle(fontWeight: FontWeight.bold),
),
AvatarOrHash(
ref
.watch(
AvatarController.provider(
relatedMessage!.metadata!["avatarUrl"],
),
)
.whenOrNull(data: (data) => data),
relatedMessage!.metadata!["displayName"].toString(),
headers: room.client.headers,
height: 16,
),
// AvatarOrHash(
// ref
// .watch(
// AvatarController.provider(
// relatedMessage!.metadata!["avatarUrl"],
// ),
// )
// .whenOrNull(data: (data) => data),
// relatedMessage!.metadata!["displayName"].toString(),
// headers: room.client.headers,
// height: 16,
// ),
Text(
relatedMessage!.metadata?["displayName"] ??
relatedMessage!.authorId,

View file

@ -1,13 +1,13 @@
import "package:fast_immutable_collections/fast_immutable_collections.dart";
import "package:flutter/material.dart";
import "package:nexus/helpers/extensions/get_headers.dart";
import "package:nexus/models/full_room.dart";
import "package:nexus/models/room.dart";
import "package:nexus/widgets/appbar.dart";
import "package:nexus/widgets/avatar_or_hash.dart";
import "package:nexus/widgets/chat_page/room_menu.dart";
class RoomAppbar extends StatelessWidget implements PreferredSizeWidget {
final bool isDesktop;
final FullRoom room;
final Room room;
final void Function(BuildContext context) onOpenMemberList;
final void Function(BuildContext context) onOpenDrawer;
const RoomAppbar(
@ -24,22 +24,27 @@ class RoomAppbar extends StatelessWidget implements PreferredSizeWidget {
@override
Widget build(BuildContext context) => Appbar(
leading: isDesktop
? AvatarOrHash(
room.avatar,
room.title,
height: 24,
fallback: Icon(Icons.numbers),
headers: room.roomData.client.headers,
)
? null
// AvatarOrHash( TODO: Images
// room.avatar,
// room.title,
// height: 24,
// fallback: Icon(Icons.numbers),
// headers: room.roomData.client.headers,
// )
: DrawerButton(onPressed: () => onOpenDrawer(context)),
scrolledUnderElevation: 0,
title: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(room.title, overflow: TextOverflow.ellipsis, maxLines: 1),
if (room.roomData.topic.isNotEmpty)
Text(
room.metadata?.name ?? "Unnamed Room",
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
if (room.metadata?.topic?.isNotEmpty == true)
Text(
room.roomData.topic,
room.metadata!.topic!,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.labelMedium?.copyWith(
@ -54,7 +59,7 @@ class RoomAppbar extends StatelessWidget implements PreferredSizeWidget {
onPressed: () => onOpenMemberList(context),
icon: Icon(Icons.people),
),
RoomMenu(room.roomData),
],
RoomMenu(room),
].toIList(),
);
}

File diff suppressed because it is too large Load diff

View file

@ -2,27 +2,20 @@ import "package:fast_immutable_collections/fast_immutable_collections.dart";
import "package:flutter/material.dart";
import "package:flutter/services.dart";
import "package:flutter_hooks/flutter_hooks.dart";
import "package:flutter_riverpod/flutter_riverpod.dart";
import "package:nexus/controllers/client_controller.dart";
import "package:nexus/models/room.dart";
import "package:nexus/widgets/form_text_input.dart";
class RoomMenu extends StatelessWidget {
class RoomMenu extends ConsumerWidget {
final Room room;
final IList<Room> children;
const RoomMenu(this.room, {this.children = const IList.empty(), super.key});
@override
Widget build(BuildContext context) {
Widget build(BuildContext context, WidgetRef ref) {
final danger = Theme.of(context).colorScheme.error;
void markRead(String roomId) async {
// TODO: Set parent read
for (final child in children) {
// await child.setReadMarker( TODO: Set children read
// child.roomData.lastEvent?.eventId,
// mRead: child.roomData.lastEvent?.eventId,
// );
}
}
final client = ref.watch(ClientController.provider.notifier);
return PopupMenuButton(
itemBuilder: (_) => [
@ -33,45 +26,51 @@ class RoomMenu extends StatelessWidget {
// },
// child: ListTile(leading: Icon(Icons.link), title: Text("Copy Link")),
// ),
// PopupMenuItem(
// onTap: () => markRead(room.id),
// child: ListTile(
// leading: Icon(Icons.check),
// title: Text("Mark as Read"),
// ),
// ),
// PopupMenuItem(
// onTap: () => showDialog(
// context: context,
// builder: (context) => AlertDialog(
// title: Text("Leave Room"),
// content: Text(
// "Are you sure you want to leave \"${room.getLocalizedDisplayname()}\"?",
// ),
// actions: [
// TextButton(
// onPressed: Navigator.of(context).pop,
// child: Text("Cancel"),
// ),
// TextButton(
// onPressed: () async {
// Navigator.of(context).pop();
// final snackbar = ScaffoldMessenger.of(
// context,
// ).showSnackBar(SnackBar(content: Text("Leaving room...")));
// await room.leave();
// snackbar.close();
// },
// child: Text("Leave"),
// ),
// ],
// ),
// ),
// child: ListTile(
// leading: Icon(Icons.logout, color: danger),
// title: Text("Leave", style: TextStyle(color: danger)),
// ),
// ),
PopupMenuItem(
onTap: () async {
await client.markRead(room);
await Future.wait(children.map((child) => client.markRead(child)));
},
child: ListTile(
leading: Icon(Icons.check),
title: Text("Mark as Read"),
),
),
PopupMenuItem(
onTap: () => showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text("Leave Room"),
content: Text(
"Are you sure you want to leave \"${room.metadata?.name ?? "Unnamed Room"}\"?",
),
actions: [
TextButton(
onPressed: Navigator.of(context).pop,
child: Text("Cancel"),
),
TextButton(
onPressed: () async {
Navigator.of(context).pop();
final snackbar = ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text("Leaving room..."),
duration: Duration(days: 1),
),
);
await client.leaveRoom(room);
snackbar.close();
},
child: Text("Leave"),
),
],
),
),
child: ListTile(
leading: Icon(Icons.logout, color: danger),
title: Text("Leave", style: TextStyle(color: danger)),
),
),
// PopupMenuItem(
// onTap: () => showDialog(
// context: context,

View file

@ -1,3 +1,4 @@
import "package:collection/collection.dart";
import "package:flutter/material.dart";
import "package:flutter_hooks/flutter_hooks.dart";
import "package:hooks_riverpod/hooks_riverpod.dart";
@ -39,7 +40,7 @@ class Sidebar extends HookConsumerWidget {
final indexOfSelectedRoom = selectedSpace.children.indexWhere(
(room) => room.metadata?.id == selectedRoomId,
);
final selectedRoomIndex = indexOfSelected == -1
final selectedRoomIndex = indexOfSelectedRoom == -1
? selectedSpace.children.isEmpty
? null
: 0
@ -65,11 +66,17 @@ class Sidebar extends HookConsumerWidget {
fallback: space.icon == null ? null : Icon(space.icon),
space.title,
headers: {}, // TODO
hasBadge: false,
// space.children.firstWhereOrNull( TODO
// (room) => room.roomData.hasNewMessages,
// ) !=
// null,
hasBadge:
space.children.firstWhereOrNull(
(room) => room.metadata?.unreadMessages != 0,
) !=
null,
badgeNumber: space.children.fold(
0,
(previousValue, room) =>
previousValue +
(room.metadata?.unreadNotifications ?? 0),
),
),
label: Text(space.title),
padding: EdgeInsets.only(top: 4),
@ -184,13 +191,16 @@ class Sidebar extends HookConsumerWidget {
// space.client.headers, TODO
),
title: Text(
selectedSpace.room?.metadata?.avatar.toString() ??
selectedSpace.title,
selectedSpace.title,
overflow: TextOverflow.ellipsis,
),
backgroundColor: Colors.transparent,
actions: [
if (selectedSpace.room != null) RoomMenu(selectedSpace.room!),
if (selectedSpace.room != null)
RoomMenu(
selectedSpace.room!,
children: selectedSpace.children,
),
],
),
body: NavigationRail(
@ -203,8 +213,9 @@ class Sidebar extends HookConsumerWidget {
(room) => NavigationRailDestination(
label: Text(room.metadata?.name ?? "Unnamed Room"),
icon: AvatarOrHash(
// hasBadge: room.roomData.hasNewMessages, TODO
null,
hasBadge: room.metadata?.unreadMessages != 0,
badgeNumber: room.metadata?.unreadNotifications ?? 0,
// room.avatar, TODO
room.metadata?.name ?? "Unnamed Room",
fallback: selectedSpaceId == "dms"

View file

@ -8,11 +8,9 @@ import "package:nexus/widgets/chat_page/html/quoted.dart";
class TopWidget extends ConsumerWidget {
final Message message;
final bool alwaysShow;
final Map<String, String> headers;
final MessageGroupStatus? groupStatus;
const TopWidget(
this.message, {
required this.headers,
required this.groupStatus,
this.alwaysShow = false,
super.key,
@ -62,11 +60,11 @@ class TopWidget extends ConsumerWidget {
mainAxisSize: MainAxisSize.min,
spacing: 8,
children: [
Avatar(
userId: replyMessage.authorId,
headers: headers,
size: 16,
),
// Avatar( TODO: images
// userId: replyMessage.authorId,
// headers: headers,
// size: 16,
// ),
Flexible(
child: Text(
replyMessage.metadata?["displayName"] ??
@ -104,7 +102,7 @@ class TopWidget extends ConsumerWidget {
mainAxisSize: MainAxisSize.min,
spacing: 8,
children: [
Avatar(userId: message.authorId, headers: headers),
// Avatar(userId: message.authorId, headers: headers), TODO: images
Flexible(
child: Text(
message.metadata?["displayName"] ?? message.authorId,