refactor: Replace flutter typeahead with autocomplete to fix

pull/2184/head
Christian Kußowski 3 months ago
parent 737f7306cd
commit 34492bd45e
No known key found for this signature in database
GPG Key ID: E067ECD60F1A0652

@ -2,10 +2,10 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:emojis/emoji.dart'; import 'package:emojis/emoji.dart';
import 'package:flutter_typeahead/flutter_typeahead.dart';
import 'package:matrix/matrix.dart'; import 'package:matrix/matrix.dart';
import 'package:slugify/slugify.dart'; import 'package:slugify/slugify.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/config/setting_keys.dart'; import 'package:fluffychat/config/setting_keys.dart';
import 'package:fluffychat/l10n/l10n.dart'; import 'package:fluffychat/l10n/l10n.dart';
import 'package:fluffychat/utils/markdown_context_builder.dart'; import 'package:fluffychat/utils/markdown_context_builder.dart';
@ -46,14 +46,12 @@ class InputBar extends StatelessWidget {
super.key, super.key,
}); });
List<Map<String, String?>> getSuggestions(String text) { List<Map<String, String?>> getSuggestions(TextEditingValue text) {
if (controller!.selection.baseOffset != if (text.selection.baseOffset != text.selection.extentOffset ||
controller!.selection.extentOffset || text.selection.baseOffset < 0) {
controller!.selection.baseOffset < 0) {
return []; // no entries if there is selected text return []; // no entries if there is selected text
} }
final searchText = final searchText = text.text.substring(0, text.selection.baseOffset);
controller!.text.substring(0, controller!.selection.baseOffset);
final ret = <Map<String, String?>>[]; final ret = <Map<String, String?>>[];
const maxResults = 30; const maxResults = 30;
@ -218,34 +216,29 @@ class InputBar extends StatelessWidget {
Widget buildSuggestion( Widget buildSuggestion(
BuildContext context, BuildContext context,
Map<String, String?> suggestion, Map<String, String?> suggestion,
void Function(Map<String, String?>) onSelected,
Client? client, Client? client,
) { ) {
final theme = Theme.of(context); final theme = Theme.of(context);
const size = 30.0; const size = 30.0;
const padding = EdgeInsets.all(4.0);
if (suggestion['type'] == 'command') { if (suggestion['type'] == 'command') {
final command = suggestion['name']!; final command = suggestion['name']!;
final hint = commandHint(L10n.of(context), command); final hint = commandHint(L10n.of(context), command);
return Tooltip( return Tooltip(
message: hint, message: hint,
waitDuration: const Duration(days: 1), // don't show on hover waitDuration: const Duration(days: 1), // don't show on hover
child: Container( child: ListTile(
padding: padding, onTap: () => onSelected(suggestion),
child: Column( title: Text(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
commandExample(command), commandExample(command),
style: const TextStyle(fontFamily: 'RobotoMono'), style: const TextStyle(fontFamily: 'RobotoMono'),
), ),
Text( subtitle: Text(
hint, hint,
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: theme.textTheme.bodySmall, style: theme.textTheme.bodySmall,
), ),
],
),
), ),
); );
} }
@ -254,19 +247,16 @@ class InputBar extends StatelessWidget {
return Tooltip( return Tooltip(
message: label, message: label,
waitDuration: const Duration(days: 1), // don't show on hover waitDuration: const Duration(days: 1), // don't show on hover
child: Container( child: ListTile(
padding: padding, onTap: () => onSelected(suggestion),
child: Text(label, style: const TextStyle(fontFamily: 'RobotoMono')), title: Text(label, style: const TextStyle(fontFamily: 'RobotoMono')),
), ),
); );
} }
if (suggestion['type'] == 'emote') { if (suggestion['type'] == 'emote') {
return Container( return ListTile(
padding: padding, onTap: () => onSelected(suggestion),
child: Row( leading: MxcImage(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
MxcImage(
// ensure proper ordering ... // ensure proper ordering ...
key: ValueKey(suggestion['name']), key: ValueKey(suggestion['name']),
uri: suggestion['mxc'] is String uri: suggestion['mxc'] is String
@ -276,7 +266,9 @@ class InputBar extends StatelessWidget {
height: size, height: size,
isThumbnail: false, isThumbnail: false,
), ),
const SizedBox(width: 6), title: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Text(suggestion['name']!), Text(suggestion['name']!),
Expanded( Expanded(
child: Align( child: Align(
@ -302,28 +294,22 @@ class InputBar extends StatelessWidget {
} }
if (suggestion['type'] == 'user' || suggestion['type'] == 'room') { if (suggestion['type'] == 'user' || suggestion['type'] == 'room') {
final url = Uri.parse(suggestion['avatar_url'] ?? ''); final url = Uri.parse(suggestion['avatar_url'] ?? '');
return Container( return ListTile(
padding: padding, onTap: () => onSelected(suggestion),
child: Row( leading: Avatar(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Avatar(
mxContent: url, mxContent: url,
name: suggestion.tryGet<String>('displayname') ?? name: suggestion.tryGet<String>('displayname') ??
suggestion.tryGet<String>('mxid'), suggestion.tryGet<String>('mxid'),
size: size, size: size,
client: client, client: client,
), ),
const SizedBox(width: 6), title: Text(suggestion['displayname'] ?? suggestion['mxid']!),
Text(suggestion['displayname'] ?? suggestion['mxid']!),
],
),
); );
} }
return const SizedBox.shrink(); return const SizedBox.shrink();
} }
void insertSuggestion(_, Map<String, String?> suggestion) { String insertSuggestion(Map<String, String?> suggestion) {
final replaceText = final replaceText =
controller!.text.substring(0, controller!.selection.baseOffset); controller!.text.substring(0, controller!.selection.baseOffset);
var startText = ''; var startText = '';
@ -384,27 +370,18 @@ class InputBar extends StatelessWidget {
(Match m) => '${m[1]}$insertText', (Match m) => '${m[1]}$insertText',
); );
} }
if (insertText.isNotEmpty && startText.isNotEmpty) {
controller!.text = startText + afterText; return startText + afterText;
controller!.selection = TextSelection(
baseOffset: startText.length,
extentOffset: startText.length,
);
}
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return TypeAheadField<Map<String, String?>>( final theme = Theme.of(context);
direction: VerticalDirection.up, return Autocomplete<Map<String, String?>>(
hideOnEmpty: true,
hideOnLoading: true,
controller: controller,
focusNode: focusNode, focusNode: focusNode,
hideOnSelect: false, textEditingController: controller,
debounceDuration: const Duration(milliseconds: 50), optionsBuilder: getSuggestions,
// show suggestions after 50ms idle time (default is 300) fieldViewBuilder: (context, controller, focusNode, _) => TextField(
builder: (context, controller, focusNode) => TextField(
controller: controller, controller: controller,
focusNode: focusNode, focusNode: focusNode,
readOnly: readOnly, readOnly: readOnly,
@ -448,17 +425,27 @@ class InputBar extends StatelessWidget {
}, },
textCapitalization: TextCapitalization.sentences, textCapitalization: TextCapitalization.sentences,
), ),
optionsViewBuilder: (c, onSelected, s) {
suggestionsCallback: getSuggestions, final suggestions = s.toList();
itemBuilder: (c, s) => buildSuggestion(c, s, Matrix.of(context).client), return Material(
onSelected: (Map<String, String?> suggestion) => elevation: theme.appBarTheme.scrolledUnderElevation ?? 4,
insertSuggestion(context, suggestion), shadowColor: theme.appBarTheme.shadowColor,
errorBuilder: (BuildContext context, Object? error) => borderRadius: BorderRadius.circular(AppConfig.borderRadius),
const SizedBox.shrink(), clipBehavior: Clip.hardEdge,
loadingBuilder: (BuildContext context) => const SizedBox.shrink(), child: ListView.builder(
// fix loading briefly flickering a dark box shrinkWrap: true,
emptyBuilder: (BuildContext context) => itemCount: suggestions.length,
const SizedBox.shrink(), // fix loading briefly showing no suggestions itemBuilder: (context, i) => buildSuggestion(
c,
suggestions[i],
onSelected,
Matrix.of(context).client,
),
),
);
},
displayStringForOption: insertSuggestion,
optionsViewOpenDirection: OptionsViewOpenDirection.up,
); );
} }
} }

@ -507,54 +507,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.1.1" version: "0.1.1"
flutter_keyboard_visibility:
dependency: transitive
description:
name: flutter_keyboard_visibility
sha256: "98664be7be0e3ffca00de50f7f6a287ab62c763fc8c762e0a21584584a3ff4f8"
url: "https://pub.dev"
source: hosted
version: "6.0.0"
flutter_keyboard_visibility_linux:
dependency: transitive
description:
name: flutter_keyboard_visibility_linux
sha256: "6fba7cd9bb033b6ddd8c2beb4c99ad02d728f1e6e6d9b9446667398b2ac39f08"
url: "https://pub.dev"
source: hosted
version: "1.0.0"
flutter_keyboard_visibility_macos:
dependency: transitive
description:
name: flutter_keyboard_visibility_macos
sha256: c5c49b16fff453dfdafdc16f26bdd8fb8d55812a1d50b0ce25fc8d9f2e53d086
url: "https://pub.dev"
source: hosted
version: "1.0.0"
flutter_keyboard_visibility_platform_interface:
dependency: transitive
description:
name: flutter_keyboard_visibility_platform_interface
sha256: e43a89845873f7be10cb3884345ceb9aebf00a659f479d1c8f4293fcb37022a4
url: "https://pub.dev"
source: hosted
version: "2.0.0"
flutter_keyboard_visibility_web:
dependency: transitive
description:
name: flutter_keyboard_visibility_web
sha256: d3771a2e752880c79203f8d80658401d0c998e4183edca05a149f5098ce6e3d1
url: "https://pub.dev"
source: hosted
version: "2.0.0"
flutter_keyboard_visibility_windows:
dependency: transitive
description:
name: flutter_keyboard_visibility_windows
sha256: fc4b0f0b6be9b93ae527f3d527fb56ee2d918cd88bbca438c478af7bcfd0ef73
url: "https://pub.dev"
source: hosted
version: "1.0.0"
flutter_linkify: flutter_linkify:
dependency: "direct main" dependency: "direct main"
description: description:
@ -718,15 +670,6 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
flutter_typeahead:
dependency: "direct main"
description:
path: "."
ref: main
resolved-ref: "3e209e67aa6e780cba61ced06cf49d2babbbcaa4"
url: "https://github.com/famedly/flutter_typeahead.git"
source: git
version: "5.2.0"
flutter_vodozemac: flutter_vodozemac:
dependency: "direct main" dependency: "direct main"
description: description:
@ -1415,38 +1358,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.8" version: "2.1.8"
pointer_interceptor:
dependency: transitive
description:
name: pointer_interceptor
sha256: "57210410680379aea8b1b7ed6ae0c3ad349bfd56fe845b8ea934a53344b9d523"
url: "https://pub.dev"
source: hosted
version: "0.10.1+2"
pointer_interceptor_ios:
dependency: transitive
description:
name: pointer_interceptor_ios
sha256: a6906772b3205b42c44614fcea28f818b1e5fdad73a4ca742a7bd49818d9c917
url: "https://pub.dev"
source: hosted
version: "0.10.1"
pointer_interceptor_platform_interface:
dependency: transitive
description:
name: pointer_interceptor_platform_interface
sha256: "0597b0560e14354baeb23f8375cd612e8bd4841bf8306ecb71fcd0bb78552506"
url: "https://pub.dev"
source: hosted
version: "0.10.0+1"
pointer_interceptor_web:
dependency: transitive
description:
name: pointer_interceptor_web
sha256: "7a7087782110f8c1827170660b09f8aa893e0e9a61431dbbe2ac3fc482e8c044"
url: "https://pub.dev"
source: hosted
version: "0.10.2+1"
pool: pool:
dependency: transitive dependency: transitive
description: description:

@ -39,10 +39,6 @@ dependencies:
flutter_openssl_crypto: ^0.5.0 flutter_openssl_crypto: ^0.5.0
flutter_secure_storage: ^9.2.4 flutter_secure_storage: ^9.2.4
flutter_shortcuts_new: ^2.0.0 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_vodozemac: ^0.2.2
flutter_web_auth_2: ^3.1.1 # Version 4 blocked by https://github.com/MixinNetwork/flutter-plugins/issues/379 flutter_web_auth_2: ^3.1.1 # Version 4 blocked by https://github.com/MixinNetwork/flutter-plugins/issues/379
flutter_webrtc: ^1.0.0 flutter_webrtc: ^1.0.0

Loading…
Cancel
Save