invitedRooms,
diff --git a/lib/pages/login_page.dart b/lib/pages/login_page.dart
index bd41d51..b15f6d4 100644
--- a/lib/pages/login_page.dart
+++ b/lib/pages/login_page.dart
@@ -97,7 +97,6 @@ class LoginPage extends HookConsumerWidget {
),
),
IconButton.filled(
- tooltip: "Confirm homeserver choice",
onPressed: isLoading.value
? null
: () => setHomeserver(Uri.tryParse(homeserverUrl.text)),
@@ -144,7 +143,6 @@ class LoginPage extends HookConsumerWidget {
? null
: () => setHomeserver(homeserver.url),
trailing: IconButton(
- tooltip: "Launch homeserver info page",
onPressed: () => launch(homeserver.url),
icon: Icon(Icons.info_outline),
),
diff --git a/lib/pages/settings_page.dart b/lib/pages/settings_page.dart
index 505904c..b348aac 100644
--- a/lib/pages/settings_page.dart
+++ b/lib/pages/settings_page.dart
@@ -1,11 +1,18 @@
import "package:flutter/material.dart";
import "package:flutter_riverpod/flutter_riverpod.dart";
+import "package:nexus/controllers/secure_storage_controller.dart";
class SettingsPage extends ConsumerWidget {
const SettingsPage({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
- return Placeholder();
+ return Scaffold(
+ appBar: AppBar(title: Text("Settings")),
+ body: ElevatedButton(
+ onPressed: ref.watch(SecureStorageController.provider.notifier).clear,
+ child: Text("Log out"),
+ ),
+ );
}
}
diff --git a/lib/widgets/appbar.dart b/lib/widgets/appbar.dart
index 5b14244..00b0e4c 100644
--- a/lib/widgets/appbar.dart
+++ b/lib/widgets/appbar.dart
@@ -49,15 +49,10 @@ class Appbar extends StatelessWidget implements PreferredSizeWidget {
if (!(Platform.isAndroid || Platform.isIOS)) ...[
if (!Platform.isLinux)
IconButton(
- tooltip: "Maximize window",
onPressed: maximize,
icon: const Icon(Icons.fullscreen),
),
- IconButton(
- tooltip: "Close window",
- onPressed: () => exit(0),
- icon: const Icon(Icons.close),
- ),
+ IconButton(onPressed: () => exit(0), icon: const Icon(Icons.close)),
],
],
),
diff --git a/lib/widgets/avatar_or_hash.dart b/lib/widgets/avatar_or_hash.dart
index 8e93b6b..a47bbb5 100644
--- a/lib/widgets/avatar_or_hash.dart
+++ b/lib/widgets/avatar_or_hash.dart
@@ -40,7 +40,7 @@ class AvatarOrHash extends ConsumerWidget {
smallSize: 12,
backgroundColor: Theme.of(context).colorScheme.primary,
child: ClipRRect(
- borderRadius: BorderRadius.all(Radius.circular((height - 8) / 2.5)),
+ borderRadius: BorderRadius.all(Radius.circular(4)),
child: SizedBox(
width: height,
height: height,
diff --git a/lib/widgets/chat_page/chat_box.dart b/lib/widgets/chat_page/chat_box.dart
index b9e7dbb..885ddd9 100644
--- a/lib/widgets/chat_page/chat_box.dart
+++ b/lib/widgets/chat_page/chat_box.dart
@@ -29,13 +29,19 @@ class ChatBox extends HookConsumerWidget {
final theme = Theme.of(context);
final controller = useRef(FlutterTaggerController());
final triggerCharacter = useState("");
- final shouldMention = useState(true);
final query = useState("");
if (relationType == RelationType.edit &&
relatedMessage is TextMessage &&
controller.value.text.isEmpty) {
- controller.value.text = relatedMessage?.metadata?["editSource"] ?? "";
+ final text = (relatedMessage as TextMessage).text;
+ final splitText = relatedMessage?.replyToMessageId == null
+ ? text
+ : text.split("\n\n").sublist(1).join("\n\n");
+ final notEmpty = splitText.isEmpty ? text : splitText;
+ controller.value.text = notEmpty.startsWith("* ")
+ ? notEmpty.substring(2)
+ : notEmpty;
}
void send() {
@@ -44,7 +50,6 @@ class ChatBox extends HookConsumerWidget {
.watch(RoomChatController.provider(room.metadata!.id).notifier)
.send(
controller.value.formattedText,
- shouldMention: shouldMention.value,
relation: relatedMessage,
relationType: relationType,
tags: controller.value.tags,
@@ -86,9 +91,6 @@ class ChatBox extends HookConsumerWidget {
child: Column(
children: [
RelationPreview(
- shouldMention: shouldMention.value,
- toggleShouldMention: () =>
- shouldMention.value = !shouldMention.value,
relatedMessage: relatedMessage,
relationType: relationType,
onDismiss: onDismiss,
@@ -100,27 +102,7 @@ class ChatBox extends HookConsumerWidget {
spacing: 8,
children: [
PopupMenuButton(
- tooltip: "Add media",
- 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),
- ),
- ),
- ],
+ itemBuilder: (context) => [],
icon: Icon(Icons.add),
// enabled: room.canSendDefaultMessages, TODO: Permissions check
),
@@ -147,10 +129,9 @@ class ChatBox extends HookConsumerWidget {
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...",
+ // hintText: room.canSendDefaultMessages
+ // ? "Your message here..."
+ // : "You don't have permission to send messages in this room...",
border: InputBorder.none,
),
controller: controller.value,
@@ -164,7 +145,6 @@ class ChatBox extends HookConsumerWidget {
onPressed: send,
// onPressed: room.canSendDefaultMessages ? send : null,
icon: Icon(Icons.send),
- tooltip: "Send message",
),
],
),
diff --git a/lib/widgets/chat_page/html/code_block.dart b/lib/widgets/chat_page/html/code_block.dart
index 80950ce..fe5b492 100644
--- a/lib/widgets/chat_page/html/code_block.dart
+++ b/lib/widgets/chat_page/html/code_block.dart
@@ -41,8 +41,6 @@ class CodeBlock extends StatelessWidget {
padding: EdgeInsets.all(8),
child: SelectableText(
code,
- minLines: 1,
- maxLines: 99,
style: TextStyle(fontFamily: "monospace"),
),
),
diff --git a/lib/widgets/chat_page/html/html.dart b/lib/widgets/chat_page/html/html.dart
index 1e1ab82..18edf4a 100644
--- a/lib/widgets/chat_page/html/html.dart
+++ b/lib/widgets/chat_page/html/html.dart
@@ -14,13 +14,11 @@ import "package:nexus/widgets/chat_page/html/quoted.dart";
class Html extends ConsumerWidget {
final String html;
- final TextStyle? textStyle;
- const Html(this.html, {this.textStyle, super.key});
+ const Html(this.html, {super.key});
@override
Widget build(BuildContext context, WidgetRef ref) => HtmlWidget(
html,
- textStyle: textStyle,
customWidgetBuilder: (element) {
if (element.attributes.keys.contains("data-mx-spoiler")) {
return InlineCustomWidget(child: SpoilerText(text: element.text));
@@ -32,14 +30,10 @@ class Html extends ConsumerWidget {
return switch (element.localName) {
"code" =>
element.parent?.localName == "pre"
- ? element.outerHtml.contains("
")
- ? Html(
- """${element.outerHtml.replaceAll("
", "\n")}""",
- )
- : CodeBlock(
- element.text,
- lang: element.className.replaceAll("language-", ""),
- )
+ ? CodeBlock(
+ element.text,
+ lang: element.className.replaceAll("language-", ""),
+ )
: null,
"blockquote" => Quoted(Html(element.innerHtml)),
@@ -51,9 +45,8 @@ class Html extends ConsumerWidget {
"img" =>
element.attributes["src"] == null
- ? SizedBox.shrink()
+ ? null
: InlineCustomWidget(
- alignment: PlaceholderAlignment.middle,
child: Image.network(
Uri.parse(element.attributes["src"]!)
.mxcToHttps(
@@ -126,7 +119,9 @@ class Html extends ConsumerWidget {
.mapTo?>(
(key, value) => switch (key) {
"data-mx-color" => MapEntry("color", value),
+
"data-mx-bg-color" => MapEntry("background-color", value),
+
_ => null,
},
)
diff --git a/lib/widgets/chat_page/image_message.dart b/lib/widgets/chat_page/image_message.dart
deleted file mode 100644
index 103fdd2..0000000
--- a/lib/widgets/chat_page/image_message.dart
+++ /dev/null
@@ -1,58 +0,0 @@
-import "dart:math";
-import "package:cross_cache/cross_cache.dart";
-import "package:flutter/material.dart";
-import "package:flutter_chat_core/flutter_chat_core.dart";
-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";
-
-class ExpandableImageMessage extends ConsumerWidget {
- final ImageMessage message;
- final int index;
-
- const ExpandableImageMessage(this.message, {required this.index, super.key});
-
- @override
- 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,
- ref.watch(CrossCacheController.provider),
- headers: ref.headers,
- ),
- errorBuilder: (context, error, stackTrace) => Center(
- child: Text(
- "Image Failed to Load",
- style: TextStyle(color: Theme.of(context).colorScheme.error),
- ),
- ),
- message: message,
- index: index,
- ),
- );
-}
diff --git a/lib/widgets/chat_page/member_list.dart b/lib/widgets/chat_page/member_list.dart
index 24d22e4..5e1f3bf 100644
--- a/lib/widgets/chat_page/member_list.dart
+++ b/lib/widgets/chat_page/member_list.dart
@@ -1,6 +1,7 @@
import "package:flutter/material.dart";
import "package:hooks_riverpod/hooks_riverpod.dart";
import "package:nexus/controllers/members_controller.dart";
+import "package:nexus/helpers/extensions/better_when.dart";
import "package:nexus/models/room.dart";
import "package:nexus/widgets/avatar_or_hash.dart";
@@ -9,49 +10,49 @@ class MemberList extends ConsumerWidget {
const MemberList(this.room, {super.key});
@override
- Widget build(BuildContext context, WidgetRef ref) {
- final members = ref.watch(MembersController.provider(room));
- return Drawer(
- shape: Border(),
- child: ListView(
- children: [
- AppBar(
- scrolledUnderElevation: 0,
- leading: Icon(Icons.people),
- title: Text("Members (${members.length})"),
- actionsPadding: EdgeInsets.only(right: 4),
- actions: [
- if (Scaffold.of(context).hasEndDrawer)
- IconButton(
- onPressed: Scaffold.of(context).closeEndDrawer,
- icon: Icon(Icons.close),
- tooltip: "Close member list",
+ Widget build(BuildContext context, WidgetRef ref) => Drawer(
+ shape: Border(),
+ child: ref
+ .watch(MembersController.provider(room))
+ .betterWhen(
+ data: (members) => ListView(
+ children: [
+ AppBar(
+ scrolledUnderElevation: 0,
+ leading: Icon(Icons.people),
+ title: Text("Members (${members.length})"),
+ actionsPadding: EdgeInsets.only(right: 4),
+ actions: [
+ if (Scaffold.of(context).hasEndDrawer)
+ IconButton(
+ onPressed: Scaffold.of(context).closeEndDrawer,
+ icon: Icon(Icons.close),
+ ),
+ ],
+ ),
+ ...members.map(
+ (member) => ListTile(
+ onTap: () => showDialog(
+ context: context,
+ builder: (context) =>
+ Dialog(child: Text("TODO: Open member popover")),
+ ),
+ leading: AvatarOrHash(
+ Uri.tryParse(member.content["avatar_url"] ?? ""),
+ member.content["displayname"].toString(),
+ ),
+ title: Text(
+ member.content["displayname"].toString(),
+ overflow: TextOverflow.ellipsis,
+ ),
+ subtitle: Text(
+ member.authorId,
+ overflow: TextOverflow.ellipsis,
+ ),
),
+ ),
],
),
- ...members.map(
- (member) => ListTile(
- onTap: () => showDialog(
- context: context,
- builder: (context) =>
- Dialog(child: Text("TODO: Open member popover")),
- ),
- leading: AvatarOrHash(
- Uri.tryParse(member.content["avatar_url"] ?? ""),
- member.content["displayname"].toString(),
- ),
- title: Text(
- member.content["displayname"].toString(),
- overflow: TextOverflow.ellipsis,
- ),
- subtitle: Text(
- member.stateKey ?? "Unknown User",
- overflow: TextOverflow.ellipsis,
- ),
- ),
- ),
- ],
- ),
- );
- }
+ ),
+ );
}
diff --git a/lib/widgets/chat_page/mention_overlay.dart b/lib/widgets/chat_page/mention_overlay.dart
index 9858574..b2f2d9d 100644
--- a/lib/widgets/chat_page/mention_overlay.dart
+++ b/lib/widgets/chat_page/mention_overlay.dart
@@ -2,6 +2,7 @@ import "package:flutter/material.dart";
import "package:hooks_riverpod/hooks_riverpod.dart";
import "package:nexus/controllers/members_controller.dart";
import "package:nexus/controllers/rooms_controller.dart";
+import "package:nexus/helpers/extensions/better_when.dart";
import "package:nexus/models/room.dart";
import "package:nexus/widgets/avatar_or_hash.dart";
import "package:nexus/widgets/loading.dart";
@@ -31,55 +32,51 @@ class MentionOverlay extends ConsumerWidget {
color: Theme.of(context).colorScheme.surfaceContainerHigh,
padding: EdgeInsets.all(8),
child: switch (triggerCharacter) {
- "@" => Consumer(
- builder: (_, ref, _) {
- final members = ref.watch(MembersController.provider(room));
- return ListView(
- children:
- (query.isEmpty
- ? members
- : members.where(
- (member) =>
- member.stateKey?.toLowerCase().contains(
- query.toLowerCase(),
- ) ==
- true ||
- (member.content["displayname"] as String?)
- ?.toLowerCase()
- .contains(query.toLowerCase()) ==
- true,
- ))
- .map(
- (member) => ListTile(
- leading: AvatarOrHash(
- Uri.tryParse(
- member.content["avatar_url"] ?? "",
- ),
- member.content["displayname"] ?? "",
- ),
- title: Text(
- member.content["displayname"] as String? ??
- member.stateKey ??
- "Unknown User",
- ),
- subtitle: member.stateKey != null
- ? Text(member.stateKey!)
- : null,
- onTap: () => addTag(
- id: "[@${member.content["displayname"]}](https://matrix.to/#/${member.stateKey})",
- name:
- member.stateKey
- ?.substring(1)
+ "@" =>
+ ref
+ .watch(MembersController.provider(room))
+ .betterWhen(
+ data: (members) => ListView(
+ children:
+ (query.isEmpty
+ ? members
+ : members.where(
+ (member) =>
+ member.authorId
+ .toLowerCase()
+ .contains(query.toLowerCase()) ||
+ (member.content["displayname"]
+ as String?)
+ ?.toLowerCase()
+ .contains(
+ query.toLowerCase(),
+ ) ==
+ true,
+ ))
+ .map(
+ (member) => ListTile(
+ leading: AvatarOrHash(
+ Uri.tryParse(
+ member.content["avatar_url"] ?? "",
+ ),
+ member.content["displayname"] ?? "",
+ ),
+ title: Text(
+ member.content["displayname"] as String? ??
+ member.authorId,
+ ),
+ onTap: () => addTag(
+ id: "[@${member.content["displayname"]}](https://matrix.to/#/${member.authorId})",
+ name: member.authorId
+ .substring(1)
.split(":")
- .first ??
- "Unknown User",
- ),
- ),
- )
- .toList(),
- );
- },
- ),
+ .first,
+ ),
+ ),
+ )
+ .toList(),
+ ),
+ ),
"#" => ListView(
children:
(query.isEmpty
diff --git a/lib/widgets/chat_page/message_wrapper.dart b/lib/widgets/chat_page/message_wrapper.dart
deleted file mode 100644
index da53be0..0000000
--- a/lib/widgets/chat_page/message_wrapper.dart
+++ /dev/null
@@ -1,54 +0,0 @@
-import "package:flutter/material.dart";
-import "package:flutter_chat_core/flutter_chat_core.dart";
-import "package:nexus/widgets/avatar_or_hash.dart";
-
-class MessageWrapper extends StatelessWidget {
- final Message message;
- final Widget child;
- final MessageGroupStatus? groupStatus;
- const MessageWrapper(this.message, this.child, this.groupStatus, {super.key});
-
- @override
- 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
- ? AvatarOrHash(
- Uri.parse(message.metadata?["avatarUrl"] ?? ""),
- height: 40,
- message.metadata?["displayName"] ?? "",
- )
- : SizedBox(width: 40),
- Expanded(
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- spacing: 4,
- children: [
- if (groupStatus?.isFirst != false)
- Text(
- message.metadata?["displayName"] ?? message.authorId,
- overflow: TextOverflow.ellipsis,
- style: Theme.of(context).textTheme.titleMedium?.copyWith(
- fontWeight: FontWeight.bold,
- ),
- ),
- child,
- ],
- ),
- ),
- ],
- ),
- ),
- );
-}
diff --git a/lib/widgets/chat_page/relation_preview.dart b/lib/widgets/chat_page/relation_preview.dart
index 7aa3ae8..9918b35 100644
--- a/lib/widgets/chat_page/relation_preview.dart
+++ b/lib/widgets/chat_page/relation_preview.dart
@@ -2,20 +2,15 @@ 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/widgets/avatar_or_hash.dart";
class RelationPreview extends ConsumerWidget {
final Message? relatedMessage;
final RelationType relationType;
final VoidCallback onDismiss;
- final bool shouldMention;
- final VoidCallback toggleShouldMention;
const RelationPreview({
required this.relatedMessage,
required this.relationType,
required this.onDismiss,
- required this.shouldMention,
- required this.toggleShouldMention,
super.key,
});
@@ -36,11 +31,18 @@ class RelationPreview extends ConsumerWidget {
"Editing message:",
style: TextStyle(fontWeight: FontWeight.bold),
),
- AvatarOrHash(
- Uri.tryParse(relatedMessage?.metadata?["avatarUrl"] ?? ""),
- relatedMessage?.metadata?["displayName"]?.toString() ?? "",
- height: 16,
- ),
+ // AvatarOrHash(
+ // ref
+ // .watch(
+ // AvatarController.provider(
+ // relatedMessage!.metadata!["avatarUrl"],
+ // ),
+ // )
+ // .whenOrNull(data: (data) => data),
+ // relatedMessage!.metadata!["displayName"].toString(),
+ // headers: room.client.headers,
+ // height: 16,
+ // ),
Text(
relatedMessage!.metadata?["displayName"] ??
relatedMessage!.authorId,
@@ -50,28 +52,16 @@ class RelationPreview extends ConsumerWidget {
),
Expanded(
child: Text(
- relatedMessage?.metadata?["body"] ??
- relatedMessage?.metadata?["eventType"],
+ (relatedMessage is TextMessage)
+ ? (relatedMessage as TextMessage).text
+ : relatedMessage?.metadata?["body"] ??
+ relatedMessage?.metadata?["eventType"],
overflow: TextOverflow.ellipsis,
style: theme.textTheme.labelMedium,
maxLines: 1,
),
),
-
- if (relationType == RelationType.reply)
- TextButton(
- onPressed: toggleShouldMention,
- child: Text(
- shouldMention ? "@On" : "@Off",
- style: TextStyle(
- fontWeight: FontWeight.w900,
- color: shouldMention ? null : Theme.of(context).disabledColor,
- ),
- ),
- ),
IconButton(
- tooltip:
- "Cancel ${relationType == RelationType.edit ? "edit" : "reply"}",
onPressed: onDismiss,
icon: Icon(Icons.close),
iconSize: 20,
diff --git a/lib/widgets/chat_page/reply_widget.dart b/lib/widgets/chat_page/reply_widget.dart
deleted file mode 100644
index cd30acc..0000000
--- a/lib/widgets/chat_page/reply_widget.dart
+++ /dev/null
@@ -1,146 +0,0 @@
-import "dart:math";
-import "package:flutter/material.dart";
-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/helpers/extensions/better_when.dart";
-import "package:nexus/models/message_config.dart";
-import "package:nexus/models/requests/get_event_request.dart";
-import "package:nexus/models/room.dart";
-import "package:nexus/widgets/avatar_or_hash.dart";
-import "package:nexus/widgets/chat_page/html/quoted.dart";
-
-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,
- super.key,
- });
-
- @override
- 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();
- }
-
- final smallerText =
- message is TextMessage &&
- replyMessage.metadata?["body"] != null
- ? replyMessage.metadata!["body"].substring(
- 0,
- min(
- max(
- max(
- (message as TextMessage)
- .text
- .length -
- (replyMessage
- .metadata?["displayName"]
- as String)
- .length -
- 5,
- message
- .metadata?["displayName"]
- .length,
- ),
- 5,
- ),
- replyMessage.metadata!["body"].length,
- ),
- )
- : null;
- final replyText =
- (smallerText == null ||
- smallerText.length ==
- replyMessage
- .metadata!["body"]
- .length)
- ? replyMessage.metadata!["body"]
- : "$smallerText...";
-
- return InkWell(
- onTap: () => onTapReply?.call(replyMessage),
- child: Row(
- mainAxisSize: MainAxisSize.min,
- spacing: 8,
- children: [
- AvatarOrHash(
- Uri.tryParse(
- replyMessage.metadata?["avatarUrl"] ??
- "",
- ),
- replyMessage.metadata?["displayName"] ??
- "",
- height: 16,
- ),
- Flexible(
- child: Text(
- replyMessage
- .metadata?["displayName"] ??
- replyMessage.authorId,
- style: Theme.of(context)
- .textTheme
- .labelMedium
- ?.copyWith(
- fontWeight: FontWeight.bold,
- ),
- overflow: TextOverflow.ellipsis,
- ),
- ),
- Flexible(
- child: Text(
- replyText,
- 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 436bcb9..21aa4ae 100644
--- a/lib/widgets/chat_page/room_appbar.dart
+++ b/lib/widgets/chat_page/room_appbar.dart
@@ -52,14 +52,9 @@ class RoomAppbar extends StatelessWidget implements PreferredSizeWidget {
],
),
actions: [
- IconButton(
- onPressed: null,
- icon: Icon(Icons.push_pin),
- tooltip: "Open pinned messages",
- ),
+ IconButton(onPressed: () {}, icon: Icon(Icons.push_pin)),
IconButton(
onPressed: () => onOpenMemberList(context),
- tooltip: "Open member list",
icon: Icon(Icons.people),
),
RoomMenu(room),
diff --git a/lib/widgets/chat_page/room_chat.dart b/lib/widgets/chat_page/room_chat.dart
index 839109f..6adc013 100644
--- a/lib/widgets/chat_page/room_chat.dart
+++ b/lib/widgets/chat_page/room_chat.dart
@@ -1,25 +1,29 @@
+import "package:cross_cache/cross_cache.dart";
import "package:flutter/material.dart";
import "package:flutter_chat_core/flutter_chat_core.dart";
import "package:flutter_chat_ui/flutter_chat_ui.dart";
import "package:flutter_hooks/flutter_hooks.dart";
+import "package:flutter_link_previewer/flutter_link_previewer.dart";
import "package:flyer_chat_file_message/flyer_chat_file_message.dart";
+import "package:flyer_chat_image_message/flyer_chat_image_message.dart";
import "package:flyer_chat_system_message/flyer_chat_system_message.dart";
+import "package:flyer_chat_text_message/flyer_chat_text_message.dart";
import "package:hooks_riverpod/hooks_riverpod.dart";
import "package:nexus/controllers/client_controller.dart";
import "package:nexus/controllers/client_state_controller.dart";
+import "package:nexus/controllers/cross_cache_controller.dart";
import "package:nexus/controllers/selected_room_controller.dart";
import "package:nexus/controllers/room_chat_controller.dart";
import "package:nexus/helpers/extensions/better_when.dart";
+import "package:nexus/helpers/extensions/get_headers.dart";
import "package:nexus/helpers/extensions/show_context_menu.dart";
import "package:nexus/models/relation_type.dart";
import "package:nexus/models/requests/report_request.dart";
import "package:nexus/widgets/chat_page/chat_box.dart";
-import "package:nexus/widgets/chat_page/image_message.dart";
+import "package:nexus/widgets/chat_page/html/html.dart";
import "package:nexus/widgets/chat_page/member_list.dart";
-import "package:nexus/widgets/chat_page/message_wrapper.dart";
import "package:nexus/widgets/chat_page/room_appbar.dart";
-import "package:nexus/widgets/chat_page/text_message_wrapper.dart";
-import "package:nexus/widgets/chat_page/reply_widget.dart";
+import "package:nexus/widgets/chat_page/top_widget.dart";
import "package:nexus/widgets/form_text_input.dart";
import "package:nexus/widgets/loading.dart";
// import "package:dynamic_polls/dynamic_polls.dart";
@@ -181,13 +185,6 @@ class RoomChat extends HookConsumerWidget {
];
}
- final chatTheme = ChatTheme.fromThemeData(theme).copyWith(
- colors: ChatColors.fromThemeData(theme).copyWith(
- primary: theme.colorScheme.primaryContainer,
- onPrimary: theme.colorScheme.onPrimaryContainer,
- ),
- );
-
return Scaffold(
appBar: RoomAppbar(
room,
@@ -209,7 +206,12 @@ class RoomChat extends HookConsumerWidget {
.betterWhen(
data: (controller) => Chat(
currentUserId: userId,
- theme: chatTheme,
+ theme: ChatTheme.fromThemeData(theme).copyWith(
+ colors: ChatColors.fromThemeData(theme).copyWith(
+ primary: theme.colorScheme.primaryContainer,
+ onPrimary: theme.colorScheme.onPrimaryContainer,
+ ),
+ ),
onMessageSecondaryTap:
(
context,
@@ -232,9 +234,36 @@ class RoomChat extends HookConsumerWidget {
globalPosition: details.globalPosition,
children: getMessageOptions(message),
),
+ onMessageTap:
+ (
+ context,
+ message, {
+ required details,
+ required index,
+ }) {
+ if (message is ImageMessage) {
+ showDialog(
+ context: context,
+ builder: (_) => Dialog(
+ backgroundColor: Colors.transparent,
+ insetPadding: EdgeInsets.all(64),
+ child: InteractiveViewer(
+ child: Image(
+ image: CachedNetworkImage(
+ message.source,
+ ref.watch(
+ CrossCacheController.provider,
+ ),
+ headers: ref.headers,
+ ),
+ ),
+ ),
+ ),
+ );
+ }
+ },
builders: Builders(
loadMoreBuilder: (_) => Loading(),
-
chatAnimatedListBuilder: (_, itemBuilder) =>
ChatAnimatedList(
itemBuilder: itemBuilder,
@@ -244,7 +273,6 @@ class RoomChat extends HookConsumerWidget {
onStartReached: () => client.markRead(room),
bottomPadding: 72,
),
-
composerBuilder: (_) => ChatBox(
relationType: relationType.value,
relatedMessage: replyToMessage.value,
@@ -252,6 +280,104 @@ class RoomChat extends HookConsumerWidget {
room: room,
),
+ // TODO: Polls
+ // customMessageBuilder:
+ // (
+ // context,
+ // message,
+ // index, {
+ // required bool isSentByMe,
+ // MessageGroupStatus? groupStatus,
+ // }) {
+ // final poll =
+ // message.metadata?["poll"]
+ // as PollStartContent;
+ // final responses =
+ // (message.metadata?["responses"]
+ // as Map<
+ // String,
+ // Set
+ // >)
+ // .values
+ // .expand((set) => set)
+ // .fold({}, (
+ // acc,
+ // value,
+ // ) {
+ // acc[value] =
+ // (acc[value] ?? 0) + 1;
+ // return acc;
+ // });
+
+ // return Column(
+ // crossAxisAlignment:
+ // CrossAxisAlignment.start,
+ // spacing: 4,
+ // children: [
+ // TopWidget(
+ // message,
+ // headers: room
+ // .roomData
+ // .client
+ // .headers,
+ // groupStatus: groupStatus,
+ // ),
+
+ // DynamicPolls(
+ // startDate: DateTime.now(),
+ // endDate: DateTime.now(),
+ // private:
+ // poll.kind ==
+ // PollKind.undisclosed,
+ // allowReselection: true,
+ // backgroundDecoration:
+ // BoxDecoration(
+ // borderRadius:
+ // BorderRadius.all(
+ // Radius.circular(16),
+ // ),
+ // border: Border.all(
+ // color: theme
+ // .colorScheme
+ // .primaryContainer,
+ // width: 4,
+ // ),
+ // ),
+ // allStyle: Styles(
+ // titleStyle: TitleStyle(
+ // style: theme
+ // .textTheme
+ // .headlineSmall,
+ // ),
+ // optionStyle: OptionStyle(
+ // fillColor: theme
+ // .colorScheme
+ // .primaryContainer,
+ // selectedBorderColor: theme
+ // .colorScheme
+ // .primary,
+ // borderColor: theme
+ // .colorScheme
+ // .primary,
+ // unselectedBorderColor:
+ // Colors.transparent,
+ // textSelectColor: theme
+ // .colorScheme
+ // .primary,
+ // ),
+ // ),
+ // onOptionSelected:
+ // (int index) {},
+ // title: poll.question.mText,
+ // options: poll.answers
+ // .map(
+ // (option) => option.mText,
+ // )
+ // .toList(),
+ // ),
+ // ],
+ // );
+ // },
textMessageBuilder:
(
context,
@@ -259,37 +385,98 @@ class RoomChat extends HookConsumerWidget {
index, {
required bool isSentByMe,
MessageGroupStatus? groupStatus,
- }) => TextMessageWrapper(
- room: room,
- message,
- content: message.text,
- groupStatus: groupStatus,
- onTapReply: notifier.scrollToMessage,
- updateMessage: controller.updateMessage,
- isSentByMe: isSentByMe,
- ),
+ }) => FlyerChatTextMessage(
+ customWidget: Column(
+ crossAxisAlignment:
+ CrossAxisAlignment.start,
+ children: [
+ Html(
+ (message.metadata?["formatted"]
+ as String)
+ .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";
+ },
+ )
+ .replaceAll("\n", "
"),
+ ),
+ if (message.editedAt != null)
+ Text(
+ "(edited)",
+ style: theme.textTheme.labelSmall,
+ ),
+ ],
+ ),
+ topWidget: TopWidget(
+ message,
+ groupStatus: groupStatus,
+ ),
+ message: message,
+ showTime: true,
+ index: index,
+ ),
+ linkPreviewBuilder: (_, message, isSentByMe) =>
+ LinkPreview(
+ text: message.text,
+ backgroundColor: isSentByMe
+ ? theme.colorScheme.inversePrimary
+ : theme.colorScheme.surfaceContainerLow,
+ insidePadding: EdgeInsets.symmetric(
+ vertical: 8,
+ horizontal: 16,
+ ),
+ linkPreviewData: message.linkPreviewData,
+ onLinkPreviewDataFetched: (linkPreviewData) =>
+ notifier.updateMessage(
+ message,
+ message.copyWith(
+ linkPreviewData: linkPreviewData,
+ ),
+ ),
+ ),
imageMessageBuilder:
(
- context,
+ _,
message,
index, {
required bool isSentByMe,
MessageGroupStatus? groupStatus,
- }) => TextMessageWrapper(
- message,
- room: room,
- content: message.text,
- groupStatus: groupStatus,
- onTapReply: notifier.scrollToMessage,
- updateMessage: controller.updateMessage,
- isSentByMe: isSentByMe,
- extra: ExpandableImageMessage(
+ }) => FlyerChatImageMessage(
+ topWidget: TopWidget(
message,
- index: index,
+ groupStatus: groupStatus,
+ alwaysShow: true,
),
+ customImageProvider: CachedNetworkImage(
+ message.source,
+ ref.watch(CrossCacheController.provider),
+ headers: ref.headers,
+ ),
+ errorBuilder: (context, error, stackTrace) =>
+ Center(
+ child: Text(
+ "Image Failed to Load",
+ style: TextStyle(
+ color: Theme.of(
+ context,
+ ).colorScheme.error,
+ ),
+ ),
+ ),
+ message: message,
+ index: index,
),
-
fileMessageBuilder:
(
_,
@@ -297,31 +484,22 @@ class RoomChat extends HookConsumerWidget {
index, {
required bool isSentByMe,
MessageGroupStatus? groupStatus,
- }) => MessageWrapper(
- message,
- InkWell(
- onTap: () => showDialog(
- context: context,
- builder: (_) => Dialog(
- child: Text(
- "TODO: Download Attachments",
- ),
- ),
- ),
- child: FlyerChatFileMessage(
- topWidget: ReplyWidget(
- room: room,
- message,
- onTapReply: notifier.scrollToMessage,
- groupStatus: groupStatus,
- ),
- message: message,
- index: index,
+ }) => InkWell(
+ onTap: () => showDialog(
+ context: context,
+ builder: (_) => Dialog(
+ child: Text("TODO: Download Attachments"),
),
),
- groupStatus,
+ child: FlyerChatFileMessage(
+ topWidget: TopWidget(
+ message,
+ groupStatus: groupStatus,
+ ),
+ message: message,
+ index: index,
+ ),
),
-
systemMessageBuilder:
(
_,
@@ -333,7 +511,6 @@ class RoomChat extends HookConsumerWidget {
message: message,
index: index,
),
-
unsupportedMessageBuilder:
(
_,
diff --git a/lib/widgets/chat_page/sidebar.dart b/lib/widgets/chat_page/sidebar.dart
index 4642a58..341dd60 100644
--- a/lib/widgets/chat_page/sidebar.dart
+++ b/lib/widgets/chat_page/sidebar.dart
@@ -155,7 +155,6 @@ class Sidebar extends HookConsumerWidget {
icon: Icon(Icons.add),
),
IconButton(
- tooltip: "Explore other rooms",
onPressed: () => showDialog(
context: context,
builder: (context) => AlertDialog(title: Text("To-do")),
@@ -163,7 +162,6 @@ class Sidebar extends HookConsumerWidget {
icon: Icon(Icons.explore),
),
IconButton(
- tooltip: "Open settings",
onPressed: () => Navigator.of(
context,
).push(MaterialPageRoute(builder: (_) => SettingsPage())),
diff --git a/lib/widgets/chat_page/text_message_wrapper.dart b/lib/widgets/chat_page/text_message_wrapper.dart
deleted file mode 100644
index 9734a34..0000000
--- a/lib/widgets/chat_page/text_message_wrapper.dart
+++ /dev/null
@@ -1,114 +0,0 @@
-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:nexus/models/room.dart";
-import "package:nexus/widgets/chat_page/html/html.dart";
-import "package:nexus/widgets/chat_page/message_wrapper.dart";
-import "package:nexus/widgets/chat_page/reply_widget.dart";
-
-class TextMessageWrapper extends StatelessWidget {
- final Message message;
- final String? content;
- final Room room;
- final MessageGroupStatus? groupStatus;
- final Future Function(Message oldMessage, Message newMessage)
- updateMessage;
- final bool isSentByMe;
- final Widget? extra;
- final OnTapReply onTapReply;
-
- const TextMessageWrapper(
- this.message, {
- this.content,
- this.onTapReply,
- required this.room,
- required this.updateMessage,
- required this.groupStatus,
- required this.isSentByMe,
- this.extra,
- super.key,
- });
-
- @override
- Widget build(BuildContext context) {
- final theme = Theme.of(context);
- final colorScheme = theme.colorScheme;
- final textMessage = message is TextMessage ? message as TextMessage : null;
-
- return MessageWrapper(
- message,
- ClipRRect(
- borderRadius: BorderRadius.all(Radius.circular(8)),
- child: Container(
- padding: EdgeInsets.symmetric(vertical: 8, horizontal: 12),
- decoration: BoxDecoration(
- color: isSentByMe
- ? colorScheme.primaryContainer
- : colorScheme.surfaceContainer,
- ),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- ReplyWidget(
- message,
- room: room,
- groupStatus: groupStatus,
- onTapReply: onTapReply,
- ),
- if (content != null)
- 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";
- },
- )
- .replaceAll("\n", "
"),
- ),
- if (textMessage?.editedAt != null)
- Text("(edited)", style: theme.textTheme.labelSmall),
- 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,
- );
- }
-}
diff --git a/lib/widgets/chat_page/top_widget.dart b/lib/widgets/chat_page/top_widget.dart
new file mode 100644
index 0000000..cfba6fc
--- /dev/null
+++ b/lib/widgets/chat_page/top_widget.dart
@@ -0,0 +1,124 @@
+import "dart:math";
+import "package:flutter/material.dart";
+import "package:flutter_chat_core/flutter_chat_core.dart";
+import "package:flutter_riverpod/flutter_riverpod.dart";
+import "package:nexus/widgets/avatar_or_hash.dart";
+import "package:nexus/widgets/chat_page/html/quoted.dart";
+
+class TopWidget extends ConsumerWidget {
+ final Message message;
+ final bool alwaysShow;
+ final MessageGroupStatus? groupStatus;
+ const TopWidget(
+ this.message, {
+ required this.groupStatus,
+ this.alwaysShow = false,
+ super.key,
+ });
+
+ @override
+ Widget build(BuildContext context, WidgetRef ref) => Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Builder(
+ builder: (_) {
+ final replyMessage = message.metadata?["reply"] as TextMessage?;
+
+ if (replyMessage == null) return SizedBox.shrink();
+ final smallerText = message is TextMessage
+ ? replyMessage.text.substring(
+ 0,
+ min(
+ max(
+ max(
+ (message as TextMessage).text.length - 20,
+ message.metadata?["displayName"].length,
+ ),
+ 5,
+ ),
+ replyMessage.text.length,
+ ),
+ )
+ : null;
+ final replyText =
+ (smallerText == null ||
+ smallerText.length == replyMessage.text.length)
+ ? replyMessage.text
+ : "$smallerText...";
+
+ return Padding(
+ padding: EdgeInsets.only(bottom: 12),
+ child: InkWell(
+ onTap: () => showDialog(
+ context: context,
+ builder: (_) => Dialog(
+ child: Text("TODO: Scroll to original message"),
+ ), // TODO
+ ),
+ child: Quoted(
+ Row(
+ mainAxisSize: MainAxisSize.min,
+ spacing: 8,
+ children: [
+ AvatarOrHash(
+ Uri.tryParse(replyMessage.metadata?["avatarUrl"] ?? ""),
+ replyMessage.metadata?["displayName"] ?? "",
+ height: 16,
+ ),
+ Flexible(
+ child: Text(
+ replyMessage.metadata?["displayName"] ??
+ replyMessage.authorId,
+ style: Theme.of(context).textTheme.labelMedium
+ ?.copyWith(fontWeight: FontWeight.bold),
+ overflow: TextOverflow.ellipsis,
+ ),
+ ),
+ Flexible(
+ child: Text(
+ replyText,
+ overflow: TextOverflow.ellipsis,
+ style: Theme.of(context).textTheme.labelMedium,
+ maxLines: 1,
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ );
+ },
+ ),
+ if (alwaysShow ||
+ groupStatus?.isFirst != false ||
+ message.metadata?["reply"] != null)
+ InkWell(
+ onTap: () => showDialog(
+ context: context,
+ builder: (_) =>
+ Dialog(child: Text("TODO: Show user profile")), // TODO
+ ),
+ child: Row(
+ mainAxisSize: MainAxisSize.min,
+ spacing: 8,
+ children: [
+ AvatarOrHash(
+ Uri.parse(message.metadata?["avatarUrl"] ?? ""),
+ message.metadata?["displayName"] ?? "",
+ ),
+ Flexible(
+ child: Text(
+ message.metadata?["displayName"] ?? message.authorId,
+ overflow: TextOverflow.ellipsis,
+ style: Theme.of(context).textTheme.titleMedium?.copyWith(
+ fontWeight: FontWeight.bold,
+ ),
+ ),
+ ),
+ ],
+ ),
+ ),
+ SizedBox(height: 4),
+ ],
+ );
+}
diff --git a/lib/widgets/loading.dart b/lib/widgets/loading.dart
index 9bb2858..aadc43c 100644
--- a/lib/widgets/loading.dart
+++ b/lib/widgets/loading.dart
@@ -1,14 +1,13 @@
import "package:flutter/material.dart";
class Loading extends StatelessWidget {
- final double? height;
- const Loading({this.height, super.key});
+ const Loading({super.key});
@override
- Widget build(BuildContext context) => Center(
- child: Padding(
- padding: EdgeInsets.all(16),
- child: SizedBox(height: height, child: CircularProgressIndicator()),
- ),
- );
+ Widget build(BuildContext context) => const Center(
+ child: Padding(
+ padding: EdgeInsets.all(16),
+ child: CircularProgressIndicator(),
+ ),
+ );
}
diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc
index f70fb6e..dffacff 100644
--- a/linux/flutter/generated_plugin_registrant.cc
+++ b/linux/flutter/generated_plugin_registrant.cc
@@ -8,6 +8,7 @@
#include
#include
+#include
#include
#include
#include
@@ -20,6 +21,9 @@ void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) file_selector_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin");
file_selector_plugin_register_with_registrar(file_selector_linux_registrar);
+ g_autoptr(FlPluginRegistrar) flutter_secure_storage_linux_registrar =
+ fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStorageLinuxPlugin");
+ flutter_secure_storage_linux_plugin_register_with_registrar(flutter_secure_storage_linux_registrar);
g_autoptr(FlPluginRegistrar) screen_retriever_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "ScreenRetrieverLinuxPlugin");
screen_retriever_linux_plugin_register_with_registrar(screen_retriever_linux_registrar);
diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake
index 78dcf40..8b658f4 100644
--- a/linux/flutter/generated_plugins.cmake
+++ b/linux/flutter/generated_plugins.cmake
@@ -5,6 +5,7 @@
list(APPEND FLUTTER_PLUGIN_LIST
dynamic_system_colors
file_selector_linux
+ flutter_secure_storage_linux
screen_retriever_linux
url_launcher_linux
window_manager
diff --git a/nix/fake-rustup.sh b/nix/fake-rustup.sh
new file mode 100644
index 0000000..7884c05
--- /dev/null
+++ b/nix/fake-rustup.sh
@@ -0,0 +1,35 @@
+#!/usr/bin/env bash
+# Fake rustup for nix-managed Rust toolchains
+
+case "$1" in
+ run)
+ if [[ "$2" == "stable" ]]; then
+ shift 2
+ if [[ $# -eq 0 ]]; then
+ echo "fake rustup: no command given" >&2
+ exit 1
+ fi
+ exec "$@"
+ exit 0
+ fi
+ ;;
+
+ toolchain)
+ if [[ "$2" == "list" ]]; then
+ echo "stable (default)"
+ exit 0
+ fi
+ ;;
+
+ target)
+ if [[ "$2" == "list" && "$3" == "--toolchain" && "$4" == "stable" && "$5" == "--installed" ]]; then
+ echo "x86_64-unknown-linux-gnu"
+ exit 0
+ fi
+ ;;
+esac
+
+echo "fake rustup: the command:" >&2
+echo " rustup $*" >&2
+echo "…is not mocked yet" >&2
+exit 1
\ No newline at end of file
diff --git a/pubspec.lock b/pubspec.lock
index da5de89..222e779 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -509,6 +509,14 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
+ flutter_math_fork:
+ dependency: transitive
+ description:
+ name: flutter_math_fork
+ sha256: "6d5f2f1aa57ae539ffb0a04bb39d2da67af74601d685a161aff7ce5bda5fa407"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.7.4"
flutter_plugin_android_lifecycle:
dependency: transitive
description:
@@ -525,6 +533,54 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.1.0"
+ flutter_secure_storage:
+ dependency: "direct main"
+ description:
+ name: flutter_secure_storage
+ sha256: da922f2aab2d733db7e011a6bcc4a825b844892d4edd6df83ff156b09a9b2e40
+ url: "https://pub.dev"
+ source: hosted
+ version: "10.0.0"
+ flutter_secure_storage_darwin:
+ dependency: transitive
+ description:
+ name: flutter_secure_storage_darwin
+ sha256: "8878c25136a79def1668c75985e8e193d9d7d095453ec28730da0315dc69aee3"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.2.0"
+ flutter_secure_storage_linux:
+ dependency: transitive
+ description:
+ name: flutter_secure_storage_linux
+ sha256: "2b5c76dce569ab752d55a1cee6a2242bcc11fdba927078fb88c503f150767cda"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.0.0"
+ flutter_secure_storage_platform_interface:
+ dependency: transitive
+ description:
+ name: flutter_secure_storage_platform_interface
+ sha256: "8ceea1223bee3c6ac1a22dabd8feefc550e4729b3675de4b5900f55afcb435d6"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.0.1"
+ flutter_secure_storage_web:
+ dependency: transitive
+ description:
+ name: flutter_secure_storage_web
+ sha256: "6a1137df62b84b54261dca582c1c09ea72f4f9a4b2fcee21b025964132d5d0c3"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.0"
+ flutter_secure_storage_windows:
+ dependency: transitive
+ description:
+ name: flutter_secure_storage_windows
+ sha256: "3b7c8e068875dfd46719ff57c90d8c459c87f2302ed6b00ff006b3c9fcad1613"
+ url: "https://pub.dev"
+ source: hosted
+ version: "4.1.0"
flutter_svg:
dependency: "direct main"
description:
@@ -583,6 +639,15 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.2.0"
+ flyer_chat_text_message:
+ dependency: "direct main"
+ description:
+ path: "packages/flyer_chat_text_message"
+ ref: HEAD
+ resolved-ref: "03be67c8c81c8f637672ee03dd8f082d2c223627"
+ url: "https://github.com/Henry-Hiles/flutter_chat_ui"
+ source: git
+ version: "2.6.0"
freezed:
dependency: "direct dev"
description:
@@ -623,6 +688,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.3"
+ gpt_markdown:
+ dependency: transitive
+ description:
+ name: gpt_markdown
+ sha256: "9b88dfaffea644070b648c204ca4a55745a49f4ad0b58ed0ab70913ad593c7a1"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.1.5"
graphs:
dependency: transitive
description:
@@ -855,6 +928,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.11.1"
+ mention_tag_text_field:
+ dependency: "direct main"
+ description:
+ name: mention_tag_text_field
+ sha256: ba7b9d8003e0f340a65c6dcdb7770f4340f653ae1612a9e31e11d12f7f1dd80f
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.0.9"
meta:
dependency: transitive
description:
@@ -1356,6 +1437,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.1.0+1"
+ tuple:
+ dependency: transitive
+ description:
+ name: tuple
+ sha256: a97ce2013f240b2f3807bcbaf218765b6f301c3eff91092bcfa23a039e7dd151
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.0.2"
typed_data:
dependency: transitive
description:
diff --git a/pubspec.yaml b/pubspec.yaml
index 3c0198d..7893653 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -42,6 +42,10 @@ dependencies:
flyer_chat_image_message: ^2.2.2
flyer_chat_system_message: ^2.1.13
flyer_chat_file_message: ^2.3.1
+ flyer_chat_text_message:
+ git:
+ url: https://github.com/Henry-Hiles/flutter_chat_ui
+ path: packages/flyer_chat_text_message
flutter_chat_ui:
git:
url: https://github.com/Henry-Hiles/flutter_chat_ui
@@ -55,7 +59,9 @@ dependencies:
flutter_svg: ^2.2.2
json_annotation: ^4.9.0
shared_preferences: ^2.5.3
+ mention_tag_text_field: ^0.0.9
fluttertagger: ^2.3.1
+ flutter_secure_storage: ^10.0.0
dynamic_polls: ^0.0.6
flutter_hooks: ^0.21.3+1
cross_cache: ^1.1.0
diff --git a/scripts/generate.dart b/scripts/generate.dart
index b240d98..9806603 100644
--- a/scripts/generate.dart
+++ b/scripts/generate.dart
@@ -25,8 +25,6 @@ void main(List args) async {
}
print("Generating FFI Bindings...");
-
- final libclangPath = Platform.environment["LIBCLANG_PATH"];
FfiGenerator(
output: Output(
dartFile: Platform.script.resolve("../lib/src/third_party/gomuks.g.dart"),
@@ -36,10 +34,6 @@ void main(List args) async {
compilerOptions: ["--no-warnings"],
),
functions: Functions.includeAll,
- ).generate(
- libclangDylib: libclangPath == null
- ? null
- : Uri.file(join(libclangPath, "libclang.so")),
- );
+ ).generate();
print("Done!");
}
diff --git a/scripts/generate.sh b/scripts/generate.sh
index 6076ab8..faafd29 100755
--- a/scripts/generate.sh
+++ b/scripts/generate.sh
@@ -1,9 +1,9 @@
#!/usr/bin/env bash
-pushd "$(dirname "$(readlink -f "$0")")"/.. > /dev/null || exit
+pushd "$(dirname "$(readlink -f "$0")")"/.. || exit
mkdir -p build
touch build/lock
dart scripts/generate.dart
rm build/lock
-popd > /dev/null || exit
\ No newline at end of file
+popd || exit
\ No newline at end of file
diff --git a/src/gomuks/libgomuks.h b/src/gomuks/libgomuks.h
new file mode 100644
index 0000000..962d281
--- /dev/null
+++ b/src/gomuks/libgomuks.h
@@ -0,0 +1,105 @@
+/* Code generated by cmd/cgo; DO NOT EDIT. */
+
+/* package go.mau.fi/gomuks/pkg/ffi */
+
+
+#line 1 "cgo-builtin-export-prolog"
+
+#include
+
+#ifndef GO_CGO_EXPORT_PROLOGUE_H
+#define GO_CGO_EXPORT_PROLOGUE_H
+
+#ifndef GO_CGO_GOSTRING_TYPEDEF
+typedef struct { const char *p; ptrdiff_t n; } _GoString_;
+extern size_t _GoStringLen(_GoString_ s);
+extern const char *_GoStringPtr(_GoString_ s);
+#endif
+
+#endif
+
+/* Start of preamble from import "C" comments. */
+
+
+#line 9 "ffi.go"
+
+#include "gomuksffi.h"
+#include
+
+static inline void _gomuks_callEventCallback(EventCallback cb, const char *command, int64_t request_id, GomuksOwnedBuffer data) {
+ cb(command, request_id, data);
+}
+
+#line 1 "cgo-generated-wrapper"
+
+
+/* End of preamble from import "C" comments. */
+
+
+/* Start of boilerplate cgo prologue. */
+#line 1 "cgo-gcc-export-header-prolog"
+
+#ifndef GO_CGO_PROLOGUE_H
+#define GO_CGO_PROLOGUE_H
+
+typedef signed char GoInt8;
+typedef unsigned char GoUint8;
+typedef short GoInt16;
+typedef unsigned short GoUint16;
+typedef int GoInt32;
+typedef unsigned int GoUint32;
+typedef long long GoInt64;
+typedef unsigned long long GoUint64;
+typedef GoInt64 GoInt;
+typedef GoUint64 GoUint;
+typedef size_t GoUintptr;
+typedef float GoFloat32;
+typedef double GoFloat64;
+#ifdef _MSC_VER
+#if !defined(__cplusplus) || _MSVC_LANG <= 201402L
+#include
+typedef _Fcomplex GoComplex64;
+typedef _Dcomplex GoComplex128;
+#else
+#include
+typedef std::complex GoComplex64;
+typedef std::complex GoComplex128;
+#endif
+#else
+typedef float _Complex GoComplex64;
+typedef double _Complex GoComplex128;
+#endif
+
+/*
+ static assertion to make sure the file is being used on architecture
+ at least with matching size of GoInt.
+*/
+typedef char _check_for_64_bit_pointer_matching_GoInt[sizeof(void*)==64/8 ? 1:-1];
+
+#ifndef GO_CGO_GOSTRING_TYPEDEF
+typedef _GoString_ GoString;
+#endif
+typedef void *GoMap;
+typedef void *GoChan;
+typedef struct { void *t; void *v; } GoInterface;
+typedef struct { void *data; GoInt len; GoInt cap; } GoSlice;
+
+#endif
+
+/* End of boilerplate cgo prologue. */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern GomuksHandle GomuksInit(void);
+extern int GomuksStart(GomuksHandle handle, EventCallback callback);
+extern void GomuksDestroy(GomuksHandle handle);
+extern GomuksResponse GomuksSubmitCommand(GomuksHandle handle, char* command, GomuksBorrowedBuffer data);
+extern GomuksAccountInfo GomuksGetAccountInfo(GomuksHandle handle);
+extern void GomuksFreeAccountInfo(GomuksAccountInfo info);
+extern void GomuksFreeBuffer(GomuksOwnedBuffer buf);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/src/gomuks/libgomuks.so b/src/gomuks/libgomuks.so
new file mode 100644
index 0000000..c4987c5
Binary files /dev/null and b/src/gomuks/libgomuks.so differ
diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc
index 55fb066..b12edca 100644
--- a/windows/flutter/generated_plugin_registrant.cc
+++ b/windows/flutter/generated_plugin_registrant.cc
@@ -8,6 +8,7 @@
#include
#include
+#include
#include
#include
#include
@@ -18,6 +19,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
registry->GetRegistrarForPlugin("DynamicColorPluginCApi"));
FileSelectorWindowsRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FileSelectorWindows"));
+ FlutterSecureStorageWindowsPluginRegisterWithRegistrar(
+ registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin"));
ScreenRetrieverWindowsPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("ScreenRetrieverWindowsPluginCApi"));
UrlLauncherWindowsRegisterWithRegistrar(
diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake
index 9333a2f..8967b80 100644
--- a/windows/flutter/generated_plugins.cmake
+++ b/windows/flutter/generated_plugins.cmake
@@ -5,6 +5,7 @@
list(APPEND FLUTTER_PLUGIN_LIST
dynamic_system_colors
file_selector_windows
+ flutter_secure_storage_windows
screen_retriever_windows
url_launcher_windows
window_manager