From 0e87fc90fed24c6b7d876ad00c65ca2082f66653 Mon Sep 17 00:00:00 2001
From: DenserMeerkat <mragulmanoharan@gmail.com>
Date: Thu, 11 Jul 2024 21:58:12 +0530
Subject: [PATCH 1/6] fix: multitriggerautocomplete + extendedtextfield

---
 .../env_regexp_span_builder.dart              |  27 ++++
 .../common_widgets/env_trigger_field.dart     | 110 +++++++++++++++++
 .../common_widgets/env_trigger_options.dart   |  66 ++++++++++
 lib/screens/common_widgets/envfield_cell.dart |   4 +-
 lib/screens/common_widgets/envfield_url.dart  |   4 +-
 .../common_widgets/environment_field.dart     |  49 --------
 lib/screens/common_widgets/envvar_span.dart   |  18 ++-
 lib/utils/envvar_utils.dart                   | 115 ++++++------------
 lib/widgets/environment_field_base.dart       | 104 ----------------
 lib/widgets/field_mention.dart                |  85 -------------
 lib/widgets/suggestions_menu.dart             |  53 --------
 lib/widgets/widgets.dart                      |   3 -
 pubspec.lock                                  |  19 +--
 pubspec.yaml                                  |   6 +-
 test/providers/ui_providers_test.dart         |  10 +-
 15 files changed, 270 insertions(+), 403 deletions(-)
 create mode 100644 lib/screens/common_widgets/env_regexp_span_builder.dart
 create mode 100644 lib/screens/common_widgets/env_trigger_field.dart
 create mode 100644 lib/screens/common_widgets/env_trigger_options.dart
 delete mode 100644 lib/screens/common_widgets/environment_field.dart
 delete mode 100644 lib/widgets/environment_field_base.dart
 delete mode 100644 lib/widgets/field_mention.dart
 delete mode 100644 lib/widgets/suggestions_menu.dart

diff --git a/lib/screens/common_widgets/env_regexp_span_builder.dart b/lib/screens/common_widgets/env_regexp_span_builder.dart
new file mode 100644
index 00000000..25124cb4
--- /dev/null
+++ b/lib/screens/common_widgets/env_regexp_span_builder.dart
@@ -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)),
+    );
+  }
+}
diff --git a/lib/screens/common_widgets/env_trigger_field.dart b/lib/screens/common_widgets/env_trigger_field.dart
new file mode 100644
index 00000000..cfc12455
--- /dev/null
+++ b/lib/screens/common_widgets/env_trigger_field.dart
@@ -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(),
+        );
+      },
+    );
+  }
+}
diff --git a/lib/screens/common_widgets/env_trigger_options.dart b/lib/screens/common_widgets/env_trigger_options.dart
new file mode 100644
index 00000000..e6550503
--- /dev/null
+++ b/lib/screens/common_widgets/env_trigger_options.dart
@@ -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),
+                      );
+                    },
+                  ),
+                ),
+              ),
+            ),
+          );
+  }
+}
diff --git a/lib/screens/common_widgets/envfield_cell.dart b/lib/screens/common_widgets/envfield_cell.dart
index a2593af6..609a2ea8 100644
--- a/lib/screens/common_widgets/envfield_cell.dart
+++ b/lib/screens/common_widgets/envfield_cell.dart
@@ -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(
diff --git a/lib/screens/common_widgets/envfield_url.dart b/lib/screens/common_widgets/envfield_url.dart
index d4982f5b..5d43e914 100644
--- a/lib/screens/common_widgets/envfield_url.dart
+++ b/lib/screens/common_widgets/envfield_url.dart
@@ -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,
diff --git a/lib/screens/common_widgets/environment_field.dart b/lib/screens/common_widgets/environment_field.dart
deleted file mode 100644
index 21188030..00000000
--- a/lib/screens/common_widgets/environment_field.dart
+++ /dev/null
@@ -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,
-    );
-  }
-}
diff --git a/lib/screens/common_widgets/envvar_span.dart b/lib/screens/common_widgets/envvar_span.dart
index 5c698db2..c9038d28 100644
--- a/lib/screens/common_widgets/envvar_span.dart
+++ b/lib/screens/common_widgets/envvar_span.dart
@@ -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,
diff --git a/lib/utils/envvar_utils.dart b/lib/utils/envvar_utils.dart
index f3157dce..aec71567 100644
--- a/lib/utils/envvar_utils.dart
+++ b/lib/utils/envvar_utils.dart
@@ -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"));
+}
diff --git a/lib/widgets/environment_field_base.dart b/lib/widgets/environment_field_base.dart
deleted file mode 100644
index fee6f587..00000000
--- a/lib/widgets/environment_field_base.dart
+++ /dev/null
@@ -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,
-      ),
-    );
-  }
-}
diff --git a/lib/widgets/field_mention.dart b/lib/widgets/field_mention.dart
deleted file mode 100644
index cfad0f2f..00000000
--- a/lib/widgets/field_mention.dart
+++ /dev/null
@@ -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,
-      ),
-    );
-  }
-}
diff --git a/lib/widgets/suggestions_menu.dart b/lib/widgets/suggestions_menu.dart
deleted file mode 100644
index a6ab063d..00000000
--- a/lib/widgets/suggestions_menu.dart
+++ /dev/null
@@ -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,
-                  ),
-                ),
-              ),
-            ),
-          );
-  }
-}
diff --git a/lib/widgets/widgets.dart b/lib/widgets/widgets.dart
index 6b480b52..efd56fa5 100644
--- a/lib/widgets/widgets.dart
+++ b/lib/widgets/widgets.dart
@@ -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';
diff --git a/pubspec.lock b/pubspec.lock
index 13ee81cf..f74a370d 100644
--- a/pubspec.lock
+++ b/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:
diff --git a/pubspec.yaml b/pubspec.yaml
index 883128b2..92cf4eb8 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -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
diff --git a/test/providers/ui_providers_test.dart b/test/providers/ui_providers_test.dart
index 41726dca..9856b4a5 100644
--- a/test/providers/ui_providers_test.dart
+++ b/test/providers/ui_providers_test.dart
@@ -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();

