diff --git a/README.md b/README.md index d706c9c..4233429 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ A simple and user-friendly Matrix client made with Flutter and a Gomuks backend. - [x] Replies - [x] Choose ping on/off - [x] Per message profiles - - [x] Attachments + - [ ] Attachments - [ ] Commands with [MSC4391](https://github.com/matrix-org/matrix-spec-proposals/pull/4391) - [x] Mentions - [x] Users diff --git a/assets/twim/oauth.webp b/assets/twim/oauth.webp deleted file mode 100644 index 36fbedf..0000000 Binary files a/assets/twim/oauth.webp and /dev/null differ diff --git a/assets/twim/settings.png b/assets/twim/settings.png deleted file mode 100644 index 3037be1..0000000 Binary files a/assets/twim/settings.png and /dev/null differ diff --git a/gomuks b/gomuks index c06eb1c..3c759ba 160000 --- a/gomuks +++ b/gomuks @@ -1 +1 @@ -Subproject commit c06eb1cbda2d49ff199a351e2d55df6ff7ab602b +Subproject commit 3c759ba03a5235deb82cb1559ebc42c36ee0e874 diff --git a/lib/controllers/attachment.dart b/lib/controllers/attachment.dart deleted file mode 100644 index e33493e..0000000 --- a/lib/controllers/attachment.dart +++ /dev/null @@ -1,40 +0,0 @@ -import "package:file_selector/file_selector.dart"; -import "package:flutter_riverpod/flutter_riverpod.dart"; -import "package:nexus/controllers/client.dart"; -import "package:nexus/controllers/rooms.dart"; -import "package:nexus/models/content/content.dart"; -import "package:nexus/models/content/message.dart"; -import "package:path/path.dart"; - -class AttachmentController extends Notifier<(String, MessageContent?)?> { - final String roomId; - AttachmentController(this.roomId); - - @override - Null build() => null; - - Future add(XFile file) async { - final filename = basename(file.path); - state = (filename, null); - - final room = ref.watch( - RoomsController.provider.select((value) => value[roomId]), - ); - - final content = await ref - .watch(ClientController.provider.notifier) - .uploadMedia( - .new( - path: file.path, - encrypt: room?.state[EventType.encryption.type]?.isNotEmpty == true, - ), - ); - - state = (filename, content); - } - - static final provider = NotifierProvider.family - .autoDispose( - AttachmentController.new, - ); -} diff --git a/lib/controllers/auth_url.dart b/lib/controllers/auth_url.dart index 156eb81..1fe0695 100644 --- a/lib/controllers/auth_url.dart +++ b/lib/controllers/auth_url.dart @@ -23,8 +23,10 @@ class AuthUrlController extends AsyncNotifier { ), ); - static final provider = AsyncNotifierProvider.family - .autoDispose( - AuthUrlController.new, - ); + static final provider = + AsyncNotifierProvider.family< + AuthUrlController, + OAuthAuthCodeResponse, + Uri + >(AuthUrlController.new); } diff --git a/lib/controllers/client.dart b/lib/controllers/client.dart index 10e153d..ef0f372 100644 --- a/lib/controllers/client.dart +++ b/lib/controllers/client.dart @@ -14,7 +14,6 @@ import "package:nexus/controllers/sync_status.dart"; import "package:nexus/controllers/top_level_spaces.dart"; import "package:nexus/helpers/extensions/gomuks_buffer.dart"; import "package:nexus/main.dart"; -import "package:nexus/models/content/message.dart"; import "package:nexus/models/event.dart"; import "package:nexus/models/oauth_auth_code_response.dart"; import "package:nexus/models/paginate.dart"; @@ -33,7 +32,6 @@ import "package:nexus/models/requests/send_event.dart"; import "package:nexus/models/requests/send_message.dart"; import "package:nexus/models/requests/set_membership.dart"; import "package:nexus/models/requests/set_state.dart"; -import "package:nexus/models/requests/upload_media.dart"; import "package:nexus/models/room.dart"; import "package:nexus/models/sync_data.dart"; import "package:nexus/src/third_party/gomuks.g.dart"; @@ -268,9 +266,6 @@ class ClientController extends AsyncNotifier { Future setMembership(SetMembershipRequest request) => _sendCommand("set_membership", request.toJson()); - Future uploadMedia(UploadMediaRequest request) async => - .fromJson(await _sendCommand("upload_media", request.toJson())); - Future logout() => _sendCommand("logout"); Future markRead(Room room) async { diff --git a/lib/controllers/image_picker.dart b/lib/controllers/image_picker.dart deleted file mode 100644 index 797c997..0000000 --- a/lib/controllers/image_picker.dart +++ /dev/null @@ -1,11 +0,0 @@ -import "package:flutter_riverpod/flutter_riverpod.dart"; -import "package:image_picker/image_picker.dart"; - -class ImagePickerController extends Notifier { - @override - ImagePicker build() => .new(); - - static final provider = NotifierProvider( - ImagePickerController.new, - ); -} diff --git a/lib/controllers/room_chat.dart b/lib/controllers/room_chat.dart index ac979e0..b79010c 100644 --- a/lib/controllers/room_chat.dart +++ b/lib/controllers/room_chat.dart @@ -4,7 +4,6 @@ import "package:collection/collection.dart"; import "package:fast_immutable_collections/fast_immutable_collections.dart"; import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:fluttertagger/fluttertagger.dart"; -import "package:nexus/controllers/attachment.dart"; import "package:nexus/controllers/client.dart"; import "package:nexus/controllers/rooms.dart"; import "package:nexus/models/content/content.dart"; @@ -121,10 +120,6 @@ class RoomChatController extends AsyncNotifier?> { required RelationType relationType, Event? relation, }) async { - final attachment = ref.watch(AttachmentController.provider(roomId)); - - if (attachment != null && attachment.$2 == null) return; - var taggedMessage = text; for (final tag in tags) { @@ -141,7 +136,6 @@ class RoomChatController extends AsyncNotifier?> { final event = await client.sendMessage( SendMessageRequest( roomId: roomId, - baseContent: attachment?.$2, mentions: Mentions( userIds: [ if (shouldMention == true && @@ -158,8 +152,6 @@ class RoomChatController extends AsyncNotifier?> { ), ); - ref.invalidate(AttachmentController.provider(roomId)); - ref .watch(RoomsController.provider.notifier) .update( diff --git a/lib/controllers/settings_sections.dart b/lib/controllers/settings_sections.dart index 7c9796b..1704ed0 100644 --- a/lib/controllers/settings_sections.dart +++ b/lib/controllers/settings_sections.dart @@ -5,7 +5,6 @@ import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:intl/intl.dart"; import "package:m3e_buttons/m3e_buttons.dart"; import "package:nexus/controllers/client.dart"; -import "package:nexus/controllers/client_state.dart"; import "package:nexus/controllers/settings.dart"; import "package:nexus/models/settings_category.dart"; import "package:nexus/main.dart"; @@ -90,47 +89,41 @@ class SettingsSectionsController ]), ), ]), - if (ref.watch(ClientStateController.provider)?.isLoggedIn == true) - "Account": .new([ - .new(title: "Profile", icon: Icons.person, settings: .new([])), - .new( - title: "Other", - icon: Icons.key, - settings: .new([ - .new( - title: "Log Out", - description: - "Log out of your account, returning you to the login page.", - builder: (title, description, icon) => Builder( - builder: (context) { - final colorScheme = Theme.of(context).colorScheme; - return M3EButton.icon( - onPressed: () async { - Navigator.of( - context, - ).popUntil((route) => route.isFirst); - - await WidgetsBinding.instance.endOfFrame; - - await ref - .watch(ClientController.provider.notifier) - .logout(); - }, - label: Text(title), - icon: Icon(icon), - tooltip: description, - decoration: .styleFrom( - backgroundColor: colorScheme.errorContainer, - foregroundColor: colorScheme.onErrorContainer, - ), - ); - }, - ), - icon: Icons.logout, + "Account": .new([ + .new(title: "Profile", icon: Icons.person, settings: .new([])), + .new( + title: "Other", + icon: Icons.key, + settings: .new([ + .new( + title: "Log Out", + description: + "Log out of your account, returning you to the login page.", + builder: (title, description, icon) => Builder( + builder: (context) { + final colorScheme = Theme.of(context).colorScheme; + return M3EButton.icon( + onPressed: () async { + await ref + .watch(ClientController.provider.notifier) + .logout(); + if (context.mounted) Navigator.of(context).pop(); + }, + label: Text(title), + icon: Icon(icon), + tooltip: description, + decoration: .styleFrom( + backgroundColor: colorScheme.errorContainer, + foregroundColor: colorScheme.onErrorContainer, + ), + ); + }, ), - ]), - ), - ]), + icon: Icons.logout, + ), + ]), + ), + ]), }); } diff --git a/lib/helpers/extensions/scheme_to_theme.dart b/lib/helpers/extensions/scheme_to_theme.dart index bf48535..fdda5d8 100644 --- a/lib/helpers/extensions/scheme_to_theme.dart +++ b/lib/helpers/extensions/scheme_to_theme.dart @@ -1,36 +1,22 @@ import "package:flutter/material.dart"; extension SchemeToTheme on ColorScheme { - ThemeData get theme { - final textTheme = ThemeData( + ThemeData get theme => .from(colorScheme: this).copyWith( + cardTheme: .new(color: primaryContainer), + popupMenuTheme: .new( + shape: RoundedRectangleBorder(borderRadius: .circular(16)), + color: surfaceContainerHigh, + ), + appBarTheme: AppBarTheme( + titleSpacing: 0, + backgroundColor: surfaceContainerLow, + ), + textTheme: ThemeData( fontFamilyFallback: ["sans", "emoji", "fallback-sans", "fallback-emoji"], brightness: brightness, - ).textTheme; - return .from(colorScheme: this).copyWith( - cardTheme: .new(color: primaryContainer), - popupMenuTheme: .new( - shape: RoundedRectangleBorder(borderRadius: .circular(16)), - color: surfaceContainerHigh, - ), - appBarTheme: AppBarTheme( - titleSpacing: 0, - backgroundColor: surfaceContainerLow, - ), - tooltipTheme: .new( - textStyle: textTheme.labelLarge?.copyWith( - fontSize: 16, - fontWeight: .w600, - ), - padding: .all(8), - decoration: BoxDecoration( - color: surfaceContainerHighest, - borderRadius: .circular(8), - ), - ), - textTheme: textTheme, - inputDecorationTheme: const InputDecorationTheme( - border: OutlineInputBorder(), - ), - ); - } + ).textTheme, + inputDecorationTheme: const InputDecorationTheme( + border: OutlineInputBorder(), + ), + ); } diff --git a/lib/main.dart b/lib/main.dart index f50d22d..05731e4 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -38,16 +38,11 @@ New Value: ${newValue is AsyncData ? newValue.value : newValue} } void showError(Object error, [StackTrace? stackTrace]) { - if (error.toString().contains("DioException") || - error.toString().contains( - "setState() or markNeedsBuild() called during build.", - ) || - error.toString().contains("Invalid source") || - error.toString().contains("UTF-16") || - error.toString().contains("HTTP request failed") || - error.toString().contains("Invalid image data")) { - return; - } + if (error.toString().contains("DioException")) return; + if (error.toString().contains("Invalid source")) return; + if (error.toString().contains("UTF-16")) return; + if (error.toString().contains("HTTP request failed")) return; + if (error.toString().contains("Invalid image data")) return; debugPrintStack(stackTrace: stackTrace, label: error.toString()); if (navigatorKey.currentContext != null) { @@ -84,7 +79,7 @@ void main() async { runApp( ProviderScope( - retry: (_, _) => null, + retry: null, observers: [ // Change false to true if you want debug information on provider reloads // ignore: dead_code diff --git a/lib/models/content/message.dart b/lib/models/content/message.dart index acbe70e..6314161 100644 --- a/lib/models/content/message.dart +++ b/lib/models/content/message.dart @@ -7,8 +7,6 @@ import "package:nexus/models/info/video.dart"; part "message.freezed.dart"; part "message.g.dart"; -typedef EncryptedFile = Map; - @Freezed(unionKey: "msgtype", fallbackUnion: "default") abstract class MessageContent extends Content with _$MessageContent { MessageContent._(); @@ -40,7 +38,7 @@ abstract class MessageContent extends Content with _$MessageContent { required String body, MessageFormat? format, String? formattedBody, - EncryptedFile? file, + // EncryptedFile? file String? filename, ImageInfo? info, Uri? url, @@ -51,7 +49,7 @@ abstract class MessageContent extends Content with _$MessageContent { required String body, MessageFormat? format, String? formattedBody, - EncryptedFile? file, + // EncryptedFile? file String? filename, FileInfo? info, Uri? url, @@ -62,7 +60,7 @@ abstract class MessageContent extends Content with _$MessageContent { required String body, MessageFormat? format, String? formattedBody, - EncryptedFile? file, + // EncryptedFile? file String? filename, AudioInfo? info, Uri? url, @@ -73,7 +71,7 @@ abstract class MessageContent extends Content with _$MessageContent { required String body, MessageFormat? format, String? formattedBody, - EncryptedFile? file, + // EncryptedFile? file String? filename, VideoInfo? info, Uri? url, diff --git a/lib/models/requests/send_message.dart b/lib/models/requests/send_message.dart index 951198e..628007f 100644 --- a/lib/models/requests/send_message.dart +++ b/lib/models/requests/send_message.dart @@ -1,6 +1,5 @@ import "package:fast_immutable_collections/fast_immutable_collections.dart"; import "package:freezed_annotation/freezed_annotation.dart"; -import "package:nexus/models/content/content.dart"; import "package:nexus/models/relation_type.dart"; part "send_message.freezed.dart"; part "send_message.g.dart"; @@ -10,7 +9,6 @@ abstract class SendMessageRequest with _$SendMessageRequest { const factory SendMessageRequest({ required String roomId, required String text, - Content? baseContent, @Default(Mentions()) @JsonKey(name: "mentions") Mentions mentions, @JsonKey(name: "relates_to") Relation? relation, }) = _SendMessageRequest; diff --git a/lib/models/requests/upload_media.dart b/lib/models/requests/upload_media.dart deleted file mode 100644 index a640bea..0000000 --- a/lib/models/requests/upload_media.dart +++ /dev/null @@ -1,24 +0,0 @@ -import "package:freezed_annotation/freezed_annotation.dart"; -part "upload_media.freezed.dart"; -part "upload_media.g.dart"; - -@freezed -abstract class UploadMediaRequest with _$UploadMediaRequest { - const factory UploadMediaRequest({ - required String path, - required bool encrypt, - String? filename, - @Default(false) @JsonKey(name: "voice_message") bool isVoiceMessage, - @Default(false) bool forceFile, - - // Below params only work if encodeTo is set - String? encodeTo, - int? resizeWidth, - int? resizeHeight, - int? resizePercent, - @Default(80) int quality, - }) = _UploadMediaRequest; - - factory UploadMediaRequest.fromJson(Map json) => - _$UploadMediaRequestFromJson(json); -} diff --git a/lib/pages/select_server.dart b/lib/pages/select_server.dart index f7cefb8..118e70a 100644 --- a/lib/pages/select_server.dart +++ b/lib/pages/select_server.dart @@ -9,7 +9,6 @@ import "package:nexus/controllers/client_id.dart"; import "package:nexus/helpers/launch_helper.dart"; import "package:nexus/main.dart"; import "package:nexus/models/homeserver.dart"; -import "package:nexus/pages/settings.dart"; import "package:nexus/widgets/appbar.dart"; import "package:nexus/widgets/divider_text.dart"; @@ -58,9 +57,6 @@ class SelectServerPage extends HookConsumerWidget { await ref.watch(LaunchHelper.provider).launchUrl(codeResponse.url); AppLinks().uriLinkStream.listen((encodedUri) async { - final state = encodedUri.queryParameters["state"]; - if (state != codeResponse.state) return; - final code = encodedUri.queryParameters["code"]; if (code != null) { @@ -83,6 +79,9 @@ class SelectServerPage extends HookConsumerWidget { .onError(showError); } }); + // await Navigator.of(context).push( + // MaterialPageRoute(builder: (_) => LoginPage(homeserver: newUrl)), + // ); } } } catch (error, stackTrace) { @@ -93,15 +92,7 @@ class SelectServerPage extends HookConsumerWidget { } return Scaffold( - appBar: Appbar( - actions: .new([ - IconButton( - onPressed: () => - showDialog(context: context, builder: (_) => SettingsPage()), - icon: Icon(Icons.settings), - ), - ]), - ), + appBar: Appbar(), body: SafeArea( child: Center( child: ConstrainedBox( diff --git a/lib/pages/settings.dart b/lib/pages/settings.dart index fa4d8da..96b9b82 100644 --- a/lib/pages/settings.dart +++ b/lib/pages/settings.dart @@ -127,13 +127,13 @@ class SettingsPage extends ConsumerWidget { ), ...sections .mapTo( - (section, categories) => [ + (categoryGroup, categories) => [ SliverToBoxAdapter( child: Padding( padding: EdgeInsets.symmetric( horizontal: 16, ).copyWith(bottom: 4), - child: DividerText(section), + child: DividerText(categoryGroup), ), ), SliverM3ECardList( @@ -150,14 +150,7 @@ class SettingsPage extends ConsumerWidget { Navigator.of(context).push( MaterialPageRoute( builder: (context) => - SettingsCategoryPage( - sections - .values - .flattenedToList - .indexOf( - categories[index], - ), - ), + SettingsCategoryPage(index), ), ), itemBuilder: (context, index) => ListTile( diff --git a/lib/pages/verify.dart b/lib/pages/verify.dart index 4eefa6c..1b4f7ad 100644 --- a/lib/pages/verify.dart +++ b/lib/pages/verify.dart @@ -2,7 +2,6 @@ import "package:flutter/material.dart"; import "package:flutter_hooks/flutter_hooks.dart"; import "package:hooks_riverpod/hooks_riverpod.dart"; import "package:nexus/controllers/client.dart"; -import "package:nexus/pages/settings.dart"; import "package:nexus/widgets/appbar.dart"; import "package:nexus/helpers/required_validator_helper.dart"; @@ -33,15 +32,7 @@ class VerifyPage extends HookConsumerWidget { } return Scaffold( - appBar: Appbar( - actions: .new([ - IconButton( - onPressed: () => - showDialog(context: context, builder: (_) => SettingsPage()), - icon: Icon(Icons.settings), - ), - ]), - ), + appBar: Appbar(), body: AlertDialog( title: Text("Verify"), content: Form( diff --git a/lib/widgets/composer/composer.dart b/lib/widgets/composer/composer.dart index c63d939..ef91dbe 100644 --- a/lib/widgets/composer/composer.dart +++ b/lib/widgets/composer/composer.dart @@ -1,13 +1,9 @@ -import "dart:io"; import "package:fast_immutable_collections/fast_immutable_collections.dart"; -import "package:file_selector/file_selector.dart"; import "package:flutter/material.dart"; import "package:flutter/services.dart"; import "package:flutter_hooks/flutter_hooks.dart"; import "package:fluttertagger/fluttertagger.dart"; import "package:hooks_riverpod/hooks_riverpod.dart"; -import "package:nexus/controllers/attachment.dart"; -import "package:nexus/controllers/image_picker.dart"; import "package:nexus/controllers/power_level.dart"; import "package:nexus/models/content/message.dart"; import "package:nexus/models/event.dart"; @@ -15,7 +11,6 @@ import "package:nexus/models/relation_type.dart"; import "package:nexus/widgets/composer/mention_overlay.dart"; import "package:nexus/widgets/composer/relation_preview.dart"; import "package:nexus/widgets/emoji_picker_button.dart"; -import "package:nexus/main.dart"; class Composer extends HookConsumerWidget { final String roomId; @@ -56,10 +51,8 @@ class Composer extends HookConsumerWidget { }; } - final attachment = ref.watch(AttachmentController.provider(roomId)); - void send() { - if (controller.value.text.isEmpty && attachment == null) return; + if (controller.value.text.isEmpty) return; onSend( controller.value.formattedText, shouldMention: shouldMention.value, @@ -77,200 +70,129 @@ class Composer extends HookConsumerWidget { return Padding( padding: .all(12), - child: Column( - children: [ - if (attachment != null) - Card( - margin: .only(bottom: 8), - child: ListTile( - leading: attachment.$2 == null - ? SizedBox( - width: 16, - height: 16, - child: CircularProgressIndicator(), - ) - : Icon(Icons.file_copy), - title: Text(attachment.$1), - trailing: IconButton( - onPressed: () => - ref.invalidate(AttachmentController.provider(roomId)), - icon: Icon(Icons.close), - ), - ), + child: ClipRRect( + borderRadius: .all(.circular(12)), + child: Column( + children: [ + RelationPreview( + relatedEvent, + shouldMention: shouldMention.value, + toggleShouldMention: () => + shouldMention.value = !shouldMention.value, + relationType: relationType, + onDismiss: onDismiss, ), - ClipRRect( - borderRadius: .all(.circular(12)), - child: Column( - children: [ - RelationPreview( - relatedEvent, - shouldMention: shouldMention.value, - toggleShouldMention: () => - shouldMention.value = !shouldMention.value, - relationType: relationType, - onDismiss: onDismiss, - ), - Container( - color: theme.colorScheme.surfaceContainerHighest, - padding: .symmetric(horizontal: 8), - child: Row( - spacing: 8, - mainAxisAlignment: .center, - children: - ref.watch( - PowerLevelController.provider( - .new(eventType: .message, roomId: roomId), - ), - ) - ? [ - EmojiPickerButton( - context: context, - onSelection: (_) => node?.requestFocus(), - controller: controller.value, - ), - PopupMenuButton( - tooltip: "Add media", - enabled: attachment == null, - itemBuilder: (context) => [ - if (Platform.isAndroid || Platform.isIOS) - PopupMenuItem( - child: ListTile( - title: Text("Camera"), - leading: Icon(Icons.add_a_photo), - ), - onTap: () async => ref - .watch( - AttachmentController.provider( - roomId, - ).notifier, - ) - .add( - (await ref - .watch( - ImagePickerController.provider, - ) - .pickImage(source: .camera))!, - ) - .onError(showError), - ), - PopupMenuItem( - child: ListTile( - title: Text("Gallery"), - leading: Icon(Icons.add_photo_alternate), - ), - onTap: () async => ref - .watch( - AttachmentController.provider( - roomId, - ).notifier, - ) - .add( - (await ref - .watch( - ImagePickerController.provider, - ) - .pickImage(source: .gallery))!, - ) - .onError(showError), - ), - PopupMenuItem( - onTap: () async => ref - .watch( - AttachmentController.provider( - roomId, - ).notifier, - ) - .add((await openFile())!) - .onError(showError), - child: ListTile( - title: Text("Files"), - leading: Icon(Icons.attachment), - ), - ), - ], - icon: Icon(Icons.add), - ), - Expanded( - child: FlutterTagger( - triggerStrategy: .eager, - overlay: MentionOverlay( - roomId, - 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) => Focus( - onKeyEvent: (_, event) { - if (event is KeyDownEvent && - event.logicalKey == - LogicalKeyboardKey.enter) { - final shiftPressed = HardwareKeyboard - .instance - .isShiftPressed; - - if (!shiftPressed) { - send(); - return KeyEventResult.handled; - } - } - - return KeyEventResult.ignored; - }, - child: TextField( - maxLines: 12, - minLines: 1, - autofocus: true, - decoration: .new( - hintText: "Your message here...", - border: .none, - ), - controller: controller.value, - key: key, - focusNode: node, - ), - ), + Container( + color: theme.colorScheme.surfaceContainerHighest, + padding: .symmetric(horizontal: 8), + child: Row( + spacing: 8, + mainAxisAlignment: .center, + children: + ref.watch( + PowerLevelController.provider( + .new(eventType: .message, roomId: roomId), + ), + ) + ? [ + EmojiPickerButton( + context: context, + onSelection: (_) => node?.requestFocus(), + controller: controller.value, + ), + PopupMenuButton( + tooltip: "Add media", + itemBuilder: (context) => [ + PopupMenuItem( + child: ListTile( + title: Text("Camera"), + leading: Icon(Icons.add_a_photo), ), ), - IconButton( - onPressed: - attachment != null && attachment.$2 == null - ? null - : send, - icon: Icon(Icons.send), - tooltip: "Send message", + PopupMenuItem( + child: ListTile( + title: Text("Gallery"), + leading: Icon(Icons.add_photo_alternate), + ), ), - ] - : [ - Expanded( - child: Padding( - padding: .symmetric( - horizontal: 8, - vertical: 12, - ), - child: Text( - "You don't have permission to send messages in this room...", - ), + PopupMenuItem( + child: ListTile( + title: Text("Files"), + leading: Icon(Icons.attachment), ), ), ], - ), - ), - ], + icon: Icon(Icons.add), + ), + Expanded( + child: FlutterTagger( + triggerStrategy: .eager, + overlay: MentionOverlay( + roomId, + 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) => Focus( + onKeyEvent: (_, event) { + if (event is KeyDownEvent && + event.logicalKey == + LogicalKeyboardKey.enter) { + final shiftPressed = + HardwareKeyboard.instance.isShiftPressed; + + if (!shiftPressed) { + send(); + return KeyEventResult.handled; + } + } + + return KeyEventResult.ignored; + }, + child: TextField( + maxLines: 12, + minLines: 1, + autofocus: true, + decoration: .new( + hintText: "Your message here...", + border: .none, + ), + controller: controller.value, + key: key, + focusNode: node, + ), + ), + ), + ), + IconButton( + onPressed: send, + icon: Icon(Icons.send), + tooltip: "Send message", + ), + ] + : [ + Expanded( + child: Padding( + padding: .symmetric(horizontal: 8, vertical: 12), + child: Text( + "You don't have permission to send messages in this room...", + ), + ), + ), + ], + ), ), - ), - ], + ], + ), ), ); } diff --git a/lib/widgets/composer/mention_overlay.dart b/lib/widgets/composer/mention_overlay.dart index ca4f95a..962ea84 100644 --- a/lib/widgets/composer/mention_overlay.dart +++ b/lib/widgets/composer/mention_overlay.dart @@ -73,23 +73,20 @@ class MentionOverlay extends ConsumerWidget { :final displayName, :final avatarUrl, ) => - Material( - color: Colors.transparent, - child: ListTile( - leading: AvatarOrHash( - avatarUrl, - displayName ?? - member.stateKey!.localpart, - ), - title: Text( - displayName ?? - member.stateKey!.localpart, - ), - subtitle: Text(member.stateKey!), - onTap: () => addTag( - id: "[@$displayName](matrix:u/${member.stateKey!.substring(1)})", - name: member.stateKey!.localpart, - ), + ListTile( + leading: AvatarOrHash( + avatarUrl, + displayName ?? + member.stateKey!.localpart, + ), + title: Text( + displayName ?? + member.stateKey!.localpart, + ), + subtitle: Text(member.stateKey!), + onTap: () => addTag( + id: "[@$displayName](matrix:u/${member.stateKey!.substring(1)})", + name: member.stateKey!.localpart, ), ), _ => SizedBox.shrink(), @@ -104,46 +101,40 @@ class MentionOverlay extends ConsumerWidget { ? rooms.values : rooms.values.where( (room) => - (room.metadata?.name ?? - room.metadata?.id ?? - "") + (room.metadata?.name ?? room.metadata!.id) .toLowerCase() .contains(query.toLowerCase()), )) .map((room) { final name = room.metadata?.name ?? - room.metadata?.canonicalAlias ?? - room.metadata?.id ?? - "Unknown Room"; - return Material( - color: Colors.transparent, - child: ListTile( - leading: AvatarOrHash( - room.metadata?.avatar, - name, - fallback: Icon(Icons.numbers), - ), - title: Text(name), - 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 ?? - "", - ); - }, + room.metadata!.canonicalAlias ?? + room.metadata!.id; + return ListTile( + leading: AvatarOrHash( + room.metadata?.avatar, + name, + fallback: Icon(Icons.numbers), ), + title: Text(name), + 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 ?? + "", + ); + }, ); }) .toList(), diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index f18d831..7b5a69f 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -7,6 +7,7 @@ import Foundation import app_links import dynamic_color +import file_picker import file_selector_macos import media_kit_libs_macos_video import media_kit_video @@ -20,6 +21,7 @@ import window_manager func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { AppLinksMacosPlugin.register(with: registry.registrar(forPlugin: "AppLinksMacosPlugin")) DynamicColorPlugin.register(with: registry.registrar(forPlugin: "DynamicColorPlugin")) + FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin")) FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) MediaKitLibsMacosVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitLibsMacosVideoPlugin")) MediaKitVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitVideoPlugin")) diff --git a/macos/Runner/DebugProfile.entitlements b/macos/Runner/DebugProfile.entitlements index 4e593b0..3ba6c12 100644 --- a/macos/Runner/DebugProfile.entitlements +++ b/macos/Runner/DebugProfile.entitlements @@ -10,7 +10,5 @@ com.apple.security.network.server - com.apple.security.files.user-selected.read-only - diff --git a/macos/Runner/Release.entitlements b/macos/Runner/Release.entitlements index 741903e..ee95ab7 100644 --- a/macos/Runner/Release.entitlements +++ b/macos/Runner/Release.entitlements @@ -6,7 +6,5 @@ com.apple.security.network.client - com.apple.security.files.user-selected.read-only - diff --git a/pubspec.lock b/pubspec.lock index 3a0aeb5..ca82296 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -402,30 +402,14 @@ packages: url: "https://pub.dev" source: hosted version: "7.0.1" - file_selector: + file_picker: dependency: "direct main" description: - name: file_selector - sha256: bd15e43e9268db636b53eeaca9f56324d1622af30e5c34d6e267649758c84d9a + name: file_picker + sha256: fdc6a37f715d19f35b131decf1ce39242eeed5ddae18c0818c3eccb731ab76be url: "https://pub.dev" source: hosted - version: "1.1.0" - file_selector_android: - dependency: transitive - description: - name: file_selector_android - sha256: "6a26687fa65cbc28a5345c7ae6f227e89f0b47740978a4c475b1a625da7a331b" - url: "https://pub.dev" - source: hosted - version: "0.5.2+8" - file_selector_ios: - dependency: transitive - description: - name: file_selector_ios - sha256: e2ecf2885c121691ce13b60db3508f53c01f869fb6e8dc5c1cfa771e4c46aeca - url: "https://pub.dev" - source: hosted - version: "0.5.3+5" + version: "12.0.0-beta.7" file_selector_linux: dependency: transitive description: @@ -450,14 +434,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.7.0" - file_selector_web: - dependency: transitive - description: - name: file_selector_web - sha256: "73181fbc5257776d8ecaa6a94ab3c8e920ad143b9132a6d984a9271dfc6928d3" - url: "https://pub.dev" - source: hosted - version: "0.9.5" file_selector_windows: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 2669fea..0002bdd 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -37,7 +37,8 @@ dependencies: path_provider: 2.1.6 url_launcher: 6.3.2 freezed_annotation: 3.1.0 - image_picker: ^1.2.3 + image_picker: 1.2.3 + file_picker: 12.0.0-beta.7 path: 1.9.1 dynamic_color: 1.8.1 collection: 1.19.1 @@ -77,7 +78,6 @@ dependencies: xdg_directories: 1.1.0 package_info_plus: 10.2.1 app_links: 7.2.1 - file_selector: ^1.1.0 dev_dependencies: build_runner: ^2.15.1