Remove flutter chat #26

Manually merged
Henry-Hiles merged 108 commits from remove-flutter-chat into main 2026-05-22 15:26:28 -04:00
3 changed files with 194 additions and 162 deletions
Showing only changes of commit c9b5b3dda8 - Show all commits

various fixes

Henry Hiles 2026-05-18 14:30:44 -04:00
Signed by: Henry-Hiles
SSH key fingerprint: SHA256:VKQUdS31Q90KvX7EkKMHMBpUspcmItAh86a+v7PGiIs

View file

@ -21,8 +21,8 @@ class UserController extends AsyncNotifier<MembershipContent> {
),
);
if (member?.content is MembershipContent) {
return member!.content as MembershipContent;
if (member?.content case final MembershipContent content) {
return content;
}
final profile = await ref.watch(ProfileController.provider(userId).future);

View file

@ -2,6 +2,7 @@ import "package:fast_immutable_collections/fast_immutable_collections.dart";
import "package:flutter/material.dart";
import "package:flutter_riverpod/flutter_riverpod.dart";
import "package:nexus/controllers/client_state_controller.dart";
import "package:nexus/helpers/extensions/show_context_menu.dart";
import "package:nexus/helpers/launch_helper.dart";
import "package:nexus/models/content/avatar.dart";
import "package:nexus/models/content/content.dart";
@ -35,6 +36,7 @@ class EventText extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
final theme = Theme.of(context);
final colorScheme = theme.colorScheme;
final errorStyle = TextStyle(color: colorScheme.error);
final timestamp = Tooltip(
message: event.timestamp.toString(),
@ -43,140 +45,165 @@ class EventText extends ConsumerWidget {
style: theme.textTheme.labelSmall?.copyWith(color: Colors.grey),
),
);
final contextMenuCallback = getEventOptions == null
? null
: (details) => context.showContextMenu(
globalPosition: details.globalPosition,
children: getEventOptions!(event).toList(),
);
return switch (event.content) {
MessageContent() => Row(
spacing: 8,
children: [
isGrouped ? SizedBox(width: 40) : MessageAvatar(event, height: 40),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (!isGrouped)
Row(
spacing: 4,
children: [
MessageDisplayname(
event,
style: TextStyle(fontWeight: FontWeight.bold),
),
Flexible(child: timestamp),
],
),
ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(8)),
child: Container(
padding: EdgeInsets.symmetric(vertical: 8, horizontal: 12),
decoration: BoxDecoration(
color:
ref.watch(
ClientStateController.provider.select(
(value) => value?.userId,
),
) ==
event.sender
? (event.eventId.startsWith("~")
? colorScheme.onPrimary
: colorScheme.primaryContainer)
: colorScheme.surfaceContainer,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
return GestureDetector(
onSecondaryTapUp: contextMenuCallback,
onLongPressStart: contextMenuCallback,
child: switch (event.content) {
MessageContent() => Row(
spacing: 8,
children: [
isGrouped ? SizedBox(width: 40) : MessageAvatar(event, height: 40),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (!isGrouped)
Row(
spacing: 4,
children: [
// Quoted( // TODO: Show replies
// EventText(replyEvent textOnly: true, maxLines: 1,)
// ),
switch (event.content) {
Content(:final parseError?) => SelectableText(
"An error occurred while parsing this message:\n$parseError",
style: TextStyle(color: colorScheme.error),
),
TextMessageContent(
:final body,
:final formattedBody,
:final format,
) =>
Column(
children: [
format == "org.matrix.custom.html"
? Html(
textStyle:
event.localContent?.bigEmoji == true
? TextStyle(fontSize: 32)
: null,
formattedBody!.replaceAllMapped(
RegExp(
"(<a\\b[^>]*>.*?<\\/a>)|(\\bhttps?:\\/\\/[^\\s<]+)",
caseSensitive: false,
dotAll: true,
),
(m) {
// If it's already an <a> tag, leave it unchanged
if (m.group(1) != null) {
return m.group(1)!;
}
// Otherwise, wrap the bare URL
final url = m.group(2)!;
return "<a href=\"$url\">$url</a>";
},
),
)
: Linkify(
text: body,
options: LinkifyOptions(
humanize: false,
),
onOpen: (link) => ref
.watch(LaunchHelper.provider)
.launchUrl(Uri.parse(link.url)),
linkStyle: TextStyle(
color: Theme.of(
context,
).colorScheme.primary,
),
),
if (event.lastEditRowId != null)
Text(
"(edited)",
style: theme.textTheme.labelSmall,
),
if (RegExp(
r'''https?://[^\s"'<>]+''',
).allMatches(body).firstOrNull?.group(0)
case final link?)
LinkPreview(link),
],
),
_ => SizedBox.shrink(),
},
MessageDisplayname(
event,
style: TextStyle(fontWeight: FontWeight.bold),
),
Flexible(child: timestamp),
],
),
ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(8)),
child: Container(
padding: EdgeInsets.symmetric(
vertical: 8,
horizontal: 12,
),
decoration: BoxDecoration(
color:
ref.watch(
ClientStateController.provider.select(
(value) => value?.userId,
),
) ==
event.sender
? (event.eventId.startsWith("~")
? colorScheme.onPrimary
: colorScheme.primaryContainer)
: colorScheme.surfaceContainer,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Quoted( // TODO: Show replies
// EventText(replyEvent textOnly: true, maxLines: 1,)
// ),
switch (event.content) {
Content(:final parseError?) => SelectableText(
"An error occurred while parsing this message:\n$parseError",
style: errorStyle,
),
TextMessageContent(
:final body,
:final formattedBody,
:final format,
) =>
Column(
children: [
format == "org.matrix.custom.html" &&
!textOnly
? Html(
textStyle:
event.localContent?.bigEmoji ==
true
? TextStyle(fontSize: 32)
: null,
formattedBody!.replaceAllMapped(
RegExp(
"(<a\\b[^>]*>.*?<\\/a>)|(\\bhttps?:\\/\\/[^\\s<]+)",
caseSensitive: false,
dotAll: true,
),
(m) {
// If it's already an <a> tag, leave it unchanged
if (m.group(1) != null) {
return m.group(1)!;
}
// Otherwise, wrap the bare URL
final url = m.group(2)!;
return "<a href=\"$url\">$url</a>";
},
),
)
: Linkify(
text: body,
maxLines: maxLines,
options: LinkifyOptions(
humanize: false,
),
onOpen: (link) => ref
.watch(LaunchHelper.provider)
.launchUrl(Uri.parse(link.url)),
linkStyle: TextStyle(
color: Theme.of(
context,
).colorScheme.primary,
),
),
if (event.lastEditRowId != null)
Text(
"(edited)",
style: theme.textTheme.labelSmall,
),
if (RegExp(
r'''https?://[^\s"'<>]+''',
).allMatches(body).firstOrNull?.group(0)
case final link?)
LinkPreview(link),
],
),
_ =>
textOnly
? Text(
"Unknown message type",
style: errorStyle,
)
: SizedBox.shrink(),
},
],
),
),
),
),
],
],
),
),
),
],
),
AvatarContent() => Row(
spacing: 4,
children: [
SizedBox(width: 4),
Icon(Icons.numbers),
MessageDisplayname(
event,
style: TextStyle(
color: theme.colorScheme.primary,
fontWeight: FontWeight.bold,
],
),
AvatarContent() => Row(
spacing: 4,
children: [
SizedBox(width: 4),
Icon(Icons.numbers),
MessageDisplayname(
event,
style: TextStyle(
color: theme.colorScheme.primary,
fontWeight: FontWeight.bold,
),
),
),
Text("changed the room avatar"),
],
),
_ => Text("AAAAA"),
};
Text("changed the room avatar"),
],
),
_ =>
textOnly
? Text("Unknown event type", style: errorStyle)
: SizedBox.shrink(),
},
);
}
}