From 9ff76135d543d74ba9013f15f1dd6e00f0dc1d3a Mon Sep 17 00:00:00 2001
From: DenserMeerkat <mragulmanoharan@gmail.com>
Date: Thu, 11 Jul 2024 22:01:16 +0530
Subject: [PATCH 2/6] fix: ui provider tests

---
 test/providers/ui_providers_test.dart | 12 ++++++++----
 1 file changed, 8 insertions(+), 4 deletions(-)

diff --git a/test/providers/ui_providers_test.dart b/test/providers/ui_providers_test.dart
index 9856b4a5..0412b6b3 100644
--- a/test/providers/ui_providers_test.dart
+++ b/test/providers/ui_providers_test.dart
@@ -448,7 +448,8 @@ void main() {
         of: find.byType(EnvURLField),
         matching: find.byType(ExtendedTextField),
       );
-      await tester.enterText(field, kTestUrl);
+      await tester.tap(field);
+      tester.testTextInput.enterText(kTestUrl);
       await tester.pump();
 
       // Tap on the "Send" button
@@ -495,7 +496,8 @@ void main() {
         of: find.byType(EnvURLField),
         matching: find.byType(ExtendedTextField),
       );
-      await tester.enterText(field, kTestUrl);
+      await tester.tap(field);
+      tester.testTextInput.enterText(kTestUrl);
       await tester.pump();
 
       // Tap on the "Send" button
@@ -546,7 +548,8 @@ void main() {
         of: find.byType(EnvURLField),
         matching: find.byType(ExtendedTextField),
       );
-      await tester.enterText(field, kTestUrl);
+      await tester.tap(field);
+      tester.testTextInput.enterText(kTestUrl);
       await tester.pump();
 
       // Tap on the "Send" button
@@ -606,7 +609,8 @@ void main() {
         of: find.byType(EnvURLField),
         matching: find.byType(ExtendedTextField),
       );
-      await tester.enterText(field, kTestUrl);
+      await tester.tap(field);
+      tester.testTextInput.enterText(kTestUrl);
       await tester.pump();
 
       // Tap on the "Send" button

From ec9d76868c95c073ff633144f4b3bc8bbec2cac2 Mon Sep 17 00:00:00 2001
From: DenserMeerkat <mragulmanoharan@gmail.com>
Date: Thu, 11 Jul 2024 23:46:33 +0530
Subject: [PATCH 3/6] fix: make widthFactor null

---
 lib/screens/common_widgets/env_trigger_field.dart | 1 +
 1 file changed, 1 insertion(+)

