mirror of
https://github.com/foss42/apidash.git
synced 2025-05-21 08:16:29 +08:00
fix: multitriggerautocomplete + extendedtextfield
This commit is contained in:
27
lib/screens/common_widgets/env_regexp_span_builder.dart
Normal file
27
lib/screens/common_widgets/env_regexp_span_builder.dart
Normal file
@ -0,0 +1,27 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:extended_text_field/extended_text_field.dart';
|
||||
import 'package:apidash/consts.dart';
|
||||
import 'envvar_span.dart';
|
||||
|
||||
class EnvRegExpSpanBuilder extends RegExpSpecialTextSpanBuilder {
|
||||
@override
|
||||
List<RegExpSpecialText> get regExps => <RegExpSpecialText>[
|
||||
RegExpEnvText(),
|
||||
];
|
||||
}
|
||||
|
||||
class RegExpEnvText extends RegExpSpecialText {
|
||||
@override
|
||||
RegExp get regExp => kEnvVarRegEx;
|
||||
@override
|
||||
InlineSpan finishText(int start, Match match,
|
||||
{TextStyle? textStyle, SpecialTextGestureTapCallback? onTap}) {
|
||||
final String value = '${match[0]}';
|
||||
return ExtendedWidgetSpan(
|
||||
actualText: value,
|
||||
start: start,
|
||||
alignment: PlaceholderAlignment.middle,
|
||||
child: EnvVarSpan(variableKey: value.substring(2, value.length - 2)),
|
||||
);
|
||||
}
|
||||
}
|
110
lib/screens/common_widgets/env_trigger_field.dart
Normal file
110
lib/screens/common_widgets/env_trigger_field.dart
Normal file
@ -0,0 +1,110 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:multi_trigger_autocomplete/multi_trigger_autocomplete.dart';
|
||||
import 'package:extended_text_field/extended_text_field.dart';
|
||||
import 'env_regexp_span_builder.dart';
|
||||
import 'env_trigger_options.dart';
|
||||
|
||||
class EnvironmentTriggerField extends StatefulWidget {
|
||||
const EnvironmentTriggerField({
|
||||
super.key,
|
||||
required this.keyId,
|
||||
this.initialValue,
|
||||
this.onChanged,
|
||||
this.onFieldSubmitted,
|
||||
this.style,
|
||||
this.decoration,
|
||||
});
|
||||
|
||||
final String keyId;
|
||||
final String? initialValue;
|
||||
final void Function(String)? onChanged;
|
||||
final void Function(String)? onFieldSubmitted;
|
||||
final TextStyle? style;
|
||||
final InputDecoration? decoration;
|
||||
|
||||
@override
|
||||
State<EnvironmentTriggerField> createState() =>
|
||||
_EnvironmentTriggerFieldState();
|
||||
}
|
||||
|
||||
class _EnvironmentTriggerFieldState extends State<EnvironmentTriggerField> {
|
||||
final TextEditingController controller = TextEditingController();
|
||||
final FocusNode focusNode = FocusNode();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
controller.text = widget.initialValue ?? '';
|
||||
controller.selection =
|
||||
TextSelection.collapsed(offset: controller.text.length);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
controller.dispose();
|
||||
focusNode.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(EnvironmentTriggerField oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (oldWidget.initialValue != widget.initialValue) {
|
||||
controller.text = widget.initialValue ?? "";
|
||||
controller.selection =
|
||||
TextSelection.collapsed(offset: controller.text.length);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MultiTriggerAutocomplete(
|
||||
key: Key(widget.keyId),
|
||||
textEditingController: controller,
|
||||
focusNode: focusNode,
|
||||
autocompleteTriggers: [
|
||||
AutocompleteTrigger(
|
||||
trigger: '{',
|
||||
triggerEnd: "}}",
|
||||
triggerOnlyAfterSpace: false,
|
||||
optionsViewBuilder: (context, autocompleteQuery, controller) {
|
||||
return EnvironmentAutocompleteOptions(
|
||||
query: autocompleteQuery.query,
|
||||
onSuggestionTap: (suggestion) {
|
||||
final autocomplete = MultiTriggerAutocomplete.of(context);
|
||||
autocomplete.acceptAutocompleteOption(
|
||||
'{${suggestion.variable.key}',
|
||||
);
|
||||
widget.onChanged?.call(controller.text);
|
||||
});
|
||||
}),
|
||||
AutocompleteTrigger(
|
||||
trigger: '{{',
|
||||
triggerEnd: "}}",
|
||||
triggerOnlyAfterSpace: false,
|
||||
optionsViewBuilder: (context, autocompleteQuery, controller) {
|
||||
return EnvironmentAutocompleteOptions(
|
||||
query: autocompleteQuery.query,
|
||||
onSuggestionTap: (suggestion) {
|
||||
final autocomplete = MultiTriggerAutocomplete.of(context);
|
||||
autocomplete.acceptAutocompleteOption(
|
||||
suggestion.variable.key,
|
||||
);
|
||||
widget.onChanged?.call(controller.text);
|
||||
});
|
||||
}),
|
||||
],
|
||||
fieldViewBuilder: (context, textEditingController, focusnode) {
|
||||
return ExtendedTextField(
|
||||
controller: textEditingController,
|
||||
focusNode: focusnode,
|
||||
decoration: widget.decoration,
|
||||
style: widget.style,
|
||||
onChanged: widget.onChanged,
|
||||
onSubmitted: widget.onFieldSubmitted,
|
||||
specialTextSpanBuilder: EnvRegExpSpanBuilder(),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
66
lib/screens/common_widgets/env_trigger_options.dart
Normal file
66
lib/screens/common_widgets/env_trigger_options.dart
Normal file
@ -0,0 +1,66 @@
|
||||
import 'package:apidash/consts.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:apidash/models/models.dart';
|
||||
import 'package:apidash/providers/providers.dart';
|
||||
import 'package:apidash/utils/utils.dart';
|
||||
|
||||
import 'envvar_indicator.dart';
|
||||
|
||||
class EnvironmentAutocompleteOptions extends ConsumerWidget {
|
||||
const EnvironmentAutocompleteOptions({
|
||||
super.key,
|
||||
required this.query,
|
||||
required this.onSuggestionTap,
|
||||
});
|
||||
|
||||
final String query;
|
||||
final ValueSetter<EnvironmentVariableSuggestion> onSuggestionTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final envMap = ref.watch(availableEnvironmentVariablesStateProvider);
|
||||
final activeEnvironmentId = ref.watch(activeEnvironmentIdStateProvider);
|
||||
final suggestions =
|
||||
getEnvironmentTriggerSuggestions(query, envMap, activeEnvironmentId);
|
||||
return suggestions == null || suggestions.isEmpty
|
||||
? const SizedBox.shrink()
|
||||
: ClipRRect(
|
||||
borderRadius: kBorderRadius8,
|
||||
child: Material(
|
||||
type: MaterialType.card,
|
||||
elevation: 8,
|
||||
child: ConstrainedBox(
|
||||
constraints:
|
||||
const BoxConstraints(maxHeight: kSuggestionsMenuMaxHeight),
|
||||
child: Ink(
|
||||
width: kSuggestionsMenuWidth,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
borderRadius: kBorderRadius8,
|
||||
border: Border.all(
|
||||
color: Theme.of(context).colorScheme.outlineVariant,
|
||||
),
|
||||
),
|
||||
child: ListView.separated(
|
||||
shrinkWrap: true,
|
||||
itemCount: suggestions.length,
|
||||
separatorBuilder: (context, index) =>
|
||||
const Divider(height: 2),
|
||||
itemBuilder: (context, index) {
|
||||
final suggestion = suggestions[index];
|
||||
return ListTile(
|
||||
dense: true,
|
||||
leading: EnvVarIndicator(suggestion: suggestion),
|
||||
title: Text(suggestion.variable.key),
|
||||
subtitle: Text(suggestion.variable.value),
|
||||
onTap: () => onSuggestionTap(suggestion),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:apidash/consts.dart';
|
||||
import 'environment_field.dart';
|
||||
import 'env_trigger_field.dart';
|
||||
|
||||
class EnvCellField extends StatelessWidget {
|
||||
const EnvCellField({
|
||||
@ -21,7 +21,7 @@ class EnvCellField extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var clrScheme = colorScheme ?? Theme.of(context).colorScheme;
|
||||
return EnvironmentField(
|
||||
return EnvironmentTriggerField(
|
||||
keyId: keyId,
|
||||
initialValue: initialValue,
|
||||
style: kCodeStyle.copyWith(
|
||||
|
@ -1,6 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:apidash/consts.dart';
|
||||
import 'environment_field.dart';
|
||||
import 'env_trigger_field.dart';
|
||||
|
||||
class EnvURLField extends StatelessWidget {
|
||||
const EnvURLField({
|
||||
@ -18,7 +18,7 @@ class EnvURLField extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return EnvironmentField(
|
||||
return EnvironmentTriggerField(
|
||||
keyId: "url-$selectedId",
|
||||
initialValue: initialValue,
|
||||
style: kCodeStyle,
|
||||
|
@ -1,49 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:apidash/providers/providers.dart';
|
||||
import 'package:apidash/utils/envvar_utils.dart';
|
||||
import 'package:apidash/widgets/widgets.dart';
|
||||
|
||||
class EnvironmentField extends HookConsumerWidget {
|
||||
const EnvironmentField({
|
||||
super.key,
|
||||
required this.keyId,
|
||||
this.initialValue,
|
||||
this.onChanged,
|
||||
this.onFieldSubmitted,
|
||||
this.style,
|
||||
this.decoration,
|
||||
});
|
||||
|
||||
final String keyId;
|
||||
final String? initialValue;
|
||||
final void Function(String)? onChanged;
|
||||
final void Function(String)? onFieldSubmitted;
|
||||
final TextStyle? style;
|
||||
final InputDecoration? decoration;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final mentionValue = ref.watch(environmentFieldEditStateProvider);
|
||||
final envMap = ref.watch(availableEnvironmentVariablesStateProvider);
|
||||
final activeEnvironmentId = ref.watch(activeEnvironmentIdStateProvider);
|
||||
final initialMentions =
|
||||
getMentions(initialValue, envMap, activeEnvironmentId);
|
||||
final suggestions = getEnvironmentVariableSuggestions(
|
||||
mentionValue, envMap, activeEnvironmentId);
|
||||
return EnvironmentFieldBase(
|
||||
key: Key(keyId),
|
||||
mentionValue: mentionValue,
|
||||
onMentionValueChanged: (value) {
|
||||
ref.read(environmentFieldEditStateProvider.notifier).state = value;
|
||||
},
|
||||
initialValue: initialValue,
|
||||
initialMentions: initialMentions,
|
||||
suggestions: suggestions,
|
||||
onChanged: onChanged,
|
||||
onFieldSubmitted: onFieldSubmitted,
|
||||
style: style,
|
||||
decoration: decoration,
|
||||
);
|
||||
}
|
||||
}
|
@ -3,7 +3,6 @@ import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:flutter_portal/flutter_portal.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:apidash/consts.dart';
|
||||
import 'package:apidash/models/models.dart';
|
||||
import 'package:apidash/providers/providers.dart';
|
||||
import 'package:apidash/utils/utils.dart';
|
||||
import 'envvar_popover.dart';
|
||||
@ -11,10 +10,10 @@ import 'envvar_popover.dart';
|
||||
class EnvVarSpan extends HookConsumerWidget {
|
||||
const EnvVarSpan({
|
||||
super.key,
|
||||
required this.suggestion,
|
||||
required this.variableKey,
|
||||
});
|
||||
|
||||
final EnvironmentVariableSuggestion suggestion;
|
||||
final String variableKey;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
@ -22,20 +21,19 @@ class EnvVarSpan extends HookConsumerWidget {
|
||||
final envMap = ref.watch(availableEnvironmentVariablesStateProvider);
|
||||
final activeEnvironmentId = ref.watch(activeEnvironmentIdStateProvider);
|
||||
|
||||
final currentSuggestion =
|
||||
getCurrentVariableStatus(suggestion, envMap, activeEnvironmentId);
|
||||
final suggestion =
|
||||
getVariableStatus(variableKey, envMap, activeEnvironmentId);
|
||||
|
||||
final showPopover = useState(false);
|
||||
|
||||
final isMissingVariable = currentSuggestion.isUnknown;
|
||||
final isMissingVariable = suggestion.isUnknown;
|
||||
final String scope = isMissingVariable
|
||||
? 'unknown'
|
||||
: getEnvironmentTitle(
|
||||
environments?[currentSuggestion.environmentId]?.name);
|
||||
: getEnvironmentTitle(environments?[suggestion.environmentId]?.name);
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
|
||||
var text = Text(
|
||||
'{{${currentSuggestion.variable.key}}}',
|
||||
'{{${suggestion.variable.key}}}',
|
||||
style: TextStyle(
|
||||
color: isMissingVariable ? colorScheme.error : colorScheme.primary,
|
||||
fontWeight: FontWeight.w600),
|
||||
@ -50,7 +48,7 @@ class EnvVarSpan extends HookConsumerWidget {
|
||||
onExit: (_) {
|
||||
showPopover.value = false;
|
||||
},
|
||||
child: EnvVarPopover(suggestion: currentSuggestion, scope: scope),
|
||||
child: EnvVarPopover(suggestion: suggestion, scope: scope),
|
||||
),
|
||||
anchor: const Aligned(
|
||||
follower: Alignment.bottomCenter,
|
||||
|
@ -1,8 +1,6 @@
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:apidash/consts.dart';
|
||||
import 'package:apidash/models/models.dart';
|
||||
import '../screens/common_widgets/common_widgets.dart';
|
||||
|
||||
String getEnvironmentTitle(String? name) {
|
||||
if (name == null || name.trim() == "") {
|
||||
@ -92,92 +90,16 @@ HttpRequestModel substituteHttpRequestModel(
|
||||
return newRequestModel;
|
||||
}
|
||||
|
||||
List<(String, Object?, Widget?)> getMentions(
|
||||
String? text,
|
||||
Map<String, List<EnvironmentVariableModel>> envMap,
|
||||
String? activeEnvironmentId) {
|
||||
if (text == null) {
|
||||
return [];
|
||||
}
|
||||
final mentions = <(String, Object?, Widget?)>[];
|
||||
|
||||
final matches = kEnvVarRegEx.allMatches(text);
|
||||
|
||||
for (final match in matches) {
|
||||
final variableName = match.group(1);
|
||||
EnvironmentVariableModel? variable;
|
||||
String? environmentId;
|
||||
|
||||
for (final entry in envMap.entries) {
|
||||
variable = entry.value.firstWhereOrNull((v) => v.key == variableName);
|
||||
if (variable != null) {
|
||||
environmentId = entry.key;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
final suggestion = EnvironmentVariableSuggestion(
|
||||
environmentId: variable == null ? "unknown" : environmentId!,
|
||||
variable: variable ??
|
||||
kEnvironmentVariableEmptyModel.copyWith(
|
||||
key: variableName ?? "",
|
||||
),
|
||||
isUnknown: variable == null);
|
||||
mentions.add((
|
||||
'{{${variable?.key ?? variableName}}}',
|
||||
suggestion,
|
||||
EnvVarSpan(suggestion: suggestion)
|
||||
));
|
||||
}
|
||||
|
||||
return mentions;
|
||||
}
|
||||
|
||||
EnvironmentVariableSuggestion getCurrentVariableStatus(
|
||||
EnvironmentVariableSuggestion currentSuggestion,
|
||||
Map<String, List<EnvironmentVariableModel>> envMap,
|
||||
String? activeEnvironmentId) {
|
||||
if (activeEnvironmentId != null) {
|
||||
final variable = envMap[activeEnvironmentId]!
|
||||
.firstWhereOrNull((v) => v.key == currentSuggestion.variable.key);
|
||||
if (variable != null) {
|
||||
return currentSuggestion.copyWith(
|
||||
environmentId: activeEnvironmentId,
|
||||
variable: variable,
|
||||
isUnknown: false,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
final globalVariable = envMap[kGlobalEnvironmentId]
|
||||
?.firstWhereOrNull((v) => v.key == currentSuggestion.variable.key);
|
||||
if (globalVariable != null) {
|
||||
return currentSuggestion.copyWith(
|
||||
environmentId: kGlobalEnvironmentId,
|
||||
variable: globalVariable,
|
||||
isUnknown: false,
|
||||
);
|
||||
}
|
||||
|
||||
return currentSuggestion.copyWith(
|
||||
isUnknown: true,
|
||||
variable: currentSuggestion.variable.copyWith(value: "unknown"));
|
||||
}
|
||||
|
||||
List<EnvironmentVariableSuggestion>? getEnvironmentVariableSuggestions(
|
||||
List<EnvironmentVariableSuggestion>? getEnvironmentTriggerSuggestions(
|
||||
String? query,
|
||||
Map<String, List<EnvironmentVariableModel>> envMap,
|
||||
String? activeEnvironmentId) {
|
||||
if (query == null || kEnvVarRegEx.hasMatch(query)) return null;
|
||||
|
||||
query = query.substring(1);
|
||||
|
||||
final suggestions = <EnvironmentVariableSuggestion>[];
|
||||
final Set<String> addedVariableKeys = {};
|
||||
|
||||
if (activeEnvironmentId != null && envMap[activeEnvironmentId] != null) {
|
||||
for (final variable in envMap[activeEnvironmentId]!) {
|
||||
if ((query.isEmpty || variable.key.contains(query)) &&
|
||||
if ((query!.isEmpty || variable.key.contains(query)) &&
|
||||
!addedVariableKeys.contains(variable.key)) {
|
||||
suggestions.add(EnvironmentVariableSuggestion(
|
||||
environmentId: activeEnvironmentId, variable: variable));
|
||||
@ -197,3 +119,36 @@ List<EnvironmentVariableSuggestion>? getEnvironmentVariableSuggestions(
|
||||
|
||||
return suggestions;
|
||||
}
|
||||
|
||||
EnvironmentVariableSuggestion getVariableStatus(
|
||||
String key,
|
||||
Map<String, List<EnvironmentVariableModel>> envMap,
|
||||
String? activeEnvironmentId) {
|
||||
if (activeEnvironmentId != null) {
|
||||
final variable =
|
||||
envMap[activeEnvironmentId]!.firstWhereOrNull((v) => v.key == key);
|
||||
if (variable != null) {
|
||||
return EnvironmentVariableSuggestion(
|
||||
environmentId: activeEnvironmentId,
|
||||
variable: variable,
|
||||
isUnknown: false,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
final globalVariable =
|
||||
envMap[kGlobalEnvironmentId]?.firstWhereOrNull((v) => v.key == key);
|
||||
if (globalVariable != null) {
|
||||
return EnvironmentVariableSuggestion(
|
||||
environmentId: kGlobalEnvironmentId,
|
||||
variable: globalVariable,
|
||||
isUnknown: false,
|
||||
);
|
||||
}
|
||||
|
||||
return EnvironmentVariableSuggestion(
|
||||
isUnknown: true,
|
||||
environmentId: "unknown",
|
||||
variable: EnvironmentVariableModel(
|
||||
key: key, type: EnvironmentVariableType.variable, value: "unknown"));
|
||||
}
|
||||
|
@ -1,104 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:flutter_portal/flutter_portal.dart';
|
||||
import 'package:mention_tag_text_field/mention_tag_text_field.dart';
|
||||
import 'package:apidash/models/models.dart';
|
||||
import 'package:apidash/widgets/field_mention.dart';
|
||||
import '../screens/common_widgets/common_widgets.dart';
|
||||
import 'suggestions_menu.dart';
|
||||
|
||||
class EnvironmentFieldBase extends StatefulHookWidget {
|
||||
const EnvironmentFieldBase({
|
||||
super.key,
|
||||
this.initialValue,
|
||||
this.onChanged,
|
||||
this.onFieldSubmitted,
|
||||
this.style,
|
||||
this.decoration,
|
||||
this.initialMentions,
|
||||
this.suggestions,
|
||||
this.mentionValue,
|
||||
required this.onMentionValueChanged,
|
||||
});
|
||||
|
||||
final String? initialValue;
|
||||
final void Function(String)? onChanged;
|
||||
final void Function(String)? onFieldSubmitted;
|
||||
final TextStyle? style;
|
||||
final InputDecoration? decoration;
|
||||
final List<(String, Object?, Widget?)>? initialMentions;
|
||||
final List<EnvironmentVariableSuggestion>? suggestions;
|
||||
final String? mentionValue;
|
||||
final void Function(String?) onMentionValueChanged;
|
||||
|
||||
@override
|
||||
State<EnvironmentFieldBase> createState() =>
|
||||
_EnvironmentAutocompleteFieldBaseState();
|
||||
}
|
||||
|
||||
class _EnvironmentAutocompleteFieldBaseState
|
||||
extends State<EnvironmentFieldBase> {
|
||||
final MentionTagTextEditingController controller =
|
||||
MentionTagTextEditingController();
|
||||
|
||||
final FocusNode focusNode = FocusNode();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
controller.text = widget.initialValue ?? "";
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isSuggestionsVisible = useState(false);
|
||||
|
||||
return PortalTarget(
|
||||
visible: isSuggestionsVisible.value && focusNode.hasFocus,
|
||||
portalFollower: SuggestionsMenu(
|
||||
mentionController: controller,
|
||||
suggestions: widget.suggestions,
|
||||
suggestionBuilder: (context, index) {
|
||||
final suggestion = widget.suggestions![index];
|
||||
return ListTile(
|
||||
dense: true,
|
||||
leading: EnvVarIndicator(suggestion: suggestion),
|
||||
title: Text(suggestion.variable.key),
|
||||
subtitle: Text(suggestion.variable.value),
|
||||
onTap: () {
|
||||
controller.addMention(
|
||||
label: '{${suggestion.variable.key}}}',
|
||||
data: suggestion,
|
||||
stylingWidget: EnvVarSpan(suggestion: suggestion));
|
||||
widget.onChanged?.call(controller.text);
|
||||
widget.onMentionValueChanged.call(null);
|
||||
isSuggestionsVisible.value = false;
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
anchor: const Aligned(
|
||||
follower: Alignment.topLeft,
|
||||
target: Alignment.bottomLeft,
|
||||
backup: Aligned(
|
||||
follower: Alignment.bottomLeft,
|
||||
target: Alignment.topLeft,
|
||||
),
|
||||
),
|
||||
child: MentionField(
|
||||
focusNode: focusNode,
|
||||
controller: controller,
|
||||
initialMentions: widget.initialMentions ?? [],
|
||||
mentionValue: widget.mentionValue,
|
||||
onMentionValueChanged: widget.onMentionValueChanged,
|
||||
isSuggestionsVisible: isSuggestionsVisible,
|
||||
onChanged: widget.onChanged,
|
||||
onFieldSubmitted: widget.onFieldSubmitted,
|
||||
style: widget.style,
|
||||
decoration: widget.decoration,
|
||||
mentionStart: const ['{'],
|
||||
maxWords: 1,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,85 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:mention_tag_text_field/mention_tag_text_field.dart';
|
||||
|
||||
class MentionField extends StatelessWidget {
|
||||
const MentionField({
|
||||
super.key,
|
||||
required this.focusNode,
|
||||
required this.controller,
|
||||
required this.initialMentions,
|
||||
required this.mentionValue,
|
||||
required this.onMentionValueChanged,
|
||||
required this.isSuggestionsVisible,
|
||||
this.onChanged,
|
||||
this.onFieldSubmitted,
|
||||
this.style,
|
||||
this.decoration,
|
||||
required this.mentionStart,
|
||||
this.mentionBreak = "",
|
||||
this.maxWords,
|
||||
this.allowDecrement = false,
|
||||
this.allowEmbedding = true,
|
||||
this.showMentionStartSymbol = false,
|
||||
});
|
||||
|
||||
final FocusNode focusNode;
|
||||
final MentionTagTextEditingController controller;
|
||||
final List<(String, Object?, Widget?)> initialMentions;
|
||||
final String? mentionValue;
|
||||
final void Function(String?) onMentionValueChanged;
|
||||
final ValueNotifier<bool> isSuggestionsVisible;
|
||||
final void Function(String)? onChanged;
|
||||
final void Function(String)? onFieldSubmitted;
|
||||
final TextStyle? style;
|
||||
final InputDecoration? decoration;
|
||||
final List<String> mentionStart;
|
||||
final String mentionBreak;
|
||||
final int? maxWords;
|
||||
final bool allowDecrement;
|
||||
final bool allowEmbedding;
|
||||
final bool showMentionStartSymbol;
|
||||
|
||||
void onMention(String? value) {
|
||||
onMentionValueChanged.call(value);
|
||||
if (value != null) {
|
||||
isSuggestionsVisible.value = true;
|
||||
} else {
|
||||
isSuggestionsVisible.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MentionTagTextField(
|
||||
focusNode: focusNode,
|
||||
onTap: () {
|
||||
focusNode.requestFocus();
|
||||
},
|
||||
onTapOutside: (event) {
|
||||
focusNode.unfocus();
|
||||
isSuggestionsVisible.value = false;
|
||||
},
|
||||
controller: controller,
|
||||
style: style,
|
||||
initialMentions: initialMentions,
|
||||
onMention: onMention,
|
||||
onChanged: (value) {
|
||||
onChanged?.call(controller.text);
|
||||
},
|
||||
onEditingComplete: () {
|
||||
focusNode.unfocus();
|
||||
onFieldSubmitted?.call(controller.text);
|
||||
isSuggestionsVisible.value = false;
|
||||
},
|
||||
decoration: decoration,
|
||||
mentionTagDecoration: MentionTagDecoration(
|
||||
mentionStart: mentionStart,
|
||||
mentionBreak: mentionBreak,
|
||||
maxWords: maxWords,
|
||||
allowDecrement: allowDecrement,
|
||||
allowEmbedding: allowEmbedding,
|
||||
showMentionStartSymbol: showMentionStartSymbol,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,53 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:mention_tag_text_field/mention_tag_text_field.dart';
|
||||
import 'package:apidash/consts.dart';
|
||||
|
||||
class SuggestionsMenu extends StatelessWidget {
|
||||
const SuggestionsMenu({
|
||||
super.key,
|
||||
required this.mentionController,
|
||||
required this.suggestions,
|
||||
required this.suggestionBuilder,
|
||||
this.menuWidth = kSuggestionsMenuWidth,
|
||||
this.menuMaxHeight = kSuggestionsMenuMaxHeight,
|
||||
});
|
||||
|
||||
final MentionTagTextEditingController mentionController;
|
||||
final List? suggestions;
|
||||
final double menuWidth;
|
||||
final double menuMaxHeight;
|
||||
final Widget? Function(BuildContext, int) suggestionBuilder;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return suggestions == null || suggestions!.isEmpty
|
||||
? const SizedBox.shrink()
|
||||
: ClipRRect(
|
||||
borderRadius: kBorderRadius8,
|
||||
child: Material(
|
||||
type: MaterialType.card,
|
||||
elevation: 8,
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(maxHeight: menuMaxHeight),
|
||||
child: Ink(
|
||||
width: menuWidth,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
borderRadius: kBorderRadius8,
|
||||
border: Border.all(
|
||||
color: Theme.of(context).colorScheme.outlineVariant,
|
||||
),
|
||||
),
|
||||
child: ListView.separated(
|
||||
shrinkWrap: true,
|
||||
itemCount: suggestions?.length ?? 0,
|
||||
separatorBuilder: (context, index) =>
|
||||
const Divider(height: 2),
|
||||
itemBuilder: suggestionBuilder,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -17,13 +17,11 @@ export 'dropdown_formdata.dart';
|
||||
export 'dropdown_http_method.dart';
|
||||
export 'editor_json.dart';
|
||||
export 'editor.dart';
|
||||
export 'environment_field_base.dart';
|
||||
export 'error_message.dart';
|
||||
export 'field_cell_obscurable.dart';
|
||||
export 'field_cell.dart';
|
||||
export 'field_header.dart';
|
||||
export 'field_json_search.dart';
|
||||
export 'field_mention.dart';
|
||||
export 'field_raw.dart';
|
||||
export 'field_url.dart';
|
||||
export 'intro_message.dart';
|
||||
@ -41,7 +39,6 @@ export 'snackbars.dart';
|
||||
export 'splitview_drawer.dart';
|
||||
export 'splitview_dashboard.dart';
|
||||
export 'splitview_equal.dart';
|
||||
export 'suggestions_menu.dart';
|
||||
export 'tables.dart';
|
||||
export 'tabs.dart';
|
||||
export 'texts.dart';
|
||||
|
19
pubspec.lock
19
pubspec.lock
@ -258,7 +258,7 @@ packages:
|
||||
source: hosted
|
||||
version: "1.0.1"
|
||||
extended_text_field:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: extended_text_field
|
||||
sha256: "954c7eea1e82728a742f7ddf09b9a51cef087d4f52b716ba88cb3eb78ccd7c6e"
|
||||
@ -817,14 +817,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.8.0"
|
||||
mention_tag_text_field:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: mention_tag_text_field
|
||||
sha256: "9768a0a6fe5771cb8eb98f94b26b4c595ca2487b0eb28b9d5624f8d71a2ac17a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.0.5"
|
||||
meta:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -865,6 +857,15 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.2.1"
|
||||
multi_trigger_autocomplete:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "."
|
||||
ref: master
|
||||
resolved-ref: "7ca8778e47f80c913cee33a3a01447039acc6936"
|
||||
url: "https://github.com/DenserMeerkat/multi_trigger_autocomplete.git"
|
||||
source: git
|
||||
version: "1.0.1"
|
||||
nested:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -63,7 +63,11 @@ dependencies:
|
||||
hooks_riverpod: ^2.5.1
|
||||
flutter_hooks: ^0.20.5
|
||||
flutter_portal: ^1.1.4
|
||||
mention_tag_text_field: ^0.0.5
|
||||
multi_trigger_autocomplete:
|
||||
git:
|
||||
url: https://github.com/DenserMeerkat/multi_trigger_autocomplete.git
|
||||
ref: master
|
||||
extended_text_field: ^15.0.0
|
||||
|
||||
dependency_overrides:
|
||||
web: ^0.5.0
|
||||
|
@ -15,12 +15,12 @@ import 'package:apidash/screens/intro_page.dart';
|
||||
import 'package:apidash/screens/settings_page.dart';
|
||||
import 'package:apidash/services/hive_services.dart';
|
||||
import 'package:apidash/widgets/widgets.dart';
|
||||
import 'package:extended_text_field/extended_text_field.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_portal/flutter_portal.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mention_tag_text_field/mention_tag_text_field.dart';
|
||||
|
||||
import '../extensions/widget_tester_extensions.dart';
|
||||
import '../test_consts.dart';
|
||||
@ -446,7 +446,7 @@ void main() {
|
||||
// Add some data in URLTextField
|
||||
Finder field = find.descendant(
|
||||
of: find.byType(EnvURLField),
|
||||
matching: find.byType(MentionTagTextField),
|
||||
matching: find.byType(ExtendedTextField),
|
||||
);
|
||||
await tester.enterText(field, kTestUrl);
|
||||
await tester.pump();
|
||||
@ -493,7 +493,7 @@ void main() {
|
||||
// Add some data in URLTextField
|
||||
Finder field = find.descendant(
|
||||
of: find.byType(EnvURLField),
|
||||
matching: find.byType(MentionTagTextField),
|
||||
matching: find.byType(ExtendedTextField),
|
||||
);
|
||||
await tester.enterText(field, kTestUrl);
|
||||
await tester.pump();
|
||||
@ -544,7 +544,7 @@ void main() {
|
||||
// Add some data in URLTextField
|
||||
Finder field = find.descendant(
|
||||
of: find.byType(EnvURLField),
|
||||
matching: find.byType(MentionTagTextField),
|
||||
matching: find.byType(ExtendedTextField),
|
||||
);
|
||||
await tester.enterText(field, kTestUrl);
|
||||
await tester.pump();
|
||||
@ -604,7 +604,7 @@ void main() {
|
||||
// Add some data in URLTextField
|
||||
Finder field = find.descendant(
|
||||
of: find.byType(EnvURLField),
|
||||
matching: find.byType(MentionTagTextField),
|
||||
matching: find.byType(ExtendedTextField),
|
||||
);
|
||||
await tester.enterText(field, kTestUrl);
|
||||
await tester.pump();
|
||||
|
Reference in New Issue
Block a user