View file

@ -99,42 +99,47 @@ class RoomChat extends HookConsumerWidget {
),
))
PopupMenuItem(
child: Row(
children: [
...{
...ref.watch(
AccountDataController.provider.select(
(value) => IList(
value["m.recent_emoji"]?.content["recent_emoji"] ??
[],
).map((entry) => entry["emoji"]),
enabled: false,
child: IconTheme(
data: theme.iconTheme,
child: Row(
children: [
...{
...ref.watch(
AccountDataController.provider.select(
(value) => IList(
value["m.recent_emoji"]
?.content["recent_emoji"] ??
[],
).map((entry) => entry["emoji"]),
),
),
"👍",
"🤣",
"😭",
"🤔",
}
.toIList()
.sublist(0, 4)
.map(
(emoji) => IconButton(
onPressed: () async {
Navigator.of(context).pop();
await notifier
.sendReaction(emoji, event)
.onError(showError);
},
icon: Text(emoji),
),
),
"👍",
"🤣",
"😭",
"🤔",
}
.toIList()
.sublist(0, 4)
.map(
(emoji) => IconButton(
onPressed: () async {
Navigator.of(context).pop();
await notifier
.sendReaction(emoji, event)
.onError(showError);
},
icon: Text(emoji),
),
),
EmojiPickerButton(
context: context,
onPressed: Navigator.of(context).pop,
onSelection: (emoji) =>
notifier.sendReaction(emoji, event).onError(showError),
),
],
EmojiPickerButton(
context: context,
onPressed: Navigator.of(context).pop,
onSelection: (emoji) =>
notifier.sendReaction(emoji, event).onError(showError),
),
],
),
),
),
if (ref.watch(