tags,
- })
- onSend;
+ final Room room;
const ChatBox({
required this.relatedMessage,
required this.relationType,
required this.onDismiss,
- required this.onSend,
- this.node,
+ required this.room,
super.key,
});
@@ -46,28 +39,42 @@ class ChatBox extends HookConsumerWidget {
}
void send() {
- if (controller.value.text.isEmpty) return;
- onSend(
- controller.value.formattedText,
- shouldMention: shouldMention.value,
- tags: controller.value.tags.toIList(),
- );
-
+ if (controller.value.text.trim().isEmpty || room.metadata == null) return;
+ ref
+ .watch(RoomChatController.provider(room.metadata!.id).notifier)
+ .send(
+ controller.value.formattedText,
+ shouldMention: shouldMention.value,
+ relation: relatedMessage,
+ relationType: relationType,
+ tags: controller.value.tags,
+ );
onDismiss();
controller.value.text = "";
}
+ final node = useFocusNode(
+ onKeyEvent: (_, event) {
+ if (event is KeyDownEvent && !Platform.isAndroid && !Platform.isIOS) {
+ if (event.logicalKey == LogicalKeyboardKey.enter &&
+ !HardwareKeyboard.instance.isShiftPressed) {
+ send();
+ return KeyEventResult.handled;
+ } else if (event.logicalKey == LogicalKeyboardKey.escape) {
+ onDismiss();
+ return KeyEventResult.handled;
+ }
+ }
+
+ return KeyEventResult.ignored;
+ },
+ )..requestFocus();
+
final style = TextStyle(
color: theme.colorScheme.primary,
fontWeight: FontWeight.bold,
);
- final canSendMessages = ref.watch(
- PowerLevelController.provider(
- PowerLevelConfig(eventType: "m.room.message"),
- ),
- );
-
return Positioned(
bottom: 0,
left: 0,
@@ -80,6 +87,7 @@ class ChatBox extends HookConsumerWidget {
children: [
RelationPreview(
relatedMessage,
+ room: room,
shouldMention: shouldMention.value,
toggleShouldMention: () =>
shouldMention.value = !shouldMention.value,
@@ -91,92 +99,75 @@ class ChatBox extends HookConsumerWidget {
padding: EdgeInsets.symmetric(horizontal: 8),
child: Row(
spacing: 8,
- mainAxisAlignment: MainAxisAlignment.center,
- children: canSendMessages
- ? [
- EmojiPickerButton(
- context: context,
- onSelection: (_) => node?.requestFocus(),
- controller: controller.value,
+ children: [
+ PopupMenuButton(
+ tooltip: "Add media",
+ itemBuilder: (context) => [
+ PopupMenuItem(
+ child: ListTile(
+ title: Text("Camera"),
+ leading: Icon(Icons.add_a_photo),
),
- PopupMenuButton(
- tooltip: "Add media",
- enabled: canSendMessages,
- itemBuilder: (context) => [
- PopupMenuItem(
- child: ListTile(
- title: Text("Camera"),
- leading: Icon(Icons.add_a_photo),
- ),
- ),
- PopupMenuItem(
- child: ListTile(
- title: Text("Gallery"),
- leading: Icon(Icons.add_photo_alternate),
- ),
- ),
- PopupMenuItem(
- child: ListTile(
- title: Text("Files"),
- leading: Icon(Icons.attachment),
- ),
- ),
- ],
- icon: Icon(Icons.add),
+ ),
+ PopupMenuItem(
+ child: ListTile(
+ title: Text("Gallery"),
+ leading: Icon(Icons.add_photo_alternate),
),
- Expanded(
- child: FlutterTagger(
- triggerStrategy: TriggerStrategy.eager,
- overlay: MentionOverlay(
- 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;
- query.value = newQuery;
- },
- triggerCharacterAndStyles: {
- "@": style,
- "#": style,
- },
- builder: (context, key) => TextFormField(
- enabled: canSendMessages,
- maxLines: 12,
- minLines: 1,
- autofocus: true,
- decoration: InputDecoration(
- hintText: "Your message here...",
- border: InputBorder.none,
- ),
- controller: controller.value,
- key: key,
- onFieldSubmitted: (_) => send(),
- // Don't defocus on submit
- onEditingComplete: () {},
- textInputAction: TextInputAction.done,
- focusNode: node,
- ),
- ),
+ ),
+ PopupMenuItem(
+ child: ListTile(
+ title: Text("Files"),
+ leading: Icon(Icons.attachment),
),
- IconButton(
- onPressed: !canSendMessages ? null : send,
- icon: Icon(Icons.send),
- tooltip: "Send message",
+ ),
+ ],
+ icon: Icon(Icons.add),
+ // 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();
+ },
+ ),
+ controller: controller.value,
+ onSearch: (newQuery, newTriggerCharacter) {
+ triggerCharacter.value = newTriggerCharacter;
+ query.value = newQuery;
+ },
+ triggerCharacterAndStyles: {"@": style, "#": style},
+ builder: (context, key) => TextFormField(
+ // enabled: room.canSendDefaultMessages,
+ maxLines: 12,
+ minLines: 1,
+ decoration: InputDecoration(
+ hintText:
+ true // TODO: room.canSendDefaultMessages
+ ? "Your message here..."
+ : "You don't have permission to send messages in this room...",
+ border: InputBorder.none,
),
- ]
- : [
- Padding(
- padding: EdgeInsetsGeometry.all(8),
- child: Text(
- "You don't have permission to send messages in this room...",
- ),
- ),
- ],
+ controller: controller.value,
+ key: key,
+ autofocus: true,
+ focusNode: node,
+ ),
+ ),
+ ),
+ IconButton(
+ onPressed: send,
+ // onPressed: room.canSendDefaultMessages ? send : null,
+ icon: Icon(Icons.send),
+ tooltip: "Send message",
+ ),
+ ],
),
),
],
diff --git a/lib/widgets/chat_page/composer/mention_overlay.dart b/lib/widgets/chat_page/composer/mention_overlay.dart
index b650421..d95253d 100644
--- a/lib/widgets/chat_page/composer/mention_overlay.dart
+++ b/lib/widgets/chat_page/composer/mention_overlay.dart
@@ -1,18 +1,19 @@
import "package:flutter/material.dart";
import "package:hooks_riverpod/hooks_riverpod.dart";
-import "package:nexus/controllers/members_by_type_controller.dart";
+import "package:nexus/controllers/members_controller.dart";
import "package:nexus/controllers/rooms_controller.dart";
-import "package:nexus/controllers/via_controller.dart";
import "package:nexus/helpers/extensions/better_when.dart";
-import "package:nexus/models/membership_status.dart";
+import "package:nexus/models/room.dart";
import "package:nexus/widgets/avatar_or_hash.dart";
import "package:nexus/widgets/loading.dart";
class MentionOverlay extends ConsumerWidget {
final String? triggerCharacter;
final String query;
+ final Room room;
final void Function({required String id, required String name}) addTag;
- const MentionOverlay({
+ const MentionOverlay(
+ this.room, {
required this.query,
required this.addTag,
required this.triggerCharacter,
@@ -33,9 +34,7 @@ class MentionOverlay extends ConsumerWidget {
child: switch (triggerCharacter) {
"@" =>
ref
- .watch(
- MembersByTypeController.provider(MembershipStatus.join),
- )
+ .watch(MembersController.provider(room))
.betterWhen(
data: (members) => ListView(
children:
@@ -63,7 +62,7 @@ class MentionOverlay extends ConsumerWidget {
title: Text(member.displayName),
subtitle: Text(member.userId),
onTap: () => addTag(
- id: "[@${member.displayName}](matrix:u/${member.userId.substring(1)})",
+ id: "[@${member.displayName}](https://matrix.to/#/${member.userId})",
name: member.userId
.substring(1)
.split(":")
@@ -79,43 +78,33 @@ class MentionOverlay extends ConsumerWidget {
(query.isEmpty
? rooms.values
: rooms.values.where(
- (room) =>
- (room.metadata?.name ?? room.metadata!.id)
- .toLowerCase()
- .contains(query.toLowerCase()),
+ (room) => (room.metadata?.name ?? "Unnamed Room")
+ .toLowerCase()
+ .contains(query.toLowerCase()),
))
- .map((room) {
- final name =
- room.metadata?.name ??
- room.metadata!.canonicalAlias ??
- room.metadata!.id;
- return ListTile(
+ .map(
+ (room) => ListTile(
leading: AvatarOrHash(
room.metadata?.avatar,
- name,
+ room.metadata?.name ?? "Unnamed Room",
fallback: Icon(Icons.numbers),
),
- title: Text(name),
+ title: Text(room.metadata?.name ?? "Unnamed Room"),
subtitle: room.metadata?.topic == null
? null
: Text(room.metadata!.topic!, maxLines: 1),
- onTap: () {
- final vias = ref.watch(
- ViaController.provider(room),
- );
- addTag(
- id: "[#$name](matrix:roomid/${room.metadata?.id.substring(1)}$vias)",
- name:
- (room.metadata?.canonicalAlias ??
- room.metadata?.id)
- ?.substring(1)
- .split(":")
- .first ??
- "",
- );
- },
- );
- })
+ onTap: () => addTag(
+ id: "[#${room.metadata?.name ?? "Unnamed Room"}](https://matrix.to/#/${room.metadata?.id})",
+ name:
+ (room.metadata?.canonicalAlias ??
+ room.metadata?.id)
+ ?.substring(1)
+ .split(":")
+ .first ??
+ "",
+ ),
+ ),
+ )
.toList(),
),
diff --git a/lib/widgets/chat_page/composer/relation_preview.dart b/lib/widgets/chat_page/composer/relation_preview.dart
index c90b07b..7fded20 100644
--- a/lib/widgets/chat_page/composer/relation_preview.dart
+++ b/lib/widgets/chat_page/composer/relation_preview.dart
@@ -2,6 +2,7 @@ import "package:flutter/material.dart";
import "package:flutter_chat_core/flutter_chat_core.dart";
import "package:hooks_riverpod/hooks_riverpod.dart";
import "package:nexus/models/relation_type.dart";
+import "package:nexus/models/room.dart";
import "package:nexus/widgets/chat_page/lazy_loading/message_avatar.dart";
import "package:nexus/widgets/chat_page/lazy_loading/message_displayname.dart";
@@ -11,9 +12,11 @@ class RelationPreview extends ConsumerWidget {
final VoidCallback onDismiss;
final bool shouldMention;
final VoidCallback toggleShouldMention;
+ final Room room;
const RelationPreview(
this.relatedMessage, {
+ required this.room,
required this.relationType,
required this.onDismiss,
required this.shouldMention,
@@ -32,38 +35,27 @@ class RelationPreview extends ConsumerWidget {
child: Row(
spacing: 8,
children: [
+ SizedBox(width: 4),
if (relationType == RelationType.edit)
Text(
"Editing message:",
style: TextStyle(fontWeight: FontWeight.bold),
),
-
- MessageAvatar(relatedMessage!),
-
+ MessageAvatar(relatedMessage!, room),
+ MessageDisplayname(
+ relatedMessage!,
+ room,
+ style: theme.textTheme.labelMedium?.copyWith(
+ fontWeight: FontWeight.bold,
+ ),
+ ),
Expanded(
- child: Row(
- spacing: 8,
- children: [
- Flexible(
- child: MessageDisplayname(
- relatedMessage!,
- style: theme.textTheme.labelMedium?.copyWith(
- fontWeight: FontWeight.bold,
- ),
- ),
- ),
- Expanded(
- child: Text(
- relatedMessage?.metadata?["body"] ??
- relatedMessage?.metadata?["eventType"] ??
- "",
- maxLines: 1,
- overflow: TextOverflow.ellipsis,
- softWrap: false,
- style: theme.textTheme.labelMedium,
- ),
- ),
- ],
+ child: Text(
+ relatedMessage?.metadata?["body"] ??
+ relatedMessage?.metadata?["eventType"],
+ overflow: TextOverflow.ellipsis,
+ style: theme.textTheme.labelMedium,
+ maxLines: 1,
),
),
@@ -78,12 +70,11 @@ class RelationPreview extends ConsumerWidget {
),
),
),
-
IconButton(
tooltip:
"Cancel ${relationType == RelationType.edit ? "edit" : "reply"}",
onPressed: onDismiss,
- icon: const Icon(Icons.close),
+ icon: Icon(Icons.close),
iconSize: 20,
),
],
diff --git a/lib/widgets/chat_page/emoji_picker_button.dart b/lib/widgets/chat_page/emoji_picker_button.dart
deleted file mode 100644
index e8805ca..0000000
--- a/lib/widgets/chat_page/emoji_picker_button.dart
+++ /dev/null
@@ -1,51 +0,0 @@
-import "package:emoji_text_field/emoji_text_field.dart";
-import "package:flutter/material.dart";
-import "package:hooks_riverpod/hooks_riverpod.dart";
-import "package:nexus/controllers/emoji_controller.dart";
-
-class EmojiPickerButton extends HookConsumerWidget {
- final TextEditingController? controller;
- final void Function(String emoji)? onSelection;
- final VoidCallback? onPressed;
- final BuildContext context;
- const EmojiPickerButton({
- this.controller,
- this.onPressed,
- this.onSelection,
- required this.context,
- super.key,
- });
-
- @override
- Widget build(_, WidgetRef ref) => IconButton(
- onPressed: () async {
- onPressed?.call();
- final controller = this.controller ?? TextEditingController();
-
- final emojis = await ref.watch(EmojiController.provider.future);
- if (context.mounted) {
- showModalBottomSheet(
- context: context,
- builder: (context) => EmojiKeyboardView(
- config: EmojiViewConfig(
- showRecentTab: false,
- customCategories: emojis.$1.unlock,
- customKeywords: emojis.$2.unlock,
- backgroundColor: Theme.of(context).colorScheme.surfaceContainer,
- height: 600,
- ),
- textController: controller
- ..addListener(() {
- // Without this, there will sometimes be a debugLocked is not true error sometimes
- Future.delayed(Duration.zero, () {
- if (context.mounted) Navigator.of(context).pop();
- });
- onSelection?.call(controller.text);
- }),
- ),
- );
- }
- },
- icon: Icon(Icons.emoji_emotions),
- );
-}
diff --git a/lib/widgets/chat_page/expandable_image.dart b/lib/widgets/chat_page/expandable_image.dart
deleted file mode 100644
index ac5bbe1..0000000
--- a/lib/widgets/chat_page/expandable_image.dart
+++ /dev/null
@@ -1,48 +0,0 @@
-import "dart:math";
-import "package:cross_cache/cross_cache.dart";
-import "package:flutter/material.dart";
-import "package:hooks_riverpod/hooks_riverpod.dart";
-import "package:nexus/controllers/cross_cache_controller.dart";
-import "package:nexus/helpers/extensions/get_headers.dart";
-import "package:nexus/widgets/error_dialog.dart";
-
-class ExpandableImage extends ConsumerWidget {
- final Widget child;
- final String? source;
- const ExpandableImage(this.source, {required this.child, super.key});
-
- @override
- Widget build(BuildContext context, WidgetRef ref) => InkWell(
- onTap: source == null
- ? null
- : () => showDialog(
- context: context,
- builder: (_) => LayoutBuilder(
- builder: (context, constraints) => Dialog(
- backgroundColor: Colors.transparent,
- insetPadding: EdgeInsets.all(constraints.maxWidth / 100),
- child: ConstrainedBox(
- constraints: BoxConstraints(
- minWidth: min(constraints.maxWidth, 1000),
- ),
- child: InteractiveViewer(
- child: Image(
- fit: BoxFit.contain,
- errorBuilder: (_, error, stackTrace) => ErrorDialog(
- "Loading failed for $source\nError: $error",
- stackTrace,
- ),
- image: CachedNetworkImage(
- source!,
- ref.watch(CrossCacheController.provider),
- headers: ref.headers,
- ),
- ),
- ),
- ),
- ),
- ),
- ),
- child: child,
- );
-}
diff --git a/lib/widgets/chat_page/html/html.dart b/lib/widgets/chat_page/html/html.dart
index fb533ad..dcc1d49 100644
--- a/lib/widgets/chat_page/html/html.dart
+++ b/lib/widgets/chat_page/html/html.dart
@@ -1,15 +1,12 @@
-import "package:cross_cache/cross_cache.dart";
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:nexus/controllers/client_state_controller.dart";
-import "package:nexus/controllers/cross_cache_controller.dart";
import "package:nexus/helpers/extensions/get_headers.dart";
import "package:nexus/helpers/extensions/link_to_mention.dart";
import "package:nexus/helpers/extensions/mxc_to_https.dart";
import "package:nexus/helpers/launch_helper.dart";
-import "package:nexus/widgets/chat_page/expandable_image.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";
@@ -33,29 +30,20 @@ class Html extends ConsumerWidget {
return InlineCustomWidget(child: SpoilerText(text: element.text));
}
- final height =
- int.tryParse(element.attributes["height"] ?? "") ??
- (element.attributes.keys.contains("data-mx-emoticon") ? 32 : null) ??
- 300;
+ final height = int.tryParse(element.attributes["height"] ?? "") ?? 300;
final width = int.tryParse(element.attributes["width"] ?? "");
- final src = Uri.tryParse(element.attributes["src"] ?? "")
- ?.mxcToHttps(
- ref.watch(
- ClientStateController.provider.select(
- (value) => value?.homeserverUrl,
- ),
- ) ??
- "",
- )
- .toString();
return switch (element.localName) {
"code" =>
element.parent?.localName == "pre"
- ? CodeBlock(
- element.text,
- lang: element.className.replaceAll("language-", ""),
- )
+ ? element.outerHtml.contains("
")
+ ? Html(
+ """${element.outerHtml.replaceAll("
", "\n")}""",
+ )
+ : CodeBlock(
+ element.text,
+ lang: element.className.replaceAll("language-", ""),
+ )
: null,
"blockquote" => Quoted(Html(element.innerHtml)),
@@ -63,40 +51,39 @@ class Html extends ConsumerWidget {
"a" =>
element.attributes["href"]?.mention == null
? null
- : InlineCustomWidget(
- child: MentionChip(element.attributes["href"]!),
- ),
+ : InlineCustomWidget(child: MentionChip(element.text)),
"img" =>
- src == null
+ element.attributes["src"] == null
? SizedBox.shrink()
: InlineCustomWidget(
alignment: PlaceholderAlignment.middle,
- child: ExpandableImage(
- src,
- child: Image(
- image: CachedNetworkImage(
- src,
- ref.watch(CrossCacheController.provider),
- headers: ref.headers,
+ child: Image.network(
+ Uri.parse(element.attributes["src"]!)
+ .mxcToHttps(
+ ref.watch(
+ ClientStateController.provider.select(
+ (value) => value?.homeserverUrl,
+ ),
+ ) ??
+ "",
+ )
+ .toString(),
+ headers: ref.headers,
+ errorBuilder: (_, error, _) => Text(
+ "Image Failed to Load",
+ style: TextStyle(
+ color: Theme.of(context).colorScheme.error,
),
- 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(),
),
+ height: height.toDouble(),
+ width: width?.toDouble(),
+ loadingBuilder: (_, child, loadingProgress) =>
+ loadingProgress == null
+ ? child
+ : CircularProgressIndicator(),
),
),
-
- // Allowed elements list
("del" ||
"h1" ||
"h2" ||
diff --git a/lib/widgets/chat_page/html/mention_chip.dart b/lib/widgets/chat_page/html/mention_chip.dart
index 575ad03..c2b832d 100644
--- a/lib/widgets/chat_page/html/mention_chip.dart
+++ b/lib/widgets/chat_page/html/mention_chip.dart
@@ -1,44 +1,25 @@
import "package:flutter/material.dart";
-import "package:flutter_riverpod/flutter_riverpod.dart";
-import "package:nexus/controllers/user_controller.dart";
import "package:nexus/helpers/extensions/link_to_mention.dart";
-import "package:nexus/helpers/extensions/show_user_popover.dart";
-class MentionChip extends ConsumerWidget {
- final String content;
- const MentionChip(this.content, {super.key});
+class MentionChip extends StatelessWidget {
+ final String label;
+ const MentionChip(this.label, {super.key});
@override
- Widget build(BuildContext context, WidgetRef ref) {
- final membership = content.mention!.startsWith("@") == true
- ? ref
- .watch(UserController.provider(content.mention!))
- .whenOrNull(data: (data) => data)
- : null;
-
- return InkWell(
- onTapUp: (details) {
- content.mention;
- if (membership != null) {
- context.showUserPopover(
- membership,
- globalPosition: details.globalPosition,
- );
- }
- },
- child: IgnorePointer(
- child: Chip(
- label: Text(
- (membership == null ? null : "@${membership.displayName}") ??
- content.mention!,
- style: TextStyle(
- fontWeight: FontWeight.bold,
- color: Theme.of(context).colorScheme.onPrimary,
- ),
- ),
- backgroundColor: Theme.of(context).colorScheme.primary,
- ),
+ Widget build(BuildContext context) => ActionChip(
+ label: Text(
+ label.mention ?? label,
+ style: TextStyle(
+ fontWeight: FontWeight.bold,
+ color: Theme.of(context).colorScheme.onPrimary,
),
- );
- }
+ ),
+ backgroundColor: Theme.of(context).colorScheme.primary,
+ onPressed: () => showDialog(
+ context: context,
+ builder: (_) => Dialog(
+ child: Text("TODO: Open room or join room dialog, or user popover"),
+ ),
+ ),
+ );
}
diff --git a/lib/widgets/chat_page/expandable_image_message.dart b/lib/widgets/chat_page/image_message.dart
similarity index 55%
rename from lib/widgets/chat_page/expandable_image_message.dart
rename to lib/widgets/chat_page/image_message.dart
index f6e8a03..103fdd2 100644
--- a/lib/widgets/chat_page/expandable_image_message.dart
+++ b/lib/widgets/chat_page/image_message.dart
@@ -1,3 +1,4 @@
+import "dart:math";
import "package:cross_cache/cross_cache.dart";
import "package:flutter/material.dart";
import "package:flutter_chat_core/flutter_chat_core.dart";
@@ -5,7 +6,6 @@ import "package:flutter_riverpod/flutter_riverpod.dart";
import "package:flyer_chat_image_message/flyer_chat_image_message.dart";
import "package:nexus/controllers/cross_cache_controller.dart";
import "package:nexus/helpers/extensions/get_headers.dart";
-import "package:nexus/widgets/chat_page/expandable_image.dart";
class ExpandableImageMessage extends ConsumerWidget {
final ImageMessage message;
@@ -14,8 +14,31 @@ class ExpandableImageMessage extends ConsumerWidget {
const ExpandableImageMessage(this.message, {required this.index, super.key});
@override
- Widget build(BuildContext context, WidgetRef ref) => ExpandableImage(
- message.source,
+ Widget build(BuildContext context, WidgetRef ref) => InkWell(
+ onTap: () => showDialog(
+ context: context,
+ builder: (_) => LayoutBuilder(
+ builder: (context, constraints) => Dialog(
+ backgroundColor: Colors.transparent,
+ insetPadding: EdgeInsets.all(constraints.maxWidth / 100),
+ child: ConstrainedBox(
+ constraints: BoxConstraints(
+ minWidth: min(constraints.maxWidth, 1000),
+ ),
+ child: InteractiveViewer(
+ child: Image(
+ fit: BoxFit.contain,
+ image: CachedNetworkImage(
+ message.source,
+ ref.watch(CrossCacheController.provider),
+ headers: ref.headers,
+ ),
+ ),
+ ),
+ ),
+ ),
+ ),
+ ),
child: FlyerChatImageMessage(
customImageProvider: CachedNetworkImage(
message.source,
diff --git a/lib/widgets/chat_page/join_dialog.dart b/lib/widgets/chat_page/join_dialog.dart
deleted file mode 100644
index e718200..0000000
--- a/lib/widgets/chat_page/join_dialog.dart
+++ /dev/null
@@ -1,137 +0,0 @@
-import "package:collection/collection.dart";
-import "package:fast_immutable_collections/fast_immutable_collections.dart";
-import "package:flutter/material.dart";
-import "package:flutter_hooks/flutter_hooks.dart";
-import "package:hooks_riverpod/hooks_riverpod.dart";
-import "package:nexus/controllers/client_controller.dart";
-import "package:nexus/controllers/key_controller.dart";
-import "package:nexus/controllers/spaces_controller.dart";
-import "package:nexus/helpers/extensions/link_to_mention.dart";
-import "package:nexus/models/requests/join_room_request.dart";
-import "package:nexus/widgets/form_text_input.dart";
-
-class JoinDialog extends HookWidget {
- final WidgetRef ref;
- const JoinDialog(this.ref, {super.key});
-
- @override
- Widget build(BuildContext context) {
- final roomAlias = useTextEditingController();
- return AlertDialog(
- title: Text("Join a Room"),
- content: Column(
- mainAxisSize: MainAxisSize.min,
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text("Enter the room alias, Matrix URI, or Matrix.to link."),
- SizedBox(height: 12),
- FormTextInput(
- required: false,
- capitalize: true,
- controller: roomAlias,
- title: "#room:server",
- ),
- ],
- ),
- actions: [
- TextButton(onPressed: Navigator.of(context).pop, child: Text("Cancel")),
- TextButton(
- onPressed: () async {
- Navigator.of(context).pop();
-
- if (context.mounted) {
- final roomIdOrAlias = roomAlias.text.mention ?? roomAlias.text;
-
- final scaffoldMessenger = ScaffoldMessenger.of(context);
-
- final snackbar = scaffoldMessenger.showSnackBar(
- SnackBar(
- content: Text("Joining room $roomIdOrAlias."),
- duration: Duration(days: 999),
- ),
- );
-
- try {
- final id = await ref
- .watch(ClientController.provider.notifier)
- .joinRoom(
- JoinRoomRequest(
- roomIdOrAlias: roomIdOrAlias,
- via: IList(
- Uri.tryParse(
- roomAlias.text.replaceAll("/#", ""),
- )?.queryParametersAll["via"] ??
- [],
- ),
- ),
- );
-
- snackbar.close();
-
- scaffoldMessenger.showSnackBar(
- SnackBar(
- content: Text("Room $roomIdOrAlias successfully joined."),
- action: SnackBarAction(
- label: "Open",
- onPressed: () async {
- final spaces = ref.watch(SpacesController.provider);
- final space = spaces.firstWhereOrNull(
- (space) => space.id == id,
- );
-
- await ref
- .watch(
- KeyController.provider(
- KeyController.spaceKey,
- ).notifier,
- )
- .set(
- space?.id ??
- spaces
- .firstWhere(
- (space) => space.children.any(
- (child) => child.metadata?.id == id,
- ),
- )
- .id,
- );
-
- if (space == null) {
- await ref
- .watch(
- KeyController.provider(
- KeyController.roomKey,
- ).notifier,
- )
- .set(id);
- }
- },
- ),
- ),
- );
- } catch (error) {
- snackbar.close();
- if (context.mounted) {
- scaffoldMessenger.showSnackBar(
- SnackBar(
- backgroundColor: Theme.of(
- context,
- ).colorScheme.errorContainer,
- content: Text(
- error.toString(),
- style: TextStyle(
- color: Theme.of(context).colorScheme.onErrorContainer,
- ),
- ),
- ),
- );
- }
- }
- }
- },
- child: Text("Join"),
- ),
- ],
- );
- }
-}
diff --git a/lib/widgets/chat_page/lazy_loading/message_avatar.dart b/lib/widgets/chat_page/lazy_loading/message_avatar.dart
index dc8dfef..71fcf84 100644
--- a/lib/widgets/chat_page/lazy_loading/message_avatar.dart
+++ b/lib/widgets/chat_page/lazy_loading/message_avatar.dart
@@ -1,30 +1,28 @@
-import "package:flutter/material.dart";
+import "package:flutter/widgets.dart";
import "package:flutter_chat_core/flutter_chat_core.dart";
import "package:flutter_riverpod/flutter_riverpod.dart";
import "package:nexus/controllers/author_controller.dart";
import "package:nexus/helpers/extensions/better_when.dart";
-import "package:nexus/helpers/extensions/show_user_popover.dart";
+import "package:nexus/models/configs/author_config.dart";
+import "package:nexus/models/room.dart";
import "package:nexus/widgets/avatar_or_hash.dart";
class MessageAvatar extends ConsumerWidget {
final Message message;
+ final Room room;
final double height;
- const MessageAvatar(this.message, {this.height = 16, super.key});
+ const MessageAvatar(this.message, this.room, {this.height = 16, super.key});
@override
Widget build(BuildContext context, WidgetRef ref) => ref
- .watch(AuthorController.provider(message))
+ .watch(
+ AuthorController.provider(AuthorConfig(room: room, message: message)),
+ )
.betterWhen(
- data: (membership) => InkWell(
- onTapUp: (details) => context.showUserPopover(
- membership,
- globalPosition: details.globalPosition,
- ),
- child: AvatarOrHash(
- membership.avatarUrl,
- membership.displayName,
- height: height,
- ),
+ data: (membership) => AvatarOrHash(
+ membership.avatarUrl,
+ membership.displayName,
+ height: height,
),
loading: () =>
AvatarOrHash(null, message.authorId.substring(1), height: height),
diff --git a/lib/widgets/chat_page/lazy_loading/message_displayname.dart b/lib/widgets/chat_page/lazy_loading/message_displayname.dart
index 88d2fa6..7c10df3 100644
--- a/lib/widgets/chat_page/lazy_loading/message_displayname.dart
+++ b/lib/widgets/chat_page/lazy_loading/message_displayname.dart
@@ -1,37 +1,27 @@
-import "package:flutter/material.dart";
+import "package:flutter/widgets.dart";
import "package:flutter_chat_core/flutter_chat_core.dart";
import "package:flutter_riverpod/flutter_riverpod.dart";
import "package:nexus/controllers/author_controller.dart";
import "package:nexus/helpers/extensions/better_when.dart";
-import "package:nexus/helpers/extensions/show_user_popover.dart";
+import "package:nexus/models/configs/author_config.dart";
+import "package:nexus/models/room.dart";
class MessageDisplayname extends ConsumerWidget {
final Message message;
+ final Room room;
final TextStyle? style;
- final bool clickable;
- const MessageDisplayname(
- this.message, {
- this.clickable = true,
- this.style,
- super.key,
- });
+ const MessageDisplayname(this.message, this.room, {this.style, super.key});
@override
Widget build(BuildContext context, WidgetRef ref) => ref
- .watch(AuthorController.provider(message))
+ .watch(
+ AuthorController.provider(AuthorConfig(room: room, message: message)),
+ )
.betterWhen(
- data: (membership) => InkWell(
- onTapUp: clickable
- ? (details) => context.showUserPopover(
- membership,
- globalPosition: details.globalPosition,
- )
- : null,
- child: Text(
- "${membership.displayName}${message.metadata?["pmp"] == null ? "" : " (via ${message.authorId})"}",
- style: style,
- overflow: TextOverflow.ellipsis,
- ),
+ data: (membership) => Text(
+ "${membership.displayName} ${message.metadata?["pmp"] == null ? "" : "(via ${message.authorId})"}",
+ style: style,
+ overflow: TextOverflow.ellipsis,
),
loading: () => Text(""),
);
diff --git a/lib/widgets/chat_page/member_list.dart b/lib/widgets/chat_page/member_list.dart
index 8be1ddd..8cdbbb9 100644
--- a/lib/widgets/chat_page/member_list.dart
+++ b/lib/widgets/chat_page/member_list.dart
@@ -1,31 +1,27 @@
import "package:flutter/material.dart";
-import "package:flutter_hooks/flutter_hooks.dart";
import "package:hooks_riverpod/hooks_riverpod.dart";
-import "package:nexus/controllers/members_by_type_controller.dart";
+import "package:nexus/controllers/members_controller.dart";
import "package:nexus/helpers/extensions/better_when.dart";
-import "package:nexus/helpers/extensions/show_user_popover.dart";
-import "package:nexus/models/membership_status.dart";
+import "package:nexus/models/room.dart";
import "package:nexus/widgets/avatar_or_hash.dart";
-class MemberList extends HookConsumerWidget {
- const MemberList({super.key});
+class MemberList extends ConsumerWidget {
+ final Room room;
+ const MemberList(this.room, {super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
- final status = useState(MembershipStatus.join);
- final membersProvider = ref.watch(
- MembersByTypeController.provider(status.value),
- );
-
+ final membersProvider = ref.watch(MembersController.provider(room));
return Drawer(
shape: Border(),
child: Column(
- spacing: 8,
children: [
AppBar(
scrolledUnderElevation: 0,
leading: Icon(Icons.people),
- title: Text("Members"),
+ title: Text(
+ "Members ${membersProvider.when(data: (members) => "${members.length}", error: (_, _) => "", loading: () => "")}",
+ ),
actionsPadding: EdgeInsets.only(right: 4),
actions: [
if (Scaffold.of(context).hasEndDrawer)
@@ -36,50 +32,28 @@ class MemberList extends HookConsumerWidget {
),
],
),
- Row(
- mainAxisAlignment: MainAxisAlignment.center,
- spacing: 8,
- children: [
- FilterChip(
- label: Text("Joined"),
- onSelected: (value) => status.value = MembershipStatus.join,
- selected: status.value == MembershipStatus.join,
- ),
- FilterChip(
- label: Text("Invited"),
- onSelected: (value) => status.value = MembershipStatus.invite,
- selected: status.value == MembershipStatus.invite,
- ),
- FilterChip(
- label: Text("Banned"),
- onSelected: (value) => status.value = MembershipStatus.ban,
- selected: status.value == MembershipStatus.ban,
- ),
- ],
- ),
membersProvider.betterWhen(
data: (members) => Expanded(
child: ListView(
children: members
.map(
- (member) => InkWell(
- onTapUp: (details) => context.showUserPopover(
- member,
- globalPosition: details.globalPosition,
+ (member) => ListTile(
+ onTap: () => showDialog(
+ context: context,
+ builder: (context) =>
+ Dialog(child: Text("TODO: Open member popover")),
),
- child: ListTile(
- leading: AvatarOrHash(
- member.avatarUrl,
- member.displayName,
- ),
- title: Text(
- member.displayName,
- overflow: TextOverflow.ellipsis,
- ),
- subtitle: Text(
- member.userId,
- overflow: TextOverflow.ellipsis,
- ),
+ leading: AvatarOrHash(
+ member.avatarUrl,
+ member.displayName,
+ ),
+ title: Text(
+ member.displayName,
+ overflow: TextOverflow.ellipsis,
+ ),
+ subtitle: Text(
+ member.userId,
+ overflow: TextOverflow.ellipsis,
),
),
)
diff --git a/lib/widgets/chat_page/reply_widget.dart b/lib/widgets/chat_page/reply_widget.dart
index b999be4..b9fa2e1 100644
--- a/lib/widgets/chat_page/reply_widget.dart
+++ b/lib/widgets/chat_page/reply_widget.dart
@@ -3,10 +3,10 @@ import "package:flutter_chat_core/flutter_chat_core.dart";
import "package:flutter_riverpod/flutter_riverpod.dart";
import "package:nexus/controllers/event_controller.dart";
import "package:nexus/controllers/message_controller.dart";
-import "package:nexus/controllers/selected_room_controller.dart";
import "package:nexus/helpers/extensions/better_when.dart";
import "package:nexus/models/configs/message_config.dart";
import "package:nexus/models/requests/get_event_request.dart";
+import "package:nexus/models/room.dart";
import "package:nexus/widgets/chat_page/html/quoted.dart";
import "package:nexus/widgets/chat_page/lazy_loading/message_avatar.dart";
import "package:nexus/widgets/chat_page/lazy_loading/message_displayname.dart";
@@ -16,10 +16,12 @@ typedef OnTapReply = void Function(Message message)?;
class ReplyWidget extends ConsumerWidget {
final Message message;
final bool alwaysShow;
+ final Room room;
final MessageGroupStatus? groupStatus;
final OnTapReply onTapReply;
const ReplyWidget(
this.message, {
+ required this.room,
required this.groupStatus,
this.onTapReply,
this.alwaysShow = false,
@@ -27,75 +29,73 @@ class ReplyWidget extends ConsumerWidget {
});
@override
- Widget build(BuildContext context, WidgetRef ref) {
- final room = ref.watch(SelectedRoomController.provider);
- return message.replyToMessageId == null || room == null
- ? SizedBox.shrink()
- : Padding(
- padding: EdgeInsets.only(bottom: 12),
- child: Quoted(
- ref
- .watch(
- EventController.provider(
- GetEventRequest(
- room: room,
- eventId: message.replyToMessageId!,
- ),
+ Widget build(BuildContext context, WidgetRef ref) =>
+ message.replyToMessageId == null
+ ? SizedBox.shrink()
+ : Padding(
+ padding: EdgeInsets.only(bottom: 12),
+ child: Quoted(
+ ref
+ .watch(
+ EventController.provider(
+ GetEventRequest(
+ room: room,
+ eventId: message.replyToMessageId!,
),
- )
- .betterWhen(
- loading: () => Text("Fetching event..."),
- data: (event) => event == null
- ? SizedBox.shrink()
- : ref
- .watch(
- MessageController.provider(
- MessageConfig(room: room, event: event),
- ),
- )
- .betterWhen(
- loading: () => Text("Parsing message..."),
- data: (replyMessage) {
- if (replyMessage == null) {
- return SizedBox.shrink();
- }
-
- return InkWell(
- onTap: () => onTapReply?.call(replyMessage),
- child: Row(
- mainAxisSize: MainAxisSize.min,
- spacing: 8,
- children: [
- MessageAvatar(replyMessage),
- Flexible(
- child: MessageDisplayname(
- replyMessage,
- clickable: false,
- style: Theme.of(context)
- .textTheme
- .labelMedium
- ?.copyWith(
- fontWeight: FontWeight.bold,
- ),
- ),
- ),
- Flexible(
- child: Text(
- replyMessage.metadata!["body"],
- overflow: TextOverflow.ellipsis,
- style: Theme.of(
- context,
- ).textTheme.labelMedium,
- maxLines: 1,
- ),
- ),
- ],
- ),
- );
- },
- ),
),
- ),
- );
- }
+ )
+ .betterWhen(
+ loading: () => Text("Fetching event..."),
+ data: (event) => event == null
+ ? SizedBox.shrink()
+ : ref
+ .watch(
+ MessageController.provider(
+ MessageConfig(room: room, event: event),
+ ),
+ )
+ .betterWhen(
+ loading: () => Text("Parsing message..."),
+ data: (replyMessage) {
+ if (replyMessage == null) {
+ return SizedBox.shrink();
+ }
+
+ return InkWell(
+ onTap: () => onTapReply?.call(replyMessage),
+ child: Row(
+ mainAxisSize: MainAxisSize.min,
+ spacing: 8,
+ children: [
+ MessageAvatar(replyMessage, room),
+ Flexible(
+ child: MessageDisplayname(
+ replyMessage,
+ room,
+ style: Theme.of(context)
+ .textTheme
+ .labelMedium
+ ?.copyWith(
+ fontWeight: FontWeight.bold,
+ ),
+ ),
+ ),
+ Flexible(
+ child: Text(
+ replyMessage.metadata!["body"],
+ overflow: TextOverflow.ellipsis,
+ style: Theme.of(
+ context,
+ ).textTheme.labelMedium,
+ maxLines: 1,
+ ),
+ ),
+ ],
+ ),
+ );
+ },
+ ),
+ ),
+ ),
+ );
}
diff --git a/lib/widgets/chat_page/room_appbar.dart b/lib/widgets/chat_page/room_appbar.dart
index 62e282d..436bcb9 100644
--- a/lib/widgets/chat_page/room_appbar.dart
+++ b/lib/widgets/chat_page/room_appbar.dart
@@ -1,20 +1,20 @@
import "package:fast_immutable_collections/fast_immutable_collections.dart";
import "package:flutter/material.dart";
-import "package:hooks_riverpod/hooks_riverpod.dart";
-import "package:nexus/controllers/selected_room_controller.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/expandable_image.dart";
import "package:nexus/widgets/chat_page/room_menu.dart";
-class RoomAppbar extends ConsumerWidget implements PreferredSizeWidget {
+class RoomAppbar extends StatelessWidget implements PreferredSizeWidget {
final bool isDesktop;
- final void Function(BuildContext context)? onOpenMemberList;
+ final Room room;
+ final void Function(BuildContext context) onOpenMemberList;
final void Function(BuildContext context) onOpenDrawer;
- const RoomAppbar({
+ const RoomAppbar(
+ this.room, {
required this.isDesktop,
+ required this.onOpenMemberList,
required this.onOpenDrawer,
- this.onOpenMemberList,
super.key,
});
@@ -22,57 +22,47 @@ class RoomAppbar extends ConsumerWidget implements PreferredSizeWidget {
Size get preferredSize => AppBar().preferredSize;
@override
- Widget build(BuildContext context, WidgetRef ref) {
- final room = ref.watch(SelectedRoomController.provider);
- return Appbar(
- leading: isDesktop
- ? room == null
- ? null
- : ExpandableImage(
- room.metadata?.avatar?.toString(),
- child: AvatarOrHash(
- room.metadata?.avatar,
- room.metadata?.name ?? "Unnamed Rooms",
- height: 24,
- fallback: Icon(Icons.numbers),
- ),
- )
- : DrawerButton(onPressed: () => onOpenDrawer(context)),
- scrolledUnderElevation: 0,
- title: room == null
- ? null
- : Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text(
- room.metadata?.name ?? "Unnamed Room",
- overflow: TextOverflow.ellipsis,
- maxLines: 1,
- ),
- if (room.metadata?.topic?.isNotEmpty == true)
- Text(
- room.metadata!.topic!,
- maxLines: 1,
- overflow: TextOverflow.ellipsis,
- style: Theme.of(context).textTheme.labelMedium?.copyWith(
- color: Theme.of(context).colorScheme.onSurfaceVariant,
- ),
- ),
- ],
+ Widget build(BuildContext context) => Appbar(
+ leading: isDesktop
+ ? AvatarOrHash(
+ room.metadata?.avatar,
+ room.metadata?.name ?? "Unnamed Rooms",
+ height: 24,
+ fallback: Icon(Icons.numbers),
+ )
+ : DrawerButton(onPressed: () => onOpenDrawer(context)),
+ scrolledUnderElevation: 0,
+ title: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(
+ room.metadata?.name ?? "Unnamed Room",
+ overflow: TextOverflow.ellipsis,
+ maxLines: 1,
+ ),
+ if (room.metadata?.topic?.isNotEmpty == true)
+ Text(
+ room.metadata!.topic!,
+ maxLines: 1,
+ overflow: TextOverflow.ellipsis,
+ style: Theme.of(context).textTheme.labelMedium?.copyWith(
+ color: Theme.of(context).colorScheme.onSurfaceVariant,
),
- actions: [
- IconButton(
- onPressed: null,
- icon: Icon(Icons.push_pin),
- tooltip: "Open pinned messages",
- ),
- IconButton(
- onPressed: () => onOpenMemberList?.call(context),
- tooltip: "Open member list",
- icon: Icon(Icons.people),
- ),
- if (room != null) RoomMenu(room),
- ].toIList(),
- );
- }
+ ),
+ ],
+ ),
+ actions: [
+ IconButton(
+ onPressed: null,
+ icon: Icon(Icons.push_pin),
+ tooltip: "Open pinned messages",
+ ),
+ IconButton(
+ onPressed: () => onOpenMemberList(context),
+ tooltip: "Open member list",
+ icon: Icon(Icons.people),
+ ),
+ RoomMenu(room),
+ ].toIList(),
+ );
}
diff --git a/lib/widgets/chat_page/room_chat.dart b/lib/widgets/chat_page/room_chat.dart
index 7fb3f8f..6b3839a 100644
--- a/lib/widgets/chat_page/room_chat.dart
+++ b/lib/widgets/chat_page/room_chat.dart
@@ -1,34 +1,27 @@
-import "package:fast_immutable_collections/fast_immutable_collections.dart";
import "package:flutter/material.dart";
-import "package:flutter/services.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:flyer_chat_file_message/flyer_chat_file_message.dart";
import "package:flyer_chat_system_message/flyer_chat_system_message.dart";
import "package:hooks_riverpod/hooks_riverpod.dart";
-import "package:nexus/controllers/account_data_controller.dart";
import "package:nexus/controllers/client_controller.dart";
import "package:nexus/controllers/client_state_controller.dart";
-import "package:nexus/controllers/power_level_controller.dart";
import "package:nexus/controllers/selected_room_controller.dart";
import "package:nexus/controllers/room_chat_controller.dart";
-import "package:nexus/controllers/via_controller.dart";
import "package:nexus/helpers/extensions/better_when.dart";
import "package:nexus/helpers/extensions/show_context_menu.dart";
-import "package:nexus/models/configs/power_level_config.dart";
import "package:nexus/models/relation_type.dart";
import "package:nexus/models/requests/report_request.dart";
import "package:nexus/widgets/chat_page/composer/chat_box.dart";
-import "package:nexus/widgets/chat_page/emoji_picker_button.dart";
-import "package:nexus/widgets/chat_page/expandable_image_message.dart";
+import "package:nexus/widgets/chat_page/image_message.dart";
import "package:nexus/widgets/chat_page/member_list.dart";
import "package:nexus/widgets/chat_page/wrappers/message_wrapper.dart";
import "package:nexus/widgets/chat_page/room_appbar.dart";
import "package:nexus/widgets/chat_page/wrappers/text_message_wrapper.dart";
import "package:nexus/widgets/chat_page/reply_widget.dart";
import "package:nexus/widgets/form_text_input.dart";
-import "package:nexus/main.dart";
+// import "package:dynamic_polls/dynamic_polls.dart";
class RoomChat extends HookConsumerWidget {
final bool isDesktop;
@@ -42,138 +35,46 @@ class RoomChat extends HookConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final client = ref.watch(ClientController.provider.notifier);
- final relatedMessage = useState(null);
+ final replyToMessage = useState(null);
final memberListOpened = useState(showMembersByDefault);
final relationType = useState(RelationType.reply);
+ final room = ref.watch(SelectedRoomController.provider);
final userId = ref.watch(ClientStateController.provider)?.userId;
- final roomId = ref.watch(
- SelectedRoomController.provider.select((value) => value?.metadata?.id),
- );
final theme = Theme.of(context);
final danger = theme.colorScheme.error;
- if (roomId == null || userId == null) {
- return Scaffold(
- appBar: RoomAppbar(
- isDesktop: isDesktop,
- onOpenDrawer: (_) => Scaffold.of(context).openDrawer(),
- onOpenMemberList: null,
- ),
- body: Center(
- child: Text(
- "Nothing to see here...",
- style: theme.textTheme.headlineMedium,
- ),
+ if (room == null || userId == null || room.metadata?.id == null) {
+ return Center(
+ child: Text(
+ "Nothing to see here...",
+ style: theme.textTheme.headlineMedium,
),
);
}
- final controllerProvider = RoomChatController.provider(roomId);
+ final controllerProvider = RoomChatController.provider(room.metadata!.id);
final notifier = ref.watch(controllerProvider.notifier);
- final composerNode = useFocusNode(
- onKeyEvent: (_, event) {
- if (event is KeyDownEvent &&
- event.logicalKey == LogicalKeyboardKey.escape) {
- relatedMessage.value = null;
- return KeyEventResult.handled;
- }
-
- return KeyEventResult.ignored;
- },
- );
-
List getMessageOptions(Message message) {
final isSentByMe = message.authorId == userId;
return [
- if (ref.watch(
- PowerLevelController.provider(
- PowerLevelConfig(eventType: "m.reaction"),
- ),
- ))
- PopupMenuItem(
- 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, message)
- .onError(showError);
- },
- icon: Text(emoji),
- ),
- ),
- EmojiPickerButton(
- context: context,
- onPressed: Navigator.of(context).pop,
- onSelection: (emoji) =>
- notifier.sendReaction(emoji, message).onError(showError),
- ),
- ],
- ),
- ),
- if (ref.watch(
- PowerLevelController.provider(
- PowerLevelConfig(eventType: "m.room.message"),
- ),
- ))
- PopupMenuItem(
- onTap: () {
- relatedMessage.value = message;
- relationType.value = RelationType.reply;
- composerNode.requestFocus();
- },
- child: ListTile(leading: Icon(Icons.reply), title: Text("Reply")),
- ),
+ PopupMenuItem(
+ onTap: () {
+ replyToMessage.value = message;
+ relationType.value = RelationType.reply;
+ },
+ child: ListTile(leading: Icon(Icons.reply), title: Text("Reply")),
+ ),
if (message is TextMessage && isSentByMe)
PopupMenuItem(
onTap: () {
- relatedMessage.value = message;
+ replyToMessage.value = message;
relationType.value = RelationType.edit;
- composerNode.requestFocus();
},
child: ListTile(leading: Icon(Icons.edit), title: Text("Edit")),
),
- PopupMenuItem(
- onTap: () async {
- final room = ref.watch(SelectedRoomController.provider);
- if (room == null) return;
-
- final vias = ref.watch(ViaController.provider(room));
-
- await Clipboard.setData(
- ClipboardData(
- text:
- "matrix:roomid/${room.metadata?.id.substring(1)}/e/${message.id}$vias)",
- ),
- );
- },
- child: ListTile(leading: Icon(Icons.link), title: Text("Copy Link")),
- ),
- if (ref.watch(
- PowerLevelController.provider(
- PowerLevelConfig(eventType: "m.room.redaction"),
- ),
- ))
+ if (isSentByMe) // TODO: Or if user has permission to redact others' messages
PopupMenuItem(
onTap: () => showDialog(
context: context,
@@ -205,13 +106,11 @@ class RoomChat extends HookConsumerWidget {
),
TextButton(
onPressed: () async {
+ notifier.deleteMessage(
+ message,
+ reason: deleteReasonController.text,
+ );
Navigator.of(context).pop();
- await notifier
- .deleteMessage(
- message,
- reason: deleteReasonController.text,
- )
- .onError(showError);
},
child: Text("Delete"),
),
@@ -220,10 +119,7 @@ class RoomChat extends HookConsumerWidget {
},
),
),
- child: ListTile(
- leading: Icon(Icons.delete, color: danger),
- title: Text("Delete", style: TextStyle(color: danger)),
- ),
+ child: ListTile(leading: Icon(Icons.delete), title: Text("Delete")),
),
PopupMenuItem(
onTap: () => showDialog(
@@ -257,9 +153,10 @@ class RoomChat extends HookConsumerWidget {
),
TextButton(
onPressed: () {
+ if (room.metadata == null) return;
client.reportEvent(
ReportRequest(
- roomId: roomId,
+ roomId: room.metadata!.id,
eventId: message.id,
reason: reasonController.text.isEmpty
? null
@@ -292,6 +189,7 @@ class RoomChat extends HookConsumerWidget {
return Scaffold(
appBar: RoomAppbar(
+ room,
isDesktop: isDesktop,
onOpenDrawer: (_) => Scaffold.of(context).openDrawer(),
onOpenMemberList: (thisContext) {
@@ -339,42 +237,18 @@ class RoomChat extends HookConsumerWidget {
chatAnimatedListBuilder: (_, itemBuilder) =>
ChatAnimatedList(
itemBuilder: itemBuilder,
- onEndReached:
- ref.watch(
- SelectedRoomController.provider.select(
- (room) => room?.hasMore == true,
- ),
- )
+ onEndReached: room.hasMore
? notifier.loadOlder
: null,
- onStartReached: () async {
- final room = ref.watch(
- SelectedRoomController.provider,
- );
- return room == null
- ? null
- : await client.markRead(room);
- },
+ onStartReached: () => client.markRead(room),
bottomPadding: 72,
),
composerBuilder: (_) => ChatBox(
- node: composerNode,
- onSend:
- (
- text, {
- required shouldMention,
- required tags,
- }) => notifier.send(
- text,
- tags: tags,
- relationType: relationType.value,
- shouldMention: shouldMention,
- relation: relatedMessage.value,
- ),
relationType: relationType.value,
- relatedMessage: relatedMessage.value,
- onDismiss: () => relatedMessage.value = null,
+ relatedMessage: replyToMessage.value,
+ onDismiss: () => replyToMessage.value = null,
+ room: room,
),
textMessageBuilder:
@@ -385,6 +259,7 @@ class RoomChat extends HookConsumerWidget {
required bool isSentByMe,
MessageGroupStatus? groupStatus,
}) => TextMessageWrapper(
+ room: room,
message,
content: message.text,
groupStatus: groupStatus,
@@ -402,6 +277,7 @@ class RoomChat extends HookConsumerWidget {
MessageGroupStatus? groupStatus,
}) => TextMessageWrapper(
message,
+ room: room,
content: message.text,
groupStatus: groupStatus,
onTapReply: notifier.scrollToMessage,
@@ -433,6 +309,7 @@ class RoomChat extends HookConsumerWidget {
),
child: FlyerChatFileMessage(
topWidget: ReplyWidget(
+ room: room,
message,
onTapReply: notifier.scrollToMessage,
groupStatus: groupStatus,
@@ -442,6 +319,7 @@ class RoomChat extends HookConsumerWidget {
),
),
groupStatus,
+ room,
),
systemMessageBuilder:
@@ -470,7 +348,7 @@ class RoomChat extends HookConsumerWidget {
),
),
),
- resolveUser: (_) async => null,
+ resolveUser: notifier.resolveUser,
chatController: controller,
),
),
@@ -480,11 +358,11 @@ class RoomChat extends HookConsumerWidget {
),
if (memberListOpened.value == true && showMembersByDefault)
- MemberList(),
+ MemberList(room),
],
),
- endDrawer: showMembersByDefault ? null : MemberList(),
+ endDrawer: showMembersByDefault ? null : MemberList(room),
);
}
}
diff --git a/lib/widgets/chat_page/room_menu.dart b/lib/widgets/chat_page/room_menu.dart
index 4405707..2687bc8 100644
--- a/lib/widgets/chat_page/room_menu.dart
+++ b/lib/widgets/chat_page/room_menu.dart
@@ -1,9 +1,7 @@
import "package:fast_immutable_collections/fast_immutable_collections.dart";
import "package:flutter/material.dart";
-import "package:flutter/services.dart";
import "package:flutter_riverpod/flutter_riverpod.dart";
import "package:nexus/controllers/client_controller.dart";
-import "package:nexus/controllers/via_controller.dart";
import "package:nexus/models/room.dart";
class RoomMenu extends ConsumerWidget {
@@ -18,6 +16,13 @@ class RoomMenu extends ConsumerWidget {
return PopupMenuButton(
itemBuilder: (_) => [
+ // PopupMenuItem(
+ // onTap: () async {
+ // final link = await room.matrixToInviteLink();
+ // await Clipboard.setData(ClipboardData(text: link.toString()));
+ // },
+ // child: ListTile(leading: Icon(Icons.link), title: Text("Copy Link")),
+ // ),
PopupMenuItem(
onTap: () async {
await client.markRead(room);
@@ -28,18 +33,6 @@ class RoomMenu extends ConsumerWidget {
title: Text("Mark as Read"),
),
),
- PopupMenuItem(
- onTap: () async {
- final vias = ref.watch(ViaController.provider(room));
-
- await Clipboard.setData(
- ClipboardData(
- text: "matrix:roomid/${room.metadata?.id.substring(1)}$vias)",
- ),
- );
- },
- child: ListTile(leading: Icon(Icons.link), title: Text("Copy Link")),
- ),
PopupMenuItem(
onTap: () => showDialog(
context: context,
diff --git a/lib/widgets/chat_page/sidebar.dart b/lib/widgets/chat_page/sidebar.dart
index f79c38f..4642a58 100644
--- a/lib/widgets/chat_page/sidebar.dart
+++ b/lib/widgets/chat_page/sidebar.dart
@@ -1,15 +1,18 @@
import "package:flutter/material.dart";
+import "package:flutter_hooks/flutter_hooks.dart";
import "package:hooks_riverpod/hooks_riverpod.dart";
+import "package:nexus/controllers/client_controller.dart";
import "package:nexus/controllers/key_controller.dart";
import "package:nexus/controllers/selected_space_controller.dart";
import "package:nexus/controllers/spaces_controller.dart";
+import "package:nexus/helpers/extensions/join_room_with_snackbars.dart";
+import "package:nexus/pages/settings_page.dart";
import "package:nexus/widgets/avatar_or_hash.dart";
-import "package:nexus/widgets/chat_page/join_dialog.dart";
import "package:nexus/widgets/chat_page/room_menu.dart";
+import "package:nexus/widgets/form_text_input.dart";
class Sidebar extends HookConsumerWidget {
- final bool isDesktop;
- const Sidebar({required this.isDesktop, super.key});
+ const Sidebar({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
@@ -88,7 +91,53 @@ class Sidebar extends HookConsumerWidget {
PopupMenuItem(
onTap: () => showDialog(
context: context,
- builder: (_) => JoinDialog(ref),
+ builder: (alertContext) => HookBuilder(
+ builder: (_) {
+ final roomAlias = useTextEditingController();
+ return AlertDialog(
+ title: Text("Join a Room"),
+ content: Column(
+ mainAxisSize: MainAxisSize.min,
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(
+ "Enter the room alias, ID, or a Matrix.to link.",
+ ),
+ SizedBox(height: 12),
+ FormTextInput(
+ required: false,
+ capitalize: true,
+ controller: roomAlias,
+ title: "#room:server",
+ ),
+ ],
+ ),
+ actions: [
+ TextButton(
+ onPressed: Navigator.of(context).pop,
+ child: Text("Cancel"),
+ ),
+ TextButton(
+ onPressed: () async {
+ Navigator.of(alertContext).pop();
+
+ final client = ref.watch(
+ ClientController.provider.notifier,
+ );
+ if (context.mounted) {
+ client.joinRoomWithSnackBars(
+ context,
+ roomAlias.text,
+ ref,
+ );
+ }
+ },
+ child: Text("Join"),
+ ),
+ ],
+ );
+ },
+ ),
),
child: ListTile(
title: Text("Join an existing room (or space)"),
@@ -96,7 +145,7 @@ class Sidebar extends HookConsumerWidget {
),
),
PopupMenuItem(
- onTap: null,
+ onTap: () {},
child: ListTile(
title: Text("Create a new room"),
leading: Icon(Icons.add),
@@ -107,15 +156,17 @@ class Sidebar extends HookConsumerWidget {
),
IconButton(
tooltip: "Explore other rooms",
- onPressed: null,
+ onPressed: () => showDialog(
+ context: context,
+ builder: (context) => AlertDialog(title: Text("To-do")),
+ ),
icon: Icon(Icons.explore),
),
IconButton(
tooltip: "Open settings",
- onPressed: null,
- // () => Navigator.of(
- // context,
- // ).push(MaterialPageRoute(builder: (_) => SettingsPage())),
+ onPressed: () => Navigator.of(
+ context,
+ ).push(MaterialPageRoute(builder: (_) => SettingsPage())),
icon: Icon(Icons.settings),
),
],
@@ -169,12 +220,9 @@ class Sidebar extends HookConsumerWidget {
),
)
.toList(),
- onDestinationSelected: (value) {
- selectedRoomIdNotifier.set(
- selectedSpace.children[value].metadata?.id,
- );
- if (!isDesktop) Navigator.of(context).pop();
- },
+ onDestinationSelected: (value) => selectedRoomIdNotifier.set(
+ selectedSpace.children[value].metadata?.id,
+ ),
),
),
),
diff --git a/lib/widgets/chat_page/user_popover.dart b/lib/widgets/chat_page/user_popover.dart
deleted file mode 100644
index a9a4799..0000000
--- a/lib/widgets/chat_page/user_popover.dart
+++ /dev/null
@@ -1,214 +0,0 @@
-import "package:flutter/material.dart";
-import "package:flutter_hooks/flutter_hooks.dart";
-import "package:flutter_riverpod/flutter_riverpod.dart";
-import "package:intl/intl.dart";
-import "package:nexus/controllers/client_controller.dart";
-import "package:nexus/controllers/client_state_controller.dart";
-import "package:nexus/controllers/power_level_controller.dart";
-import "package:nexus/controllers/profile_controller.dart";
-import "package:nexus/controllers/selected_room_controller.dart";
-import "package:nexus/helpers/extensions/better_when.dart";
-import "package:nexus/models/configs/power_level_config.dart";
-import "package:nexus/models/membership.dart";
-import "package:nexus/models/membership_status.dart";
-import "package:nexus/models/requests/membership_action.dart";
-import "package:nexus/models/requests/set_membership_request.dart";
-import "package:nexus/widgets/avatar_or_hash.dart";
-import "package:nexus/main.dart";
-import "package:nexus/widgets/chat_page/expandable_image.dart";
-import "package:nexus/widgets/form_text_input.dart";
-
-class UserPopover extends ConsumerWidget {
- final Membership member;
- const UserPopover(this.member, {super.key});
-
- @override
- Widget build(BuildContext context, WidgetRef ref) {
- final theme = Theme.of(context);
- final textTheme = theme.textTheme;
- final client = ref.watch(ClientController.provider.notifier);
- final roomId = ref.watch(
- SelectedRoomController.provider.select((room) => room?.metadata?.id),
- );
-
- void showMembershipDialog(MembershipAction action) => showDialog(
- context: context,
- builder: (context) => HookBuilder(
- builder: (context) {
- final actionReasonController = useTextEditingController();
- return AlertDialog(
- title: Text(
- "${toBeginningOfSentenceCase(action.name)} ${member.userId}",
- ),
- content: Column(
- mainAxisSize: MainAxisSize.min,
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text(
- "Are you sure you want to ${action.name} ${member.userId}?",
- ),
- SizedBox(height: 12),
- FormTextInput(
- required: false,
- capitalize: true,
- controller: actionReasonController,
- title: "Reason for ${action.name} (optional)",
- ),
- ],
- ),
- actions: [
- TextButton(
- onPressed: Navigator.of(context).pop,
- child: Text("Cancel"),
- ),
- TextButton(
- onPressed: () {
- Navigator.of(context).pop();
- client
- .setMembership(
- SetMembershipRequest(
- userId: member.userId,
- roomId: roomId!,
- action: action,
- reason: actionReasonController.text,
- ),
- )
- .onError(showError);
- },
- child: Text(toBeginningOfSentenceCase(action.name)),
- ),
- ],
- );
- },
- ),
- );
-
- return Column(
- spacing: 16,
- crossAxisAlignment: CrossAxisAlignment.stretch,
- children: [
- Wrap(
- alignment: WrapAlignment.center,
- spacing: 16,
- runSpacing: 8,
- children: [
- ExpandableImage(
- member.avatarUrl?.toString(),
- child: AvatarOrHash(
- member.avatarUrl,
- member.displayName,
- height: 80,
- ),
- ),
- Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- SelectableText(
- member.displayName,
- style: textTheme.headlineSmall,
- ),
- SelectableText(member.userId, style: textTheme.titleSmall),
- SizedBox(height: 4),
- ref
- .watch(ProfileController.provider(member.userId))
- .betterWhen(
- loading: SizedBox.shrink,
- data: (profile) => Wrap(
- spacing: 4,
- children: [
- for (final pronoun in profile.pronouns.where(
- (pronoun) => pronoun.language == "en",
- ))
- Chip(
- label: Text(pronoun.summary),
- labelStyle: TextStyle(
- color: theme.colorScheme.onPrimary,
- ),
- color: WidgetStatePropertyAll(
- theme.colorScheme.primary,
- ),
- ),
- if (profile.timezone != null)
- Chip(
- label: Text(profile.timezone!),
- labelStyle: TextStyle(
- color: theme.colorScheme.onPrimary,
- ),
- color: WidgetStatePropertyAll(
- theme.colorScheme.primary,
- ),
- ),
- ],
- ),
- ),
- ],
- ),
- ],
- ),
- if (member.userId !=
- ref.watch(ClientStateController.provider)?.userId &&
- roomId != null)
- Wrap(
- spacing: 8,
- runSpacing: 8,
- children: [
- FilledButton.icon(onPressed: null, label: Text("Message")),
-
- if (ref.watch(
- PowerLevelController.provider(
- PowerLevelConfig(
- eventType: "m.room.member",
- action: MembershipAction.kick,
- isStateEvent: true,
- targetUser: member.userId,
- ),
- ),
- ) &&
- member.status == MembershipStatus.join ||
- member.status == MembershipStatus.invite)
- FilledButton.icon(
- onPressed: () => showMembershipDialog(MembershipAction.kick),
- label: Text("Kick"),
- style: ButtonStyle(
- backgroundColor: WidgetStatePropertyAll(
- theme.colorScheme.error,
- ),
- foregroundColor: WidgetStatePropertyAll(
- theme.colorScheme.onError,
- ),
- ),
- ),
- if (ref.watch(
- PowerLevelController.provider(
- PowerLevelConfig(
- eventType: "m.room.member",
- action: MembershipAction.ban,
- isStateEvent: true,
- targetUser: member.userId,
- ),
- ),
- ))
- ElevatedButton.icon(
- onPressed: () => showMembershipDialog(
- member.status == MembershipStatus.ban
- ? MembershipAction.unban
- : MembershipAction.ban,
- ),
- label: Text(
- member.status == MembershipStatus.ban ? "Unban" : "Ban",
- ),
- style: ButtonStyle(
- backgroundColor: WidgetStatePropertyAll(
- theme.colorScheme.errorContainer,
- ),
- foregroundColor: WidgetStatePropertyAll(
- theme.colorScheme.onErrorContainer,
- ),
- ),
- ),
- ],
- ),
- ],
- );
- }
-}
diff --git a/lib/widgets/chat_page/wrappers/message_wrapper.dart b/lib/widgets/chat_page/wrappers/message_wrapper.dart
index 9c70c27..1be6c2b 100644
--- a/lib/widgets/chat_page/wrappers/message_wrapper.dart
+++ b/lib/widgets/chat_page/wrappers/message_wrapper.dart
@@ -1,83 +1,59 @@
import "package:flutter/material.dart";
import "package:flutter_chat_core/flutter_chat_core.dart";
+import "package:nexus/models/room.dart";
import "package:nexus/widgets/chat_page/lazy_loading/message_avatar.dart";
import "package:nexus/widgets/chat_page/lazy_loading/message_displayname.dart";
-import "package:nexus/widgets/chat_page/wrappers/reaction_row.dart";
-import "package:timeago/timeago.dart";
class MessageWrapper extends StatelessWidget {
final Message message;
final Widget child;
+ final Room room;
final MessageGroupStatus? groupStatus;
- const MessageWrapper(this.message, this.child, this.groupStatus, {super.key});
+ const MessageWrapper(
+ this.message,
+ this.child,
+ this.groupStatus,
+ this.room, {
+ super.key,
+ });
@override
- Widget build(BuildContext context) {
- final theme = Theme.of(context);
- final error = message.metadata?["error"];
-
- return ClipRRect(
- borderRadius: BorderRadius.all(Radius.circular(12)),
- child: AnimatedContainer(
- padding: message.metadata?["flashing"] == true
- ? EdgeInsets.all(8)
- : EdgeInsets.all(0),
- color: message.metadata?["flashing"] == true
- ? Theme.of(context).colorScheme.onSurface.withAlpha(50)
- : Colors.transparent,
- duration: Duration(milliseconds: 250),
- child: Row(
- spacing: 8,
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- groupStatus?.isFirst != false
- ? MessageAvatar(message, height: 40)
- : SizedBox(width: 40),
- Expanded(
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- spacing: 4,
- children: [
- if (groupStatus?.isFirst != false)
- Row(
- spacing: 4,
- children: [
- Flexible(
- child: MessageDisplayname(
- message,
- style: theme.textTheme.titleMedium?.copyWith(
- fontWeight: FontWeight.bold,
- ),
- ),
- ),
- if (message.deliveredAt != null &&
- groupStatus?.isFirst != false)
- Tooltip(
- message: message.deliveredAt!.toString(),
- child: Text(
- format(message.deliveredAt!),
- style: theme.textTheme.labelSmall?.copyWith(
- color: Colors.grey,
- ),
- ),
- ),
- ],
+ Widget build(BuildContext context) => ClipRRect(
+ borderRadius: BorderRadius.all(Radius.circular(12)),
+ child: AnimatedContainer(
+ padding: message.metadata?["flashing"] == true
+ ? EdgeInsets.all(8)
+ : EdgeInsets.all(0),
+ color: message.metadata?["flashing"] == true
+ ? Theme.of(context).colorScheme.onSurface.withAlpha(50)
+ : Colors.transparent,
+ duration: Duration(milliseconds: 250),
+ child: Row(
+ spacing: 8,
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ groupStatus?.isFirst != false
+ ? MessageAvatar(message, room, height: 40)
+ : SizedBox(width: 40),
+ Expanded(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ spacing: 4,
+ children: [
+ if (groupStatus?.isFirst != false)
+ MessageDisplayname(
+ message,
+ room,
+ style: Theme.of(context).textTheme.titleMedium?.copyWith(
+ fontWeight: FontWeight.bold,
),
- child,
- if (error != null && error != "not sent")
- Text(
- error,
- style: theme.textTheme.labelSmall?.copyWith(
- color: theme.colorScheme.error,
- ),
- ),
- ReactionRow(message),
- ],
- ),
+ ),
+ child,
+ ],
),
- ],
- ),
+ ),
+ ],
),
- );
- }
+ ),
+ );
}
diff --git a/lib/widgets/chat_page/wrappers/reaction_row.dart b/lib/widgets/chat_page/wrappers/reaction_row.dart
deleted file mode 100644
index 5e8fe86..0000000
--- a/lib/widgets/chat_page/wrappers/reaction_row.dart
+++ /dev/null
@@ -1,116 +0,0 @@
-import "package:cross_cache/cross_cache.dart";
-import "package:fast_immutable_collections/fast_immutable_collections.dart";
-import "package:flutter/material.dart";
-import "package:flutter_chat_core/flutter_chat_core.dart";
-import "package:flutter_hooks/flutter_hooks.dart";
-import "package:flutter_riverpod/flutter_riverpod.dart";
-import "package:nexus/controllers/client_state_controller.dart";
-import "package:nexus/controllers/cross_cache_controller.dart";
-import "package:nexus/controllers/room_chat_controller.dart";
-import "package:nexus/controllers/selected_room_controller.dart";
-import "package:nexus/helpers/extensions/get_headers.dart";
-import "package:nexus/helpers/extensions/mxc_to_https.dart";
-import "package:nexus/main.dart";
-
-class ReactionRow extends ConsumerWidget {
- final Message message;
- const ReactionRow(this.message, {super.key});
-
- @override
- Widget build(BuildContext context, WidgetRef ref) {
- final clientState = ref.watch(ClientStateController.provider);
-
- return Wrap(
- spacing: 4,
- runSpacing: 4,
- children: clientState?.homeserverUrl == null || message.reactions == null
- ? []
- : message.reactions!
- .mapTo(
- (reaction, reactors) => HookBuilder(
- builder: (context) {
- final enabled = useState(true);
- final selected = reactors.contains(clientState!.userId);
- return Tooltip(
- message: reactors.join(", "),
- child: ChoiceChip(
- showCheckmark: false,
- selected: selected,
- label: Row(
- mainAxisSize: MainAxisSize.min,
- spacing: 8,
- children: [
- Flexible(
- child: reaction.startsWith("mxc://")
- ? Image(
- height: 20,
- image: CachedNetworkImage(
- headers: ref.headers,
- Uri.parse(reaction)
- .mxcToHttps(
- clientState.homeserverUrl!,
- )
- .toString(),
- ref.watch(
- CrossCacheController.provider,
- ),
- ),
- )
- : Text(
- reaction,
- overflow: TextOverflow.ellipsis,
- ),
- ),
- Text(
- reactors.length.toString(),
- overflow: TextOverflow.ellipsis,
- ),
- ],
- ),
- onSelected: enabled.value
- ? (value) async {
- enabled.value = false;
- try {
- final roomId = ref.watch(
- SelectedRoomController.provider.select(
- (value) => value?.metadata?.id,
- ),
- );
- if (roomId == null ||
- clientState.userId == null) {
- return;
- }
-
- final controller = ref.watch(
- RoomChatController.provider(
- roomId,
- ).notifier,
- );
-
- if (selected) {
- await controller
- .removeReaction(
- reaction,
- message,
- clientState.userId!,
- )
- .onError(showError);
- } else {
- await controller
- .sendReaction(reaction, message)
- .onError(showError);
- }
- } finally {
- enabled.value = true;
- }
- }
- : null,
- ),
- );
- },
- ),
- )
- .toList(),
- );
- }
-}
diff --git a/lib/widgets/chat_page/wrappers/text_message_wrapper.dart b/lib/widgets/chat_page/wrappers/text_message_wrapper.dart
index 8d7a625..41bc01e 100644
--- a/lib/widgets/chat_page/wrappers/text_message_wrapper.dart
+++ b/lib/widgets/chat_page/wrappers/text_message_wrapper.dart
@@ -1,21 +1,15 @@
-import "package:cross_cache/cross_cache.dart";
import "package:flutter/material.dart";
import "package:flutter_chat_core/flutter_chat_core.dart";
import "package:flutter_link_previewer/flutter_link_previewer.dart";
-import "package:flutter_linkify/flutter_linkify.dart";
-import "package:flutter_riverpod/flutter_riverpod.dart";
-import "package:nexus/controllers/cross_cache_controller.dart";
-import "package:nexus/controllers/url_preview_controller.dart";
-import "package:nexus/helpers/extensions/better_when.dart";
-import "package:nexus/helpers/extensions/get_headers.dart";
-import "package:nexus/helpers/launch_helper.dart";
+import "package:nexus/models/room.dart";
import "package:nexus/widgets/chat_page/html/html.dart";
import "package:nexus/widgets/chat_page/wrappers/message_wrapper.dart";
import "package:nexus/widgets/chat_page/reply_widget.dart";
-class TextMessageWrapper extends ConsumerWidget {
+class TextMessageWrapper extends StatelessWidget {
final Message message;
final String? content;
+ final Room room;
final MessageGroupStatus? groupStatus;
final Future Function(Message oldMessage, Message newMessage)
updateMessage;
@@ -27,6 +21,7 @@ class TextMessageWrapper extends ConsumerWidget {
this.message, {
this.content,
this.onTapReply,
+ required this.room,
required this.updateMessage,
required this.groupStatus,
required this.isSentByMe,
@@ -35,17 +30,11 @@ class TextMessageWrapper extends ConsumerWidget {
});
@override
- Widget build(BuildContext context, WidgetRef ref) {
+ Widget build(BuildContext context) {
final theme = Theme.of(context);
final colorScheme = theme.colorScheme;
final textMessage = message is TextMessage ? message as TextMessage : null;
- final link = textMessage == null
- ? null
- : RegExp(
- r'''https?://[^\s"'<>]+''',
- ).allMatches(textMessage.text).firstOrNull?.group(0);
-
return MessageWrapper(
message,
ClipRRect(
@@ -54,9 +43,7 @@ class TextMessageWrapper extends ConsumerWidget {
padding: EdgeInsets.symmetric(vertical: 8, horizontal: 12),
decoration: BoxDecoration(
color: isSentByMe
- ? (message.id.startsWith("~")
- ? colorScheme.onPrimary
- : colorScheme.primaryContainer)
+ ? colorScheme.primaryContainer
: colorScheme.surfaceContainer,
),
child: Column(
@@ -64,84 +51,65 @@ class TextMessageWrapper extends ConsumerWidget {
children: [
ReplyWidget(
message,
+ room: room,
groupStatus: groupStatus,
onTapReply: onTapReply,
),
if (content != null)
- message.metadata?["format"] == "org.matrix.custom.html"
- ? Html(
- textStyle: message.metadata?["big"] == true
- ? TextStyle(fontSize: 32)
- : null,
- content!.replaceAllMapped(
- RegExp(
- "(]*>.*?<\\/a>)|(\\bhttps?:\\/\\/[^\\s<]+)",
- caseSensitive: false,
- dotAll: true,
- ),
- (m) {
- // If it's already an tag, leave it unchanged
- if (m.group(1) != null) {
- return m.group(1)!;
- }
+ Html(
+ textStyle: message.metadata?["big"] == true
+ ? TextStyle(fontSize: 32)
+ : null,
+ content!
+ .replaceAllMapped(
+ RegExp(
+ "(]*>.*?<\\/a>)|(\\bhttps?:\\/\\/[^\\s<]+)",
+ caseSensitive: false,
+ ),
+ (m) {
+ // If it's already an tag, leave it unchanged
+ if (m.group(1) != null) {
+ return m.group(1)!;
+ }
- // Otherwise, wrap the bare URL
- final url = m.group(2)!;
- return "$url";
- },
- ),
+ // Otherwise, wrap the bare URL
+ final url = m.group(2)!;
+ return "$url";
+ },
)
- : Linkify(
- text: content!,
- options: LinkifyOptions(humanize: false),
- onOpen: (link) => ref
- .watch(LaunchHelper.provider)
- .launchUrl(Uri.parse(link.url)),
- linkStyle: TextStyle(
- color: Theme.of(context).colorScheme.primary,
- ),
- ),
+ .replaceAll("\n", "
"),
+ ),
if (textMessage?.editedAt != null)
Text("(edited)", style: theme.textTheme.labelSmall),
- if (link != null)
- ref
- .watch(UrlPreviewController.provider(link))
- .betterWhen(
- loading: SizedBox.shrink,
- data: (preview) => preview == null
- ? SizedBox.shrink()
- : LinkPreview(
- onTap: (url) => ref
- .watch(LaunchHelper.provider)
- .launchUrl(Uri.parse(url)),
- imageBuilder: (url) => Image(
- image: CachedNetworkImage(
- url,
- ref.watch(CrossCacheController.provider),
- headers: ref.headers,
- ),
- fit: BoxFit.cover,
- errorBuilder: (_, _, _) => SizedBox.shrink(),
- ),
- text: link,
- backgroundColor: isSentByMe
- ? colorScheme.inversePrimary
- : colorScheme.surfaceContainerLow,
- outsidePadding: EdgeInsets.only(top: 4),
- insidePadding: EdgeInsets.symmetric(
- vertical: 8,
- horizontal: 16,
- ),
- linkPreviewData: preview,
- onLinkPreviewDataFetched: (_) => null,
- ),
+ if (textMessage != null)
+ LinkPreview(
+ text: textMessage.text,
+ backgroundColor: isSentByMe
+ ? colorScheme.inversePrimary
+ : colorScheme.surfaceContainerLow,
+ outsidePadding: EdgeInsets.only(top: 4),
+ insidePadding: EdgeInsets.symmetric(
+ vertical: 8,
+ horizontal: 16,
+ ),
+ linkPreviewData: message.metadata?["linkPreviewData"],
+ onLinkPreviewDataFetched: (linkPreviewData) => updateMessage(
+ message,
+ message.copyWith(
+ metadata: {
+ ...(message.metadata ?? {}),
+ "linkPreviewData": linkPreviewData,
+ },
),
+ ),
+ ),
if (extra != null) extra!,
],
),
),
),
groupStatus,
+ room,
);
}
}
diff --git a/linux/CMakeLists.txt b/linux/CMakeLists.txt
index fee47c5..2e0c766 100644
--- a/linux/CMakeLists.txt
+++ b/linux/CMakeLists.txt
@@ -7,7 +7,7 @@ project(runner LANGUAGES CXX)
set(BINARY_NAME "nexus")
# The unique GTK application identifier for this application. See:
# https://wiki.gnome.org/HowDoI/ChooseApplicationID
-set(APPLICATION_ID "nexus.federated.Nexus")
+set(APPLICATION_ID "nexus.federated.nexus")
# Explicitly opt in to modern CMake behaviors to avoid warnings with recent
# versions of CMake.
diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc
index 5485b95..f70fb6e 100644
--- a/linux/flutter/generated_plugin_registrant.cc
+++ b/linux/flutter/generated_plugin_registrant.cc
@@ -11,6 +11,7 @@
#include
#include
#include
+#include
void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) dynamic_system_colors_registrar =
@@ -28,4 +29,7 @@ void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) window_manager_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "WindowManagerPlugin");
window_manager_plugin_register_with_registrar(window_manager_registrar);
+ g_autoptr(FlPluginRegistrar) window_size_registrar =
+ fl_plugin_registry_get_registrar_for_plugin(registry, "WindowSizePlugin");
+ window_size_plugin_register_with_registrar(window_size_registrar);
}
diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake
index 13ef2de..78dcf40 100644
--- a/linux/flutter/generated_plugins.cmake
+++ b/linux/flutter/generated_plugins.cmake
@@ -8,6 +8,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
screen_retriever_linux
url_launcher_linux
window_manager
+ window_size
)
list(APPEND FLUTTER_FFI_PLUGIN_LIST
diff --git a/linux/nexus.federated.Nexus.desktop b/linux/nexus.federated.Nexus.desktop
deleted file mode 100644
index d3fa575..0000000
--- a/linux/nexus.federated.Nexus.desktop
+++ /dev/null
@@ -1,9 +0,0 @@
-[Desktop Entry]
-Name=Nexus
-GenericName=Matrix Client
-Comment=A simple and user-friendly Matrix client
-Exec=nexus
-Icon=nexus
-Terminal=false
-Type=Application
-Categories=Chat;Network;InstantMessaging;
\ No newline at end of file
diff --git a/linux/nix/devshell.nix b/linux/nix/devshell.nix
deleted file mode 100644
index 91ba95a..0000000
--- a/linux/nix/devshell.nix
+++ /dev/null
@@ -1,41 +0,0 @@
-{ pkgs, lib }:
-let
- android = pkgs.androidenv.composeAndroidPackages {
- toolsVersion = "26.1.1";
- platformToolsVersion = "36.0.1";
- buildToolsVersions = [
- "35.0.0"
- "36.0.0"
- ];
- cmakeVersions = [ "3.22.1" ];
- platformVersions = [ "36" ];
- abiVersions = [
- "armeabi-v7a"
- "arm64-v8a"
- ];
- includeNDK = true;
- ndkVersions = [ "28.2.13676358" ];
- };
-in
-pkgs.mkShell {
- packages = with pkgs; [
- go
- git
- jdk17
- flutter
- android.platform-tools
- ];
-
- env = rec {
- LIBCLANG_PATH = lib.makeLibraryPath [ pkgs.libclang ];
- LD_LIBRARY_PATH = "./build/native_assets/linux:${lib.makeLibraryPath [ pkgs.zlib ]}";
- CPATH = lib.makeSearchPath "include" [ pkgs.glibc.dev ];
-
- ANDROID_HOME = "${android.androidsdk}/libexec/android-sdk";
- ANDROID_SDK_ROOT = ANDROID_HOME;
- JAVA_HOME = pkgs.jdk17;
-
- TOOLS = "${ANDROID_HOME}/build-tools/${"36.0.0"}";
- GRADLE_OPTS = "-Dorg.gradle.project.android.aapt2FromMavenOverride=${TOOLS}/aapt2";
- };
-}
diff --git a/linux/nix/pkg/default.nix b/linux/nix/pkg/default.nix
deleted file mode 100644
index adaeb15..0000000
--- a/linux/nix/pkg/default.nix
+++ /dev/null
@@ -1,45 +0,0 @@
-{
- lib,
- callPackage,
- libclang,
- flutter,
- src,
-}:
-
-flutter.buildFlutterApplication {
- pname = "nexus";
- version = "0.1.0";
- inherit src;
-
- preBuild = ''
- cp ${callPackage ./gomuks.nix { inherit src; }}/lib/* .
- packageRunCustom nexus generate source/scripts test
- packageRun build_runner build
- '';
-
- env.LIBCLANG_PATH = lib.makeLibraryPath [ libclang ];
-
- autoPubspecLock = src + "/pubspec.lock";
-
- gitHashes = {
- window_size = "sha256-XelNtp7tpZ91QCEcvewVphNUtgQX7xrp5QP0oFo6DgM=";
- dynamic_system_colors = "sha256-es6rjMK1drkqZBKYUP77yw/q5+0uLwWOEDOXRawy3Dc=";
- flutter_chat_ui = "sha256-4fuag7lRH5cMBFD3fUzj2K541JwXLoz8HF/4OMr3uhk=";
- flutter_link_previewer = "sha256-4fuag7lRH5cMBFD3fUzj2K541JwXLoz8HF/4OMr3uhk=";
- emoji_text_field = "sha256-3TOys09EP2GRo6pUBGPXaqBlE39O2Cmwt42Hs1cTDKo=";
- };
-
- postInstall = ''
- install -D assets/icon.svg $out/share/icons/hicolor/scalable/apps/nexus.svg
- install -Dm755 linux/nexus.federated.Nexus.desktop -t $out/share/applications
- wrapProgram $out/bin/nexus \
- --suffix LD_LIBRARY_PATH : $out/app/nexus/lib
- '';
-
- meta = {
- description = "A simple and user-friendly Matrix client";
- mainProgram = "nexus";
- platforms = lib.platforms.linux;
- maintainers = with lib.maintainers; [ quadradical ];
- };
-}
diff --git a/linux/nix/pkg/gomuks.nix b/linux/nix/pkg/gomuks.nix
deleted file mode 100644
index 1bc92bf..0000000
--- a/linux/nix/pkg/gomuks.nix
+++ /dev/null
@@ -1,31 +0,0 @@
-{
- src,
- buildGoModule,
-}:
-
-buildGoModule (finalAttrs: {
- pname = "gomuks-ffi";
- version = "submodule";
-
- doCheck = false;
-
- src = "${src}/gomuks";
-
- vendorHash = "sha256-zBDfBZqUoHIfZ0AajZEvSBbskjpFB7yIsomt0KYDo7Y=";
-
- buildPhase = ''
- runHook preBuild
-
- go build -buildmode=c-shared -o libgomuks.so -tags goolm,noheic ./pkg/ffi
-
- runHook postBuild
- '';
-
- installPhase = ''
- runHook preInstall
-
- install -Dm0644 libgomuks.so -t $out/lib
-
- runHook postInstall
- '';
-})
diff --git a/linux/runner/my_application.cc b/linux/runner/my_application.cc
index abf5dc5..58cd859 100644
--- a/linux/runner/my_application.cc
+++ b/linux/runner/my_application.cc
@@ -43,7 +43,6 @@ static void my_application_activate(GApplication* application) {
}
}
#endif
- gtk_widget_set_size_request(GTK_WIDGET(window), 250, -1);
if (use_header_bar) {
GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new());
gtk_widget_show(GTK_WIDGET(header_bar));
diff --git a/nix/android.nix b/nix/android.nix
new file mode 100644
index 0000000..f373968
--- /dev/null
+++ b/nix/android.nix
@@ -0,0 +1,20 @@
+{
+ androidenv,
+}:
+androidenv.composeAndroidPackages {
+ toolsVersion = "26.1.1";
+ platformToolsVersion = "36.0.1";
+ buildToolsVersions = [
+ "35.0.0"
+ "36.0.0"
+ ];
+ cmakeVersions = [ "3.22.1" ];
+ platformVersions = [ "36" ];
+ abiVersions = [
+ "armeabi-v7a"
+ "arm64-v8a"
+ ];
+ includeNDK = true;
+ ndkVersions = [ "27.0.12077973" ];
+
+}
diff --git a/pubspec.lock b/pubspec.lock
index 984341b..af73796 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -348,21 +348,11 @@ packages:
dynamic_system_colors:
dependency: "direct main"
description:
- path: "."
- ref: HEAD
- resolved-ref: "3b61760d5e0ac1229eefde5b61247947eede4110"
- url: "https://github.com/hasali19/flutter_dynamic_system_colors"
- source: git
+ name: dynamic_system_colors
+ sha256: "43794e658fa88cbdec9f397dd1afd2eb69b6c9717e99b93b16ba37c3aa3b3a8c"
+ url: "https://pub.dev"
+ source: hosted
version: "1.8.0"
- emoji_text_field:
- dependency: "direct main"
- description:
- path: "."
- ref: HEAD
- resolved-ref: "5f7baaf8a6f059ec3ab8ff0f5d02339b00bf6997"
- url: "https://github.com/Henry-Hiles/emoji_text_field"
- source: git
- version: "1.0.0"
encrypt:
dependency: transitive
description:
@@ -475,10 +465,11 @@ packages:
flutter_chat_ui:
dependency: "direct main"
description:
- name: flutter_chat_ui
- sha256: cfbaac38f429beb33d9cc1ca920ae7ccbadbed282c99335d590d61306d3a3d0f
- url: "https://pub.dev"
- source: hosted
+ path: "packages/flutter_chat_ui"
+ ref: HEAD
+ resolved-ref: "03be67c8c81c8f637672ee03dd8f082d2c223627"
+ url: "https://github.com/Henry-Hiles/flutter_chat_ui"
+ source: git
version: "2.11.1"
flutter_hooks:
dependency: "direct main"
@@ -499,19 +490,12 @@ packages:
flutter_link_previewer:
dependency: "direct main"
description:
- name: flutter_link_previewer
- sha256: "346f345064e65bc8bf739bccf19d6d6ca50f8183ffc52e452afa58c06ee2cbf7"
- url: "https://pub.dev"
- source: hosted
+ path: "packages/flutter_link_previewer"
+ ref: HEAD
+ resolved-ref: "03be67c8c81c8f637672ee03dd8f082d2c223627"
+ url: "https://github.com/Henry-Hiles/flutter_chat_ui"
+ source: git
version: "4.2.0"
- flutter_linkify:
- dependency: "direct main"
- description:
- name: flutter_linkify
- sha256: "74669e06a8f358fee4512b4320c0b80e51cffc496607931de68d28f099254073"
- url: "https://pub.dev"
- source: hosted
- version: "6.0.0"
flutter_lints:
dependency: "direct dev"
description:
@@ -672,7 +656,7 @@ packages:
source: hosted
version: "0.15.6"
http:
- dependency: "direct main"
+ dependency: transitive
description:
name: http
sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412"
@@ -839,14 +823,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.0.2"
- linkify:
- dependency: transitive
- description:
- name: linkify
- sha256: "4139ea77f4651ab9c315b577da2dd108d9aa0bd84b5d03d33323f1970c645832"
- url: "https://pub.dev"
- source: hosted
- version: "5.0.0"
lints:
dependency: transitive
description:
@@ -1380,14 +1356,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.1.0+1"
- timeago:
- dependency: "direct main"
- description:
- name: timeago
- sha256: b05159406a97e1cbb2b9ee4faa9fb096fe0e2dfcd8b08fcd2a00553450d3422e
- url: "https://pub.dev"
- source: hosted
- version: "3.7.1"
typed_data:
dependency: transitive
description:
@@ -1580,6 +1548,15 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.5.1"
+ window_size:
+ dependency: "direct main"
+ description:
+ path: "plugins/window_size"
+ ref: HEAD
+ resolved-ref: eb3964990cf19629c89ff8cb4a37640c7b3d5601
+ url: "https://github.com/google/flutter-desktop-embedding"
+ source: git
+ version: "0.1.0"
xdg_directories:
dependency: transitive
description:
diff --git a/pubspec.yaml b/pubspec.yaml
index dbed5c5..ec69ae9 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,6 +1,6 @@
name: nexus
description: "Yet another Matrix client"
-version: 0.1.0
+version: 1.0.0
publish_to: none
flutter:
@@ -31,17 +31,25 @@ dependencies:
image_picker: ^1.1.2
file_picker: ^10.3.3
path: ^1.9.0
- dynamic_system_colors:
- git:
- url: https://github.com/hasali19/flutter_dynamic_system_colors
+ dynamic_system_colors: ^1.8.0
collection: ^1.19.1
window_manager: ^0.5.1
+ window_size:
+ git:
+ url: https://github.com/google/flutter-desktop-embedding
+ path: plugins/window_size
flutter_chat_core: ^2.0.0
flyer_chat_image_message: ^2.2.2
flyer_chat_system_message: ^2.1.13
flyer_chat_file_message: ^2.3.1
- flutter_chat_ui: ^2.11.1
- flutter_link_previewer: ^4.2.0
+ flutter_chat_ui:
+ git:
+ url: https://github.com/Henry-Hiles/flutter_chat_ui
+ path: packages/flutter_chat_ui
+ flutter_link_previewer:
+ git:
+ url: https://github.com/Henry-Hiles/flutter_chat_ui
+ path: packages/flutter_link_previewer
color_hash: ^1.0.1
flutter_widget_from_html_core: ^0.17.0
flutter_svg: ^2.2.2
@@ -55,12 +63,6 @@ dependencies:
hooks: ^1.0.0
code_assets: ^1.0.0
ffigen: ^20.1.1
- timeago: ^3.7.1
- http: ^1.6.0
- flutter_linkify: ^6.0.0
- emoji_text_field:
- git:
- url: https://github.com/Henry-Hiles/emoji_text_field
dev_dependencies:
build_runner: ^2.4.11
@@ -75,9 +77,8 @@ flutter_launcher_icons:
ios: true
android: true
image_path: assets/icon.png
- adaptive_icon_background: assets/background.png
+ adaptive_icon_background: "#000000"
adaptive_icon_foreground: assets/foreground.png
- adaptive_icon_monochrome: assets/monochrome.png
remove_alpha_ios: true
windows:
generate: true
\ No newline at end of file
diff --git a/scripts/generate.dart b/scripts/generate.dart
index 446a469..b240d98 100644
--- a/scripts/generate.dart
+++ b/scripts/generate.dart
@@ -3,7 +3,26 @@ import "package:ffigen/ffigen.dart";
import "package:path/path.dart";
void main(List args) async {
- final repoDir = Directory.fromUri(Platform.script.resolve("../gomuks"));
+ final repoDir = Directory.fromUri(
+ Platform.script.resolve("../src/gomuks/source"),
+ );
+ if (await repoDir.exists()) await repoDir.delete(recursive: true);
+ await repoDir.create(recursive: true);
+
+ print("Cloning Gomuks repository...");
+ final cloneResult = await Process.run("git", [
+ "clone",
+ "--depth",
+ "1",
+ "https://mau.dev/gomuks/gomuks",
+ repoDir.path,
+ ]);
+
+ if (cloneResult.exitCode != 0) {
+ throw Exception(
+ "Failed to clone Gomuks repository: \n${cloneResult.stderr}",
+ );
+ }
print("Generating FFI Bindings...");
diff --git a/scripts/generate.sh b/scripts/generate.sh
new file mode 100755
index 0000000..6076ab8
--- /dev/null
+++ b/scripts/generate.sh
@@ -0,0 +1,9 @@
+#!/usr/bin/env bash
+pushd "$(dirname "$(readlink -f "$0")")"/.. > /dev/null || exit
+
+mkdir -p build
+touch build/lock
+dart scripts/generate.dart
+rm build/lock
+
+popd > /dev/null || exit
\ No newline at end of file
diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc
index bde1c28..55fb066 100644
--- a/windows/flutter/generated_plugin_registrant.cc
+++ b/windows/flutter/generated_plugin_registrant.cc
@@ -11,6 +11,7 @@
#include
#include
#include
+#include
void RegisterPlugins(flutter::PluginRegistry* registry) {
DynamicColorPluginCApiRegisterWithRegistrar(
@@ -23,4 +24,6 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
WindowManagerPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("WindowManagerPlugin"));
+ WindowSizePluginRegisterWithRegistrar(
+ registry->GetRegistrarForPlugin("WindowSizePlugin"));
}
diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake
index 7b6b425..9333a2f 100644
--- a/windows/flutter/generated_plugins.cmake
+++ b/windows/flutter/generated_plugins.cmake
@@ -8,6 +8,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
screen_retriever_windows
url_launcher_windows
window_manager
+ window_size
)
list(APPEND FLUTTER_FFI_PLUGIN_LIST
diff --git a/windows/runner/Runner.rc b/windows/runner/Runner.rc
index 3583d23..24405eb 100644
--- a/windows/runner/Runner.rc
+++ b/windows/runner/Runner.rc
@@ -89,11 +89,11 @@ BEGIN
BEGIN
BLOCK "040904e4"
BEGIN
- VALUE "CompanyName", "nexus.federated.Nexus" "\0"
+ VALUE "CompanyName", "nexus.federated.nexus" "\0"
VALUE "FileDescription", "nexus" "\0"
VALUE "FileVersion", VERSION_AS_STRING "\0"
VALUE "InternalName", "nexus" "\0"
- VALUE "LegalCopyright", "Copyright (C) 2025 nexus.federated.Nexus. All rights reserved." "\0"
+ VALUE "LegalCopyright", "Copyright (C) 2025 nexus.federated.nexus. All rights reserved." "\0"
VALUE "OriginalFilename", "nexus.exe" "\0"
VALUE "ProductName", "nexus" "\0"
VALUE "ProductVersion", VERSION_AS_STRING "\0"
diff --git a/windows/runner/resources/app_icon.ico b/windows/runner/resources/app_icon.ico
index f8a91f7..e3c83c9 100644
Binary files a/windows/runner/resources/app_icon.ico and b/windows/runner/resources/app_icon.ico differ