mirror of
https://github.com/foss42/apidash.git
synced 2025-05-21 16:26:37 +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:flutter/material.dart';
|
||||||
import 'package:apidash/consts.dart';
|
import 'package:apidash/consts.dart';
|
||||||
import 'environment_field.dart';
|
import 'env_trigger_field.dart';
|
||||||
|
|
||||||
class EnvCellField extends StatelessWidget {
|
class EnvCellField extends StatelessWidget {
|
||||||
const EnvCellField({
|
const EnvCellField({
|
||||||
@ -21,7 +21,7 @@ class EnvCellField extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var clrScheme = colorScheme ?? Theme.of(context).colorScheme;
|
var clrScheme = colorScheme ?? Theme.of(context).colorScheme;
|
||||||
return EnvironmentField(
|
return EnvironmentTriggerField(
|
||||||
keyId: keyId,
|
keyId: keyId,
|
||||||
initialValue: initialValue,
|
initialValue: initialValue,
|
||||||
style: kCodeStyle.copyWith(
|
style: kCodeStyle.copyWith(
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:apidash/consts.dart';
|
import 'package:apidash/consts.dart';
|
||||||
import 'environment_field.dart';
|
import 'env_trigger_field.dart';
|
||||||
|
|
||||||
class EnvURLField extends StatelessWidget {
|
class EnvURLField extends StatelessWidget {
|
||||||
const EnvURLField({
|
const EnvURLField({
|
||||||
@ -18,7 +18,7 @@ class EnvURLField extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return EnvironmentField(
|
return EnvironmentTriggerField(
|
||||||
keyId: "url-$selectedId",
|
keyId: "url-$selectedId",
|
||||||
initialValue: initialValue,
|
initialValue: initialValue,
|
||||||
style: kCodeStyle,
|
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:flutter_portal/flutter_portal.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:apidash/consts.dart';
|
import 'package:apidash/consts.dart';
|
||||||
import 'package:apidash/models/models.dart';
|
|
||||||
import 'package:apidash/providers/providers.dart';
|
import 'package:apidash/providers/providers.dart';
|
||||||
import 'package:apidash/utils/utils.dart';
|
import 'package:apidash/utils/utils.dart';
|
||||||
import 'envvar_popover.dart';
|
import 'envvar_popover.dart';
|
||||||
@ -11,10 +10,10 @@ import 'envvar_popover.dart';
|
|||||||
class EnvVarSpan extends HookConsumerWidget {
|
class EnvVarSpan extends HookConsumerWidget {
|
||||||
const EnvVarSpan({
|
const EnvVarSpan({
|
||||||
super.key,
|
super.key,
|
||||||
required this.suggestion,
|
required this.variableKey,
|
||||||
});
|
});
|
||||||
|
|
||||||
final EnvironmentVariableSuggestion suggestion;
|
final String variableKey;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
@ -22,20 +21,19 @@ class EnvVarSpan extends HookConsumerWidget {
|
|||||||
final envMap = ref.watch(availableEnvironmentVariablesStateProvider);
|
final envMap = ref.watch(availableEnvironmentVariablesStateProvider);
|
||||||
final activeEnvironmentId = ref.watch(activeEnvironmentIdStateProvider);
|
final activeEnvironmentId = ref.watch(activeEnvironmentIdStateProvider);
|
||||||
|
|
||||||
final currentSuggestion =
|
final suggestion =
|
||||||
getCurrentVariableStatus(suggestion, envMap, activeEnvironmentId);
|
getVariableStatus(variableKey, envMap, activeEnvironmentId);
|
||||||
|
|
||||||
final showPopover = useState(false);
|
final showPopover = useState(false);
|
||||||
|
|
||||||
final isMissingVariable = currentSuggestion.isUnknown;
|
final isMissingVariable = suggestion.isUnknown;
|
||||||
final String scope = isMissingVariable
|
final String scope = isMissingVariable
|
||||||
? 'unknown'
|
? 'unknown'
|
||||||
: getEnvironmentTitle(
|
: getEnvironmentTitle(environments?[suggestion.environmentId]?.name);
|
||||||
environments?[currentSuggestion.environmentId]?.name);
|
|
||||||
final colorScheme = Theme.of(context).colorScheme;
|
final colorScheme = Theme.of(context).colorScheme;
|
||||||
|
|
||||||
var text = Text(
|
var text = Text(
|
||||||
'{{${currentSuggestion.variable.key}}}',
|
'{{${suggestion.variable.key}}}',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: isMissingVariable ? colorScheme.error : colorScheme.primary,
|
color: isMissingVariable ? colorScheme.error : colorScheme.primary,
|
||||||
fontWeight: FontWeight.w600),
|
fontWeight: FontWeight.w600),
|
||||||
@ -50,7 +48,7 @@ class EnvVarSpan extends HookConsumerWidget {
|
|||||||
onExit: (_) {
|
onExit: (_) {
|
||||||
showPopover.value = false;
|
showPopover.value = false;
|
||||||
},
|
},
|
||||||
child: EnvVarPopover(suggestion: currentSuggestion, scope: scope),
|
child: EnvVarPopover(suggestion: suggestion, scope: scope),
|
||||||
),
|
),
|
||||||
anchor: const Aligned(
|
anchor: const Aligned(
|
||||||
follower: Alignment.bottomCenter,
|
follower: Alignment.bottomCenter,
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:apidash/consts.dart';
|
import 'package:apidash/consts.dart';
|
||||||
import 'package:apidash/models/models.dart';
|
import 'package:apidash/models/models.dart';
|
||||||
import '../screens/common_widgets/common_widgets.dart';
|
|
||||||
|
|
||||||
String getEnvironmentTitle(String? name) {
|
String getEnvironmentTitle(String? name) {
|
||||||
if (name == null || name.trim() == "") {
|
if (name == null || name.trim() == "") {
|
||||||
@ -92,92 +90,16 @@ HttpRequestModel substituteHttpRequestModel(
|
|||||||
return newRequestModel;
|
return newRequestModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<(String, Object?, Widget?)> getMentions(
|
List<EnvironmentVariableSuggestion>? getEnvironmentTriggerSuggestions(
|
||||||
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(
|
|
||||||
String? query,
|
String? query,
|
||||||
Map<String, List<EnvironmentVariableModel>> envMap,
|
Map<String, List<EnvironmentVariableModel>> envMap,
|
||||||
String? activeEnvironmentId) {
|
String? activeEnvironmentId) {
|
||||||
if (query == null || kEnvVarRegEx.hasMatch(query)) return null;
|
|
||||||
|
|
||||||
query = query.substring(1);
|
|
||||||
|
|
||||||
final suggestions = <EnvironmentVariableSuggestion>[];
|
final suggestions = <EnvironmentVariableSuggestion>[];
|
||||||
final Set<String> addedVariableKeys = {};
|
final Set<String> addedVariableKeys = {};
|
||||||
|
|
||||||
if (activeEnvironmentId != null && envMap[activeEnvironmentId] != null) {
|
if (activeEnvironmentId != null && envMap[activeEnvironmentId] != null) {
|
||||||
for (final variable in envMap[activeEnvironmentId]!) {
|
for (final variable in envMap[activeEnvironmentId]!) {
|
||||||
if ((query.isEmpty || variable.key.contains(query)) &&
|
if ((query!.isEmpty || variable.key.contains(query)) &&
|
||||||
!addedVariableKeys.contains(variable.key)) {
|
!addedVariableKeys.contains(variable.key)) {
|
||||||
suggestions.add(EnvironmentVariableSuggestion(
|
suggestions.add(EnvironmentVariableSuggestion(
|
||||||
environmentId: activeEnvironmentId, variable: variable));
|
environmentId: activeEnvironmentId, variable: variable));
|
||||||
@ -197,3 +119,36 @@ List<EnvironmentVariableSuggestion>? getEnvironmentVariableSuggestions(
|
|||||||
|
|
||||||
return suggestions;
|
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 'dropdown_http_method.dart';
|
||||||
export 'editor_json.dart';
|
export 'editor_json.dart';
|
||||||
export 'editor.dart';
|
export 'editor.dart';
|
||||||
export 'environment_field_base.dart';
|
|
||||||
export 'error_message.dart';
|
export 'error_message.dart';
|
||||||
export 'field_cell_obscurable.dart';
|
export 'field_cell_obscurable.dart';
|
||||||
export 'field_cell.dart';
|
export 'field_cell.dart';
|
||||||
export 'field_header.dart';
|
export 'field_header.dart';
|
||||||
export 'field_json_search.dart';
|
export 'field_json_search.dart';
|
||||||
export 'field_mention.dart';
|
|
||||||
export 'field_raw.dart';
|
export 'field_raw.dart';
|
||||||
export 'field_url.dart';
|
export 'field_url.dart';
|
||||||
export 'intro_message.dart';
|
export 'intro_message.dart';
|
||||||
@ -41,7 +39,6 @@ export 'snackbars.dart';
|
|||||||
export 'splitview_drawer.dart';
|
export 'splitview_drawer.dart';
|
||||||
export 'splitview_dashboard.dart';
|
export 'splitview_dashboard.dart';
|
||||||
export 'splitview_equal.dart';
|
export 'splitview_equal.dart';
|
||||||
export 'suggestions_menu.dart';
|
|
||||||
export 'tables.dart';
|
export 'tables.dart';
|
||||||
export 'tabs.dart';
|
export 'tabs.dart';
|
||||||
export 'texts.dart';
|
export 'texts.dart';
|
||||||
|
19
pubspec.lock
19
pubspec.lock
@ -258,7 +258,7 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.1"
|
version: "1.0.1"
|
||||||
extended_text_field:
|
extended_text_field:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: extended_text_field
|
name: extended_text_field
|
||||||
sha256: "954c7eea1e82728a742f7ddf09b9a51cef087d4f52b716ba88cb3eb78ccd7c6e"
|
sha256: "954c7eea1e82728a742f7ddf09b9a51cef087d4f52b716ba88cb3eb78ccd7c6e"
|
||||||
@ -817,14 +817,6 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.8.0"
|
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:
|
meta:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -865,6 +857,15 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.2.1"
|
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:
|
nested:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -63,7 +63,11 @@ dependencies:
|
|||||||
hooks_riverpod: ^2.5.1
|
hooks_riverpod: ^2.5.1
|
||||||
flutter_hooks: ^0.20.5
|
flutter_hooks: ^0.20.5
|
||||||
flutter_portal: ^1.1.4
|
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:
|
dependency_overrides:
|
||||||
web: ^0.5.0
|
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/screens/settings_page.dart';
|
||||||
import 'package:apidash/services/hive_services.dart';
|
import 'package:apidash/services/hive_services.dart';
|
||||||
import 'package:apidash/widgets/widgets.dart';
|
import 'package:apidash/widgets/widgets.dart';
|
||||||
|
import 'package:extended_text_field/extended_text_field.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_portal/flutter_portal.dart';
|
import 'package:flutter_portal/flutter_portal.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:flutter_test/flutter_test.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 '../extensions/widget_tester_extensions.dart';
|
||||||
import '../test_consts.dart';
|
import '../test_consts.dart';
|
||||||
@ -446,7 +446,7 @@ void main() {
|
|||||||
// Add some data in URLTextField
|
// Add some data in URLTextField
|
||||||
Finder field = find.descendant(
|
Finder field = find.descendant(
|
||||||
of: find.byType(EnvURLField),
|
of: find.byType(EnvURLField),
|
||||||
matching: find.byType(MentionTagTextField),
|
matching: find.byType(ExtendedTextField),
|
||||||
);
|
);
|
||||||
await tester.enterText(field, kTestUrl);
|
await tester.enterText(field, kTestUrl);
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
@ -493,7 +493,7 @@ void main() {
|
|||||||
// Add some data in URLTextField
|
// Add some data in URLTextField
|
||||||
Finder field = find.descendant(
|
Finder field = find.descendant(
|
||||||
of: find.byType(EnvURLField),
|
of: find.byType(EnvURLField),
|
||||||
matching: find.byType(MentionTagTextField),
|
matching: find.byType(ExtendedTextField),
|
||||||
);
|
);
|
||||||
await tester.enterText(field, kTestUrl);
|
await tester.enterText(field, kTestUrl);
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
@ -544,7 +544,7 @@ void main() {
|
|||||||
// Add some data in URLTextField
|
// Add some data in URLTextField
|
||||||
Finder field = find.descendant(
|
Finder field = find.descendant(
|
||||||
of: find.byType(EnvURLField),
|
of: find.byType(EnvURLField),
|
||||||
matching: find.byType(MentionTagTextField),
|
matching: find.byType(ExtendedTextField),
|
||||||
);
|
);
|
||||||
await tester.enterText(field, kTestUrl);
|
await tester.enterText(field, kTestUrl);
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
@ -604,7 +604,7 @@ void main() {
|
|||||||
// Add some data in URLTextField
|
// Add some data in URLTextField
|
||||||
Finder field = find.descendant(
|
Finder field = find.descendant(
|
||||||
of: find.byType(EnvURLField),
|
of: find.byType(EnvURLField),
|
||||||
matching: find.byType(MentionTagTextField),
|
matching: find.byType(ExtendedTextField),
|
||||||
);
|
);
|
||||||
await tester.enterText(field, kTestUrl);
|
await tester.enterText(field, kTestUrl);
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
|
Reference in New Issue
Block a user