diff --git a/lib/screens/common_widgets/env_trigger_field.dart b/lib/screens/common_widgets/env_trigger_field.dart
index cfc12455..9c568803 100644
--- a/lib/screens/common_widgets/env_trigger_field.dart
+++ b/lib/screens/common_widgets/env_trigger_field.dart
@@ -62,6 +62,7 @@ class _EnvironmentTriggerFieldState extends State<EnvironmentTriggerField> {
       key: Key(widget.keyId),
       textEditingController: controller,
       focusNode: focusNode,
+      optionsWidthFactor: null,
       autocompleteTriggers: [
         AutocompleteTrigger(
             trigger: '{',

From 5d28b5c4f9327bfaa256c34addb04a161f58bce3 Mon Sep 17 00:00:00 2001
From: DenserMeerkat <mragulmanoharan@gmail.com>
Date: Thu, 11 Jul 2024 23:50:01 +0530
Subject: [PATCH 4/6] fix: widthFactor 1 for urlfield

---
 lib/screens/common_widgets/env_trigger_field.dart | 4 +++-
 lib/screens/common_widgets/envfield_url.dart      | 1 +
 2 files changed, 4 insertions(+), 1 deletion(-)

diff --git a/lib/screens/common_widgets/env_trigger_field.dart b/lib/screens/common_widgets/env_trigger_field.dart
index 9c568803..5c3096f0 100644
--- a/lib/screens/common_widgets/env_trigger_field.dart
+++ b/lib/screens/common_widgets/env_trigger_field.dart
@@ -13,6 +13,7 @@ class EnvironmentTriggerField extends StatefulWidget {
     this.onFieldSubmitted,
     this.style,
     this.decoration,
+    this.optionsWidthFactor,
   });
 
   final String keyId;
@@ -21,6 +22,7 @@ class EnvironmentTriggerField extends StatefulWidget {
   final void Function(String)? onFieldSubmitted;
   final TextStyle? style;
   final InputDecoration? decoration;
+  final double? optionsWidthFactor;
 
   @override
   State<EnvironmentTriggerField> createState() =>
@@ -62,7 +64,7 @@ class _EnvironmentTriggerFieldState extends State<EnvironmentTriggerField> {
       key: Key(widget.keyId),
       textEditingController: controller,
       focusNode: focusNode,
-      optionsWidthFactor: null,
+      optionsWidthFactor: widget.optionsWidthFactor,
       autocompleteTriggers: [
         AutocompleteTrigger(
             trigger: '{',
diff --git a/lib/screens/common_widgets/envfield_url.dart b/lib/screens/common_widgets/envfield_url.dart
index 5d43e914..8f98e597 100644
--- a/lib/screens/common_widgets/envfield_url.dart
+++ b/lib/screens/common_widgets/envfield_url.dart
@@ -33,6 +33,7 @@ class EnvURLField extends StatelessWidget {
       ),
       onChanged: onChanged,
       onFieldSubmitted: onFieldSubmitted,
+      optionsWidthFactor: 1,
     );
   }
 }

From ca836d8ddfe5a3da1a2f2fef2bb3f8b44f7485b1 Mon Sep 17 00:00:00 2001
From: Ashita Prasad <ashitaprasad92@gmail.com>
Date: Sat, 13 Jul 2024 21:53:21 +0530
Subject: [PATCH 5/6] Update pubspec.yaml

---
 pubspec.yaml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pubspec.yaml b/pubspec.yaml
index 6a526548..7703e6c0 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -65,7 +65,7 @@ dependencies:
   flutter_portal: ^1.1.4
   multi_trigger_autocomplete:
     git:
-      url: https://github.com/DenserMeerkat/multi_trigger_autocomplete.git
+      url: https://github.com/foss42/multi_trigger_autocomplete.git
       ref: master
   extended_text_field: ^15.0.0
 

From da9b2c35d6826aa14dfc9615c04b140dd5f2fd9b Mon Sep 17 00:00:00 2001
From: Ashita Prasad <ashitaprasad92@gmail.com>
Date: Sat, 13 Jul 2024 21:54:41 +0530
Subject: [PATCH 6/6] Update pubspec.lock

---
 pubspec.lock | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/pubspec.lock b/pubspec.lock
index f1c82607..f2feb559 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -862,8 +862,8 @@ packages:
     description:
       path: "."
       ref: master
-      resolved-ref: "7ca8778e47f80c913cee33a3a01447039acc6936"
-      url: "https://github.com/DenserMeerkat/multi_trigger_autocomplete.git"
+      resolved-ref: cb22bab30dd14452d184bc6ad3bb41b612b22c70
+      url: "https://github.com/foss42/multi_trigger_autocomplete.git"
     source: git
     version: "1.0.1"
   nested: