refactor: Replace flutter typeahead with autocomplete to fix
This commit is contained in:
parent
737f7306cd
commit
34492bd45e
3 changed files with 71 additions and 177 deletions
|
|
@ -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/config/setting_keys.dart';
|
||||
import 'package:fluffychat/l10n/l10n.dart';
|
||||
import 'package:fluffychat/utils/markdown_context_builder.dart';
|
||||
|
|
@ -46,14 +46,12 @@ class InputBar extends StatelessWidget {
|
|||
super.key,
|
||||
});
|
||||
|
||||
List<Map<String, String?>> getSuggestions(String text) {
|
||||
if (controller!.selection.baseOffset !=
|
||||
controller!.selection.extentOffset ||
|
||||
controller!.selection.baseOffset < 0) {
|
||||
List<Map<String, String?>> 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 = <Map<String, String?>>[];
|
||||
const maxResults = 30;
|
||||
|
||||
|
|
@ -218,33 +216,28 @@ class InputBar extends StatelessWidget {
|
|||
Widget buildSuggestion(
|
||||
BuildContext context,
|
||||
Map<String, String?> suggestion,
|
||||
void Function(Map<String, String?>) 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,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
@ -254,29 +247,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: <Widget>[
|
||||
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(
|
||||
|
|
@ -302,28 +294,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: <Widget>[
|
||||
Avatar(
|
||||
mxContent: url,
|
||||
name: suggestion.tryGet<String>('displayname') ??
|
||||
suggestion.tryGet<String>('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<String>('displayname') ??
|
||||
suggestion.tryGet<String>('mxid'),
|
||||
size: size,
|
||||
client: client,
|
||||
),
|
||||
title: Text(suggestion['displayname'] ?? suggestion['mxid']!),
|
||||
);
|
||||
}
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
void insertSuggestion(_, Map<String, String?> suggestion) {
|
||||
String insertSuggestion(Map<String, String?> suggestion) {
|
||||
final replaceText =
|
||||
controller!.text.substring(0, controller!.selection.baseOffset);
|
||||
var startText = '';
|
||||
|
|
@ -384,27 +370,18 @@ 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;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return TypeAheadField<Map<String, String?>>(
|
||||
direction: VerticalDirection.up,
|
||||
hideOnEmpty: true,
|
||||
hideOnLoading: true,
|
||||
controller: controller,
|
||||
final theme = Theme.of(context);
|
||||
return Autocomplete<Map<String, String?>>(
|
||||
focusNode: focusNode,
|
||||
hideOnSelect: false,
|
||||
debounceDuration: const Duration(milliseconds: 50),
|
||||
// show suggestions after 50ms idle time (default is 300)
|
||||
builder: (context, controller, focusNode) => TextField(
|
||||
textEditingController: controller,
|
||||
optionsBuilder: getSuggestions,
|
||||
fieldViewBuilder: (context, controller, focusNode, _) => TextField(
|
||||
controller: controller,
|
||||
focusNode: focusNode,
|
||||
readOnly: readOnly,
|
||||
|
|
@ -448,17 +425,27 @@ class InputBar extends StatelessWidget {
|
|||
},
|
||||
textCapitalization: TextCapitalization.sentences,
|
||||
),
|
||||
|
||||
suggestionsCallback: getSuggestions,
|
||||
itemBuilder: (c, s) => buildSuggestion(c, s, Matrix.of(context).client),
|
||||
onSelected: (Map<String, String?> 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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
89
pubspec.lock
89
pubspec.lock
|
|
@ -507,54 +507,6 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
|
@ -718,15 +670,6 @@ packages:
|
|||
description: flutter
|
||||
source: sdk
|
||||
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:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
|
@ -1415,38 +1358,6 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
|
|||
|
|
@ -39,10 +39,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.0.0
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue