diff --git a/.github/workflows/versions.env b/.github/workflows/versions.env index 4d5a9ba26..5a23ceb3e 100644 --- a/.github/workflows/versions.env +++ b/.github/workflows/versions.env @@ -1,2 +1,2 @@ -FLUTTER_VERSION=3.35.3 +FLUTTER_VERSION=3.35.2 JAVA_VERSION=17 diff --git a/lib/pages/chat/input_bar.dart b/lib/pages/chat/input_bar.dart index d89cee360..5d9563e6c 100644 --- a/lib/pages/chat/input_bar.dart +++ b/lib/pages/chat/input_bar.dart @@ -2,10 +2,10 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:emojis/emoji.dart'; -import 'package:flutter_typeahead/flutter_typeahead.dart'; import 'package:matrix/matrix.dart'; import 'package:slugify/slugify.dart'; +import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/l10n/l10n.dart'; import 'package:fluffychat/pangea/choreographer/choreo_constants.dart'; import 'package:fluffychat/pangea/choreographer/choreo_mode_enum.dart'; @@ -34,16 +34,14 @@ class InputBar extends StatelessWidget { final FocusNode? focusNode; // #Pangea // final TextEditingController? controller; + final PangeaTextController? controller; + final Choreographer choreographer; + final VoidCallback showNextMatch; // Pangea# final InputDecoration decoration; final ValueChanged? onChanged; final bool? autofocus; final bool readOnly; - // #Pangea - final PangeaTextController? controller; - final Choreographer choreographer; - final VoidCallback showNextMatch; - // Pangea# const InputBar({ required this.room, @@ -66,14 +64,12 @@ class InputBar extends StatelessWidget { super.key, }); - List> getSuggestions(String text) { - if (controller!.selection.baseOffset != - controller!.selection.extentOffset || - controller!.selection.baseOffset < 0) { + List> getSuggestions(TextEditingValue text) { + if (text.selection.baseOffset != text.selection.extentOffset || + text.selection.baseOffset < 0) { return []; // no entries if there is selected text } - final searchText = - controller!.text.substring(0, controller!.selection.baseOffset); + final searchText = text.text.substring(0, text.selection.baseOffset); final ret = >[]; const maxResults = 30; @@ -240,33 +236,28 @@ class InputBar extends StatelessWidget { Widget buildSuggestion( BuildContext context, Map suggestion, + void Function(Map) onSelected, Client? client, ) { final theme = Theme.of(context); const size = 30.0; - const padding = EdgeInsets.all(4.0); if (suggestion['type'] == 'command') { final command = suggestion['name']!; final hint = commandHint(L10n.of(context), command); return Tooltip( message: hint, waitDuration: const Duration(days: 1), // don't show on hover - child: Container( - padding: padding, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - commandExample(command), - style: const TextStyle(fontFamily: 'RobotoMono'), - ), - Text( - hint, - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: theme.textTheme.bodySmall, - ), - ], + child: ListTile( + onTap: () => onSelected(suggestion), + title: Text( + commandExample(command), + style: const TextStyle(fontFamily: 'RobotoMono'), + ), + subtitle: Text( + hint, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: theme.textTheme.bodySmall, ), ), ); @@ -276,29 +267,28 @@ class InputBar extends StatelessWidget { return Tooltip( message: label, waitDuration: const Duration(days: 1), // don't show on hover - child: Container( - padding: padding, - child: Text(label, style: const TextStyle(fontFamily: 'RobotoMono')), + child: ListTile( + onTap: () => onSelected(suggestion), + title: Text(label, style: const TextStyle(fontFamily: 'RobotoMono')), ), ); } if (suggestion['type'] == 'emote') { - return Container( - padding: padding, - child: Row( + return ListTile( + onTap: () => onSelected(suggestion), + leading: MxcImage( + // ensure proper ordering ... + key: ValueKey(suggestion['name']), + uri: suggestion['mxc'] is String + ? Uri.parse(suggestion['mxc'] ?? '') + : null, + width: size, + height: size, + isThumbnail: false, + ), + title: Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ - MxcImage( - // ensure proper ordering ... - key: ValueKey(suggestion['name']), - uri: suggestion['mxc'] is String - ? Uri.parse(suggestion['mxc'] ?? '') - : null, - width: size, - height: size, - isThumbnail: false, - ), - const SizedBox(width: 6), Text(suggestion['name']!), Expanded( child: Align( @@ -324,28 +314,22 @@ class InputBar extends StatelessWidget { } if (suggestion['type'] == 'user' || suggestion['type'] == 'room') { final url = Uri.parse(suggestion['avatar_url'] ?? ''); - return Container( - padding: padding, - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Avatar( - mxContent: url, - name: suggestion.tryGet('displayname') ?? - suggestion.tryGet('mxid'), - size: size, - client: client, - ), - const SizedBox(width: 6), - Text(suggestion['displayname'] ?? suggestion['mxid']!), - ], + return ListTile( + onTap: () => onSelected(suggestion), + leading: Avatar( + mxContent: url, + name: suggestion.tryGet('displayname') ?? + suggestion.tryGet('mxid'), + size: size, + client: client, ), + title: Text(suggestion['displayname'] ?? suggestion['mxid']!), ); } return const SizedBox.shrink(); } - void insertSuggestion(_, Map suggestion) { + String insertSuggestion(Map suggestion) { final replaceText = controller!.text.substring(0, controller!.selection.baseOffset); var startText = ''; @@ -409,13 +393,8 @@ class InputBar extends StatelessWidget { (Match m) => '${m[1]}$insertText', ); } - if (insertText.isNotEmpty && startText.isNotEmpty) { - controller!.text = startText + afterText; - controller!.selection = TextSelection( - baseOffset: startText.length, - extentOffset: startText.length, - ); - } + + return startText + afterText; } // #Pangea @@ -484,105 +463,115 @@ class InputBar extends StatelessWidget { @override Widget build(BuildContext context) { - return TypeAheadField>( - direction: VerticalDirection.up, - hideOnEmpty: true, - hideOnLoading: true, - controller: controller, + final theme = Theme.of(context); + return Autocomplete>( focusNode: focusNode, - hideOnSelect: false, - debounceDuration: const Duration(milliseconds: 50), - // show suggestions after 50ms idle time (default is 300) + textEditingController: controller, + optionsBuilder: getSuggestions, // #Pangea - // builder: (context, controller, focusNode) => TextField( - builder: (context, _, focusNode) => TextField( - // Pangea# - controller: controller, - focusNode: focusNode, - // #Pangea - // readOnly: readOnly, - // contextMenuBuilder: (c, e) => markdownContextBuilder(c, e, controller), - contextMenuBuilder: (c, e) => markdownContextBuilder(c, e, controller!), - onTap: () => _onInputTap(context), - readOnly: choreographer.choreoMode == ChoreoModeEnum.it, - autocorrect: MatrixState.pangeaController.userController - .isToolEnabled(ToolSetting.enableAutocorrect), - // Pangea# - contentInsertionConfiguration: ContentInsertionConfiguration( - onContentInserted: (KeyboardInsertedContent content) { - final data = content.data; - if (data == null) return; + // fieldViewBuilder: (context, controller, focusNode, _) => TextField( + fieldViewBuilder: (context, __, focusNode, _) => ValueListenableBuilder( + valueListenable: choreographer.itController.open, + builder: (context, _, __) { + return TextField( + // Pangea# + controller: controller, + focusNode: focusNode, + // #Pangea + // readOnly: readOnly, + // contextMenuBuilder: (c, e) => + // markdownContextBuilder(c, e, controller), + contextMenuBuilder: (c, e) => + markdownContextBuilder(c, e, controller!), + onTap: () => _onInputTap(context), + readOnly: choreographer.choreoMode == ChoreoModeEnum.it, + autocorrect: MatrixState.pangeaController.userController + .isToolEnabled(ToolSetting.enableAutocorrect), + // Pangea# + contentInsertionConfiguration: ContentInsertionConfiguration( + onContentInserted: (KeyboardInsertedContent content) { + final data = content.data; + if (data == null) return; - final file = MatrixFile( - mimeType: content.mimeType, - bytes: data, - name: content.uri.split('/').last, - ); - room.sendFileEvent( - file, - shrinkImageMaxDimension: 1600, - ); - }, - ), - minLines: minLines, - maxLines: maxLines, - keyboardType: keyboardType!, - textInputAction: textInputAction, - autofocus: autofocus!, - inputFormatters: [ - // #Pangea - //LengthLimitingTextInputFormatter((maxPDUSize / 3).floor()), - //setting max character count to 1000 - //after max, nothing else can be typed - LengthLimitingTextInputFormatter(1000), - // Pangea# - ], - onSubmitted: (text) { - // fix for library for now - // it sets the types for the callback incorrectly - onSubmitted!(text); - }, - // #Pangea - // maxLength: - // AppSettings.textMessageMaxLength.getItem(Matrix.of(context).store), - // decoration: decoration, - decoration: decoration.copyWith( - hint: StreamBuilder( - stream: MatrixState - .pangeaController.userController.languageStream.stream, - builder: (context, _) => SizedBox( - height: 24, - child: ShrinkableText( - text: choreographer.itController.open.value - ? L10n.of(context).buildTranslation - : _defaultHintText(context), - maxWidth: double.infinity, - style: Theme.of(context).textTheme.bodyLarge?.copyWith( - color: Theme.of(context).disabledColor, - ), + final file = MatrixFile( + mimeType: content.mimeType, + bytes: data, + name: content.uri.split('/').last, + ); + room.sendFileEvent( + file, + shrinkImageMaxDimension: 1600, + ); + }, + ), + minLines: minLines, + maxLines: maxLines, + keyboardType: keyboardType!, + textInputAction: textInputAction, + autofocus: autofocus!, + inputFormatters: [ + // #Pangea + // LengthLimitingTextInputFormatter((maxPDUSize / 3).floor()), + LengthLimitingTextInputFormatter(1000), + // Pangea# + ], + onSubmitted: (text) { + // fix for library for now + // it sets the types for the callback incorrectly + onSubmitted!(text); + }, + // #Pangea + // maxLength: AppSettings.textMessageMaxLength.value, + // decoration: decoration!, + decoration: decoration.copyWith( + hint: StreamBuilder( + stream: MatrixState + .pangeaController.userController.languageStream.stream, + builder: (context, _) => SizedBox( + height: 24, + child: ShrinkableText( + text: choreographer.itController.open.value + ? L10n.of(context).buildTranslation + : _defaultHintText(context), + maxWidth: double.infinity, + style: Theme.of(context).textTheme.bodyLarge?.copyWith( + color: Theme.of(context).disabledColor, + ), + ), + ), ), ), - ), - ), - // Pangea# - onChanged: (text) { - // fix for the library for now - // it sets the types for the callback incorrectly - onChanged!(text); + // Pangea# + onChanged: (text) { + // fix for the library for now + // it sets the types for the callback incorrectly + onChanged!(text); + }, + textCapitalization: TextCapitalization.sentences, + ); }, - textCapitalization: TextCapitalization.sentences, ), - - suggestionsCallback: getSuggestions, - itemBuilder: (c, s) => buildSuggestion(c, s, Matrix.of(context).client), - onSelected: (Map suggestion) => - insertSuggestion(context, suggestion), - errorBuilder: (BuildContext context, Object? error) => - const SizedBox.shrink(), - loadingBuilder: (BuildContext context) => const SizedBox.shrink(), - // fix loading briefly flickering a dark box - emptyBuilder: (BuildContext context) => - const SizedBox.shrink(), // fix loading briefly showing no suggestions + optionsViewBuilder: (c, onSelected, s) { + final suggestions = s.toList(); + return Material( + elevation: theme.appBarTheme.scrolledUnderElevation ?? 4, + shadowColor: theme.appBarTheme.shadowColor, + borderRadius: BorderRadius.circular(AppConfig.borderRadius), + clipBehavior: Clip.hardEdge, + child: ListView.builder( + shrinkWrap: true, + itemCount: suggestions.length, + itemBuilder: (context, i) => buildSuggestion( + c, + suggestions[i], + onSelected, + Matrix.of(context).client, + ), + ), + ); + }, + displayStringForOption: insertSuggestion, + optionsViewOpenDirection: OptionsViewOpenDirection.up, ); } } diff --git a/lib/pages/chat_list/chat_list.dart b/lib/pages/chat_list/chat_list.dart index 36036cdcb..59abca6b5 100644 --- a/lib/pages/chat_list/chat_list.dart +++ b/lib/pages/chat_list/chat_list.dart @@ -43,7 +43,6 @@ import '../../widgets/matrix.dart'; import 'package:fluffychat/utils/tor_stub.dart' if (dart.library.html) 'package:tor_detector_web/tor_detector_web.dart'; - enum PopupMenuAction { settings, invite, diff --git a/pubspec.yaml b/pubspec.yaml index 794269b13..c436b9997 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -47,10 +47,6 @@ dependencies: flutter_openssl_crypto: ^0.5.0 flutter_secure_storage: ^9.2.4 flutter_shortcuts_new: ^2.0.0 - flutter_typeahead: ## Custom fork from flutter_typeahead since the package is not maintain well. - git: - url: https://github.com/famedly/flutter_typeahead.git - ref: main flutter_vodozemac: ^0.2.2 flutter_web_auth_2: ^3.1.1 # Version 4 blocked by https://github.com/MixinNetwork/flutter-plugins/issues/379 flutter_webrtc: ^1.1.0 diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index f21b452d0..3fe4d1e9e 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -53,7 +53,7 @@ platforms: parts: flutter-git: source: https://github.com/flutter/flutter.git - source-tag: 3.32.4 + source-tag: 3.35.2 source-depth: 1 plugin: nil override-build: |