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) { if (member?.content case final MembershipContent content) {
return member!.content as MembershipContent; return content;
} }
final profile = await ref.watch(ProfileController.provider(userId).future); 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/material.dart";
import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:flutter_riverpod/flutter_riverpod.dart";
import "package:nexus/controllers/client_state_controller.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/helpers/launch_helper.dart";
import "package:nexus/models/content/avatar.dart"; import "package:nexus/models/content/avatar.dart";
import "package:nexus/models/content/content.dart"; import "package:nexus/models/content/content.dart";
@ -35,6 +36,7 @@ class EventText extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final theme = Theme.of(context); final theme = Theme.of(context);
final colorScheme = theme.colorScheme; final colorScheme = theme.colorScheme;
final errorStyle = TextStyle(color: colorScheme.error);
final timestamp = Tooltip( final timestamp = Tooltip(
message: event.timestamp.toString(), message: event.timestamp.toString(),
@ -43,140 +45,165 @@ class EventText extends ConsumerWidget {
style: theme.textTheme.labelSmall?.copyWith(color: Colors.grey), 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) { return GestureDetector(
MessageContent() => Row( onSecondaryTapUp: contextMenuCallback,
spacing: 8, onLongPressStart: contextMenuCallback,
children: [ child: switch (event.content) {
isGrouped ? SizedBox(width: 40) : MessageAvatar(event, height: 40), MessageContent() => Row(
Expanded( spacing: 8,
child: Column( children: [
crossAxisAlignment: CrossAxisAlignment.start, isGrouped ? SizedBox(width: 40) : MessageAvatar(event, height: 40),
children: [ Expanded(
if (!isGrouped) child: Column(
Row( crossAxisAlignment: CrossAxisAlignment.start,
spacing: 4, children: [
children: [ if (!isGrouped)
MessageDisplayname( Row(
event, spacing: 4,
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: [ children: [
// Quoted( // TODO: Show replies MessageDisplayname(
// EventText(replyEvent textOnly: true, maxLines: 1,) event,
// ), style: TextStyle(fontWeight: FontWeight.bold),
switch (event.content) { ),
Content(:final parseError?) => SelectableText( Flexible(child: timestamp),
"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(),
},
], ],
), ),
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(
AvatarContent() => Row( spacing: 4,
spacing: 4, children: [
children: [ SizedBox(width: 4),
SizedBox(width: 4), Icon(Icons.numbers),
Icon(Icons.numbers), MessageDisplayname(
MessageDisplayname( event,
event, style: TextStyle(
style: TextStyle( color: theme.colorScheme.primary,
color: theme.colorScheme.primary, fontWeight: FontWeight.bold,
fontWeight: FontWeight.bold, ),
), ),
), Text("changed the room avatar"),
Text("changed the room avatar"), ],
], ),
), _ =>
_ => Text("AAAAA"), textOnly
}; ? Text("Unknown event type", style: errorStyle)
: SizedBox.shrink(),
},
);
} }
} }

View file

@ -99,42 +99,47 @@ class RoomChat extends HookConsumerWidget {
), ),
)) ))
PopupMenuItem( PopupMenuItem(
child: Row( enabled: false,
children: [ child: IconTheme(
...{ data: theme.iconTheme,
...ref.watch( child: Row(
AccountDataController.provider.select( children: [
(value) => IList( ...{
value["m.recent_emoji"]?.content["recent_emoji"] ?? ...ref.watch(
[], AccountDataController.provider.select(
).map((entry) => entry["emoji"]), (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),
), ),
), ),
"👍", EmojiPickerButton(
"🤣", context: context,
"😭", onPressed: Navigator.of(context).pop,
"🤔", onSelection: (emoji) =>
} notifier.sendReaction(emoji, event).onError(showError),
.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),
),
],
), ),
), ),
if (ref.watch( if (ref.watch(