From 7ab1c4f293a8338cf4421827d97b125a42967f3a Mon Sep 17 00:00:00 2001 From: DenserMeerkat Date: Fri, 14 Feb 2025 03:42:17 +0530 Subject: [PATCH 01/32] feat: header & env suggestions --- .../common_widgets/env_trigger_field.dart | 62 ++---- lib/screens/common_widgets/envfield_cell.dart | 65 +++++- lib/screens/common_widgets/envfield_url.dart | 56 ++++- lib/widgets/field_header.dart | 205 ++++++++++-------- pubspec.lock | 184 +++------------- pubspec.yaml | 5 +- 6 files changed, 277 insertions(+), 300 deletions(-) diff --git a/lib/screens/common_widgets/env_trigger_field.dart b/lib/screens/common_widgets/env_trigger_field.dart index bccaf048..d662e13d 100644 --- a/lib/screens/common_widgets/env_trigger_field.dart +++ b/lib/screens/common_widgets/env_trigger_field.dart @@ -4,69 +4,39 @@ import 'package:extended_text_field/extended_text_field.dart'; import 'env_regexp_span_builder.dart'; import 'env_trigger_options.dart'; -class EnvironmentTriggerField extends StatefulWidget { +class EnvironmentTriggerField extends StatelessWidget { const EnvironmentTriggerField({ super.key, required this.keyId, - this.initialValue, + required this.controller, + required this.focusNode, this.onChanged, this.onFieldSubmitted, this.style, this.decoration, this.optionsWidthFactor, + this.autocompleteNoTrigger, }); final String keyId; - final String? initialValue; + final TextEditingController controller; + final FocusNode focusNode; final void Function(String)? onChanged; final void Function(String)? onFieldSubmitted; final TextStyle? style; final InputDecoration? decoration; final double? optionsWidthFactor; - - @override - State createState() => - EnvironmentTriggerFieldState(); -} - -class EnvironmentTriggerFieldState extends State { - 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.keyId != widget.keyId) || - (oldWidget.initialValue != widget.initialValue)) { - controller.text = widget.initialValue ?? ""; - controller.selection = - TextSelection.collapsed(offset: controller.text.length); - } - } + final AutocompleteNoTrigger? autocompleteNoTrigger; @override Widget build(BuildContext context) { return MultiTriggerAutocomplete( - key: Key(widget.keyId), + key: Key(keyId), textEditingController: controller, focusNode: focusNode, - optionsWidthFactor: widget.optionsWidthFactor, + optionsWidthFactor: optionsWidthFactor, autocompleteTriggers: [ + if (autocompleteNoTrigger != null) autocompleteNoTrigger!, AutocompleteTrigger( trigger: '{', triggerEnd: "}}", @@ -79,7 +49,7 @@ class EnvironmentTriggerFieldState extends State { autocomplete.acceptAutocompleteOption( '{${suggestion.variable.key}', ); - widget.onChanged?.call(controller.text); + onChanged?.call(controller.text); }); }), AutocompleteTrigger( @@ -94,7 +64,7 @@ class EnvironmentTriggerFieldState extends State { autocomplete.acceptAutocompleteOption( suggestion.variable.key, ); - widget.onChanged?.call(controller.text); + onChanged?.call(controller.text); }); }), ], @@ -102,10 +72,10 @@ class EnvironmentTriggerFieldState extends State { return ExtendedTextField( controller: textEditingController, focusNode: focusnode, - decoration: widget.decoration, - style: widget.style, - onChanged: widget.onChanged, - onSubmitted: widget.onFieldSubmitted, + decoration: decoration, + style: style, + onChanged: onChanged, + onSubmitted: onFieldSubmitted, specialTextSpanBuilder: EnvRegExpSpanBuilder(), onTapOutside: (event) { focusNode.unfocus(); diff --git a/lib/screens/common_widgets/envfield_cell.dart b/lib/screens/common_widgets/envfield_cell.dart index 6eedad02..ce0f2f62 100644 --- a/lib/screens/common_widgets/envfield_cell.dart +++ b/lib/screens/common_widgets/envfield_cell.dart @@ -1,8 +1,9 @@ import 'package:apidash_design_system/apidash_design_system.dart'; import 'package:flutter/material.dart'; +import 'package:multi_trigger_autocomplete/multi_trigger_autocomplete.dart'; import 'env_trigger_field.dart'; -class EnvCellField extends StatelessWidget { +class EnvCellField extends StatefulWidget { const EnvCellField({ super.key, required this.keyId, @@ -10,20 +11,69 @@ class EnvCellField extends StatelessWidget { this.hintText, this.onChanged, this.colorScheme, - }); + this.autocompleteNoTrigger, + this.focusNode, + this.controller, + }) : assert( + !(controller != null && initialValue != null), + 'controller and initialValue cannot be simultaneously defined.', + ); final String keyId; final String? initialValue; final String? hintText; final void Function(String)? onChanged; final ColorScheme? colorScheme; + final AutocompleteNoTrigger? autocompleteNoTrigger; + final FocusNode? focusNode; + final TextEditingController? controller; + + @override + State createState() => _EnvCellFieldState(); +} + +class _EnvCellFieldState extends State { + late TextEditingController _controller; + late FocusNode _focusNode; + + @override + void initState() { + super.initState(); + _controller = widget.controller ?? + TextEditingController.fromValue(TextEditingValue( + text: widget.initialValue!, + selection: + TextSelection.collapsed(offset: widget.initialValue!.length))); + _focusNode = widget.focusNode ?? FocusNode(); + } + + @override + void dispose() { + _controller.dispose(); + _focusNode.dispose(); + super.dispose(); + } + + @override + void didUpdateWidget(EnvCellField oldWidget) { + super.didUpdateWidget(oldWidget); + if ((oldWidget.keyId != widget.keyId) || + (oldWidget.initialValue != widget.initialValue)) { + _controller = widget.controller ?? + TextEditingController.fromValue(TextEditingValue( + text: widget.initialValue!, + selection: TextSelection.collapsed( + offset: widget.initialValue!.length))); + } + } @override Widget build(BuildContext context) { - var clrScheme = colorScheme ?? Theme.of(context).colorScheme; + var clrScheme = widget.colorScheme ?? Theme.of(context).colorScheme; return EnvironmentTriggerField( - keyId: keyId, - initialValue: initialValue, + keyId: widget.keyId, + controller: _controller, + focusNode: _focusNode, style: kCodeStyle.copyWith( color: clrScheme.onSurface, ), @@ -33,7 +83,7 @@ class EnvCellField extends StatelessWidget { kHintOpacity, ), ), - hintText: hintText, + hintText: widget.hintText, contentPadding: const EdgeInsets.only(bottom: 12), focusedBorder: UnderlineInputBorder( borderSide: BorderSide( @@ -48,7 +98,8 @@ class EnvCellField extends StatelessWidget { ), ), ), - onChanged: onChanged, + autocompleteNoTrigger: widget.autocompleteNoTrigger, + onChanged: widget.onChanged, ); } } diff --git a/lib/screens/common_widgets/envfield_url.dart b/lib/screens/common_widgets/envfield_url.dart index 2e7e7de5..ab5569da 100644 --- a/lib/screens/common_widgets/envfield_url.dart +++ b/lib/screens/common_widgets/envfield_url.dart @@ -3,25 +3,69 @@ import 'package:flutter/material.dart'; import 'package:apidash/consts.dart'; import 'env_trigger_field.dart'; -class EnvURLField extends StatelessWidget { +class EnvURLField extends StatefulWidget { const EnvURLField({ super.key, required this.selectedId, this.initialValue, this.onChanged, this.onFieldSubmitted, - }); + this.focusNode, + this.controller, + }) : assert( + !(controller != null && initialValue != null), + 'controller and initialValue cannot be simultaneously defined.', + ); final String selectedId; final String? initialValue; final void Function(String)? onChanged; final void Function(String)? onFieldSubmitted; + final FocusNode? focusNode; + final TextEditingController? controller; + + @override + State createState() => _EnvURLFieldState(); +} + +class _EnvURLFieldState extends State { + late TextEditingController _controller; + late FocusNode _focusNode; + + @override + void initState() { + super.initState(); + _controller = widget.controller ?? + TextEditingController.fromValue(widget.initialValue != null + ? TextEditingValue(text: widget.initialValue!) + : TextEditingValue.empty); + _focusNode = widget.focusNode ?? FocusNode(); + } + + @override + void dispose() { + _controller.dispose(); + _focusNode.dispose(); + super.dispose(); + } + + @override + void didUpdateWidget(EnvURLField oldWidget) { + super.didUpdateWidget(oldWidget); + if ((oldWidget.initialValue != widget.initialValue)) { + _controller = widget.controller ?? + TextEditingController.fromValue(widget.initialValue != null + ? TextEditingValue(text: widget.initialValue!) + : TextEditingValue.empty); + } + } @override Widget build(BuildContext context) { return EnvironmentTriggerField( - keyId: "url-$selectedId", - initialValue: initialValue, + keyId: "url-${widget.selectedId}", + controller: _controller, + focusNode: _focusNode, style: kCodeStyle, decoration: InputDecoration( hintText: kHintTextUrlCard, @@ -32,8 +76,8 @@ class EnvURLField extends StatelessWidget { ), border: InputBorder.none, ), - onChanged: onChanged, - onFieldSubmitted: onFieldSubmitted, + onChanged: widget.onChanged, + onFieldSubmitted: widget.onFieldSubmitted, optionsWidthFactor: 1, ); } diff --git a/lib/widgets/field_header.dart b/lib/widgets/field_header.dart index 90a5cd76..4c8892a9 100644 --- a/lib/widgets/field_header.dart +++ b/lib/widgets/field_header.dart @@ -1,7 +1,9 @@ import 'package:apidash_design_system/apidash_design_system.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_typeahead/flutter_typeahead.dart'; +import 'package:multi_trigger_autocomplete/multi_trigger_autocomplete.dart'; import 'package:apidash/utils/utils.dart'; +import 'package:apidash/screens/common_widgets/common_widgets.dart'; +import 'package:apidash/consts.dart'; class HeaderField extends StatefulWidget { const HeaderField({ @@ -23,97 +25,28 @@ class HeaderField extends StatefulWidget { } class _HeaderFieldState extends State { - final TextEditingController controller = TextEditingController(); - - @override - void initState() { - super.initState(); - controller.text = widget.initialValue ?? ""; - controller.selection = - TextSelection.collapsed(offset: controller.text.length); - } - - @override - void dispose() { - controller.dispose(); - super.dispose(); - } - - @override - void didUpdateWidget(HeaderField oldWidget) { - super.didUpdateWidget(oldWidget); - if (oldWidget.initialValue != widget.initialValue) { - controller.text = widget.initialValue ?? ""; - controller.selection = - TextSelection.collapsed(offset: controller.text.length); - } - } - + final FocusNode focusNode = FocusNode(); @override Widget build(BuildContext context) { var colorScheme = widget.colorScheme ?? Theme.of(context).colorScheme; - return TypeAheadField( - key: Key(widget.keyId), - hideOnEmpty: true, - controller: controller, - onSelected: (value) { - setState(() { - controller.text = value; - }); - widget.onChanged!.call(value); - }, - itemBuilder: (context, String suggestion) { - return ListTile( - dense: true, - title: Text(suggestion), - ); - }, - suggestionsCallback: headerSuggestionCallback, - decorationBuilder: (context, child) => - suggestionBoxDecorations(context, child, colorScheme), - constraints: const BoxConstraints(maxHeight: 400), - builder: (context, controller, focusNode) => TextField( - onChanged: widget.onChanged, - controller: controller, - focusNode: focusNode, - style: kCodeStyle.copyWith( - color: colorScheme.onSurface, - ), - decoration: InputDecoration( - hintStyle: kCodeStyle.copyWith( - color: colorScheme.outline.withOpacity(kHintOpacity)), - hintText: widget.hintText, - contentPadding: const EdgeInsets.only(bottom: 12), - focusedBorder: UnderlineInputBorder( - borderSide: BorderSide( - color: colorScheme.primary.withOpacity( - kHintOpacity, - ), - ), - ), - enabledBorder: UnderlineInputBorder( - borderSide: BorderSide( - color: colorScheme.surfaceContainerHighest, - ), - ), - ), - ), - ); - } - - Theme suggestionBoxDecorations( - BuildContext context, Widget child, ColorScheme colorScheme) { - return Theme( - data: ThemeData(colorScheme: colorScheme), - child: Material( - elevation: 4, - shape: RoundedRectangleBorder( - side: BorderSide(color: Theme.of(context).dividerColor, width: 1.2), - borderRadius: const BorderRadius.vertical(bottom: Radius.circular(8)), - ), - clipBehavior: Clip.hardEdge, - child: child, - ), + return EnvCellField( + keyId: widget.keyId, + hintText: widget.hintText, + initialValue: widget.initialValue, + focusNode: focusNode, + onChanged: widget.onChanged, + colorScheme: colorScheme, + autocompleteNoTrigger: AutocompleteNoTrigger( + optionsViewBuilder: (context, autocompleteQuery, controller) { + return HeaderSuggestions( + suggestionsCallback: headerSuggestionCallback, + query: autocompleteQuery.query, + onSuggestionTap: (suggestion) { + controller.text = suggestion; + widget.onChanged?.call(controller.text); + focusNode.unfocus(); + }); + }), ); } @@ -121,6 +54,98 @@ class _HeaderFieldState extends State { if (pattern.isEmpty) { return null; } - return getHeaderSuggestions(pattern); + return getHeaderSuggestions(pattern) + .where( + (suggestion) => suggestion.toLowerCase() != pattern.toLowerCase()) + .toList(); + } +} + +class HeaderSuggestions extends StatefulWidget { + const HeaderSuggestions({ + super.key, + required this.suggestionsCallback, + required this.query, + required this.onSuggestionTap, + }); + final Future?> Function(String) suggestionsCallback; + final String query; + final ValueSetter onSuggestionTap; + + @override + State createState() => _HeaderSuggestionsState(); +} + +class _HeaderSuggestionsState extends State { + List? suggestions; + + @override + void initState() { + super.initState(); + widget.suggestionsCallback(widget.query).then((value) { + if (mounted) { + setState(() { + suggestions = value; + }); + } + }); + } + + @override + void didUpdateWidget(HeaderSuggestions oldWidget) { + super.didUpdateWidget(oldWidget); + if (oldWidget.query != widget.query) { + widget.suggestionsCallback(widget.query).then((value) { + if (mounted) { + setState(() { + suggestions = value; + }); + } + }); + } + } + + @override + Widget build(BuildContext context) { + if (suggestions == null) { + return const SizedBox.shrink(); + } + return 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, + title: Text(suggestion), + onTap: () => widget.onSuggestionTap(suggestion), + ); + }, + ), + ), + ), + ), + ); } } diff --git a/pubspec.lock b/pubspec.lock index 0f9f35a4..ef201514 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,23 +5,23 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: f256b0c0ba6c7577c15e2e4e114755640a875e885099367bf6e012b19314c834 + sha256: "16e298750b6d0af7ce8a3ba7c18c69c3785d11b15ec83f6dcd0ad2a0009b3cab" url: "https://pub.dev" source: hosted - version: "72.0.0" + version: "76.0.0" _macros: dependency: transitive description: dart source: sdk - version: "0.3.2" + version: "0.3.3" analyzer: dependency: transitive description: name: analyzer - sha256: b652861553cd3990d8ed361f7979dc6d7053a9ac8843fa73820ab68ce5410139 + sha256: "1f14db053a8c23e260789e9b0980fa27f2680dd640932cae5e1137cce0e46e1e" url: "https://pub.dev" source: hosted - version: "6.7.0" + version: "6.11.0" ansi_styles: dependency: transitive description: @@ -240,10 +240,10 @@ packages: dependency: transitive description: name: collection - sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf url: "https://pub.dev" source: hosted - version: "1.18.0" + version: "1.19.0" conventional_commit: dependency: transitive description: @@ -501,54 +501,6 @@ packages: url: "https://pub.dev" source: hosted version: "0.20.5" - flutter_keyboard_visibility: - dependency: transitive - description: - name: flutter_keyboard_visibility - sha256: "98664be7be0e3ffca00de50f7f6a287ab62c763fc8c762e0a21584584a3ff4f8" - url: "https://pub.dev" - source: hosted - version: "6.0.0" - flutter_keyboard_visibility_linux: - dependency: transitive - description: - name: flutter_keyboard_visibility_linux - sha256: "6fba7cd9bb033b6ddd8c2beb4c99ad02d728f1e6e6d9b9446667398b2ac39f08" - url: "https://pub.dev" - source: hosted - version: "1.0.0" - flutter_keyboard_visibility_macos: - dependency: transitive - description: - name: flutter_keyboard_visibility_macos - sha256: c5c49b16fff453dfdafdc16f26bdd8fb8d55812a1d50b0ce25fc8d9f2e53d086 - url: "https://pub.dev" - source: hosted - version: "1.0.0" - flutter_keyboard_visibility_platform_interface: - dependency: transitive - description: - name: flutter_keyboard_visibility_platform_interface - sha256: e43a89845873f7be10cb3884345ceb9aebf00a659f479d1c8f4293fcb37022a4 - url: "https://pub.dev" - source: hosted - version: "2.0.0" - flutter_keyboard_visibility_web: - dependency: transitive - description: - name: flutter_keyboard_visibility_web - sha256: d3771a2e752880c79203f8d80658401d0c998e4183edca05a149f5098ce6e3d1 - url: "https://pub.dev" - source: hosted - version: "2.0.0" - flutter_keyboard_visibility_windows: - dependency: transitive - description: - name: flutter_keyboard_visibility_windows - sha256: fc4b0f0b6be9b93ae527f3d527fb56ee2d918cd88bbca438c478af7bcfd0ef73 - url: "https://pub.dev" - source: hosted - version: "1.0.0" flutter_launcher_icons: dependency: "direct dev" description: @@ -610,14 +562,6 @@ packages: description: flutter source: sdk version: "0.0.0" - flutter_typeahead: - dependency: "direct main" - description: - name: flutter_typeahead - sha256: d64712c65db240b1057559b952398ebb6e498077baeebf9b0731dade62438a6d - url: "https://pub.dev" - source: hosted - version: "5.2.0" flutter_web_plugins: dependency: transitive description: flutter @@ -876,18 +820,18 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" + sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06" url: "https://pub.dev" source: hosted - version: "10.0.5" + version: "10.0.7" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" + sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379" url: "https://pub.dev" source: hosted - version: "3.0.5" + version: "3.0.8" leak_tracker_testing: dependency: transitive description: @@ -924,10 +868,10 @@ packages: dependency: transitive description: name: macros - sha256: "0acaed5d6b7eab89f63350bccd82119e6c602df0f391260d0e32b5e23db79536" + sha256: "1d9e801cd66f7ea3663c45fc708450db1fa57f988142c64289142c9b7ee80656" url: "https://pub.dev" source: hosted - version: "0.1.2-main.4" + version: "0.1.3-main.0" markdown: dependency: "direct main" description: @@ -956,10 +900,10 @@ packages: dependency: "direct dev" description: name: melos - sha256: a62abfa8c7826cec927f8585572bb9adf591be152150494d879ca2c75118809d + sha256: "3f3ab3f902843d1e5a1b1a4dd39a4aca8ba1056f2d32fd8995210fa2843f646f" url: "https://pub.dev" source: hosted - version: "6.2.0" + version: "6.3.2" meta: dependency: transitive description: @@ -1004,9 +948,9 @@ packages: dependency: "direct main" description: path: "." - ref: cb22bab30dd14452d184bc6ad3bb41b612b22c70 - resolved-ref: cb22bab30dd14452d184bc6ad3bb41b612b22c70 - url: "https://github.com/foss42/multi_trigger_autocomplete.git" + ref: feat-no-trigger-autocomplete + resolved-ref: "28b593c69d0cc3774ff642ab345e0960a2f7153c" + url: "https://github.com/DenserMeerkat/multi_trigger_autocomplete.git" source: git version: "1.0.1" mustache_template: @@ -1169,38 +1113,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.8" - pointer_interceptor: - dependency: transitive - description: - name: pointer_interceptor - sha256: "57210410680379aea8b1b7ed6ae0c3ad349bfd56fe845b8ea934a53344b9d523" - url: "https://pub.dev" - source: hosted - version: "0.10.1+2" - pointer_interceptor_ios: - dependency: transitive - description: - name: pointer_interceptor_ios - sha256: a6906772b3205b42c44614fcea28f818b1e5fdad73a4ca742a7bd49818d9c917 - url: "https://pub.dev" - source: hosted - version: "0.10.1" - pointer_interceptor_platform_interface: - dependency: transitive - description: - name: pointer_interceptor_platform_interface - sha256: "0597b0560e14354baeb23f8375cd612e8bd4841bf8306ecb71fcd0bb78552506" - url: "https://pub.dev" - source: hosted - version: "0.10.0+1" - pointer_interceptor_web: - dependency: transitive - description: - name: pointer_interceptor_web - sha256: "7a7087782110f8c1827170660b09f8aa893e0e9a61431dbbe2ac3fc482e8c044" - url: "https://pub.dev" - source: hosted - version: "0.10.2+1" pool: dependency: transitive description: @@ -1264,22 +1176,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.4.0" - pubspec: - dependency: transitive - description: - name: pubspec - sha256: f534a50a2b4d48dc3bc0ec147c8bd7c304280fff23b153f3f11803c4d49d927e - url: "https://pub.dev" - source: hosted - version: "2.3.0" pubspec_parse: dependency: transitive description: name: pubspec_parse - sha256: c799b721d79eb6ee6fa56f00c04b472dcd44a30d258fac2174a6ec57302678f8 + sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082" url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.5.0" qr: dependency: transitive description: @@ -1288,14 +1192,6 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.2" - quiver: - dependency: transitive - description: - name: quiver - sha256: ea0b925899e64ecdfbf9c7becb60d5b50e706ade44a85b2363be2a22d88117d2 - url: "https://pub.dev" - source: hosted - version: "3.2.2" riverpod: dependency: "direct main" description: @@ -1468,7 +1364,7 @@ packages: dependency: transitive description: flutter source: sdk - version: "0.0.99" + version: "0.0.0" source_gen: dependency: transitive description: @@ -1529,10 +1425,10 @@ packages: dependency: transitive description: name: stack_trace - sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377" url: "https://pub.dev" source: hosted - version: "1.11.1" + version: "1.12.0" state_notifier: dependency: transitive description: @@ -1561,10 +1457,10 @@ packages: dependency: transitive description: name: string_scanner - sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.3.0" sync_http: dependency: transitive description: @@ -1585,26 +1481,26 @@ packages: dependency: "direct dev" description: name: test - sha256: "7ee44229615f8f642b68120165ae4c2a75fe77ae2065b1e55ae4711f6cf0899e" + sha256: "713a8789d62f3233c46b4a90b174737b2c04cb6ae4500f2aa8b1be8f03f5e67f" url: "https://pub.dev" source: hosted - version: "1.25.7" + version: "1.25.8" test_api: dependency: transitive description: name: test_api - sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" + sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c" url: "https://pub.dev" source: hosted - version: "0.7.2" + version: "0.7.3" test_core: dependency: transitive description: name: test_core - sha256: "55ea5a652e38a1dfb32943a7973f3681a60f872f8c3a05a14664ad54ef9c6696" + sha256: "12391302411737c176b0b5d6491f466b0dd56d4763e347b6714efbaa74d7953d" url: "https://pub.dev" source: hosted - version: "0.6.4" + version: "0.6.5" textwrap: dependency: transitive description: @@ -1645,14 +1541,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.2.2" - uri: - dependency: transitive - description: - name: uri - sha256: "889eea21e953187c6099802b7b4cf5219ba8f3518f604a1033064d45b1b8268a" - url: "https://pub.dev" - source: hosted - version: "1.0.0" url_launcher: dependency: "direct main" description: @@ -1801,10 +1689,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" + sha256: f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b url: "https://pub.dev" source: hosted - version: "14.2.5" + version: "14.3.0" watcher: dependency: transitive description: @@ -1841,10 +1729,10 @@ packages: dependency: transitive description: name: webdriver - sha256: "003d7da9519e1e5f329422b36c4dcdf18d7d2978d1ba099ea4e45ba490ed845e" + sha256: "3d773670966f02a646319410766d3b5e1037efb7f07cc68f844d5e06cd4d61c8" url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.4" webkit_inspection_protocol: dependency: transitive description: @@ -1911,5 +1799,5 @@ packages: source: hosted version: "2.2.1" sdks: - dart: ">=3.5.3 <3.999.0" + dart: ">=3.6.0 <3.999.0" flutter: ">=3.24.2" diff --git a/pubspec.yaml b/pubspec.yaml index e2d00bad..3c5b01be 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -27,7 +27,6 @@ dependencies: flutter_portal: ^1.1.4 flutter_riverpod: ^2.5.1 flutter_svg: ^2.0.10+1 - flutter_typeahead: ^5.2.0 fvp: ^0.26.1 highlighter: ^0.1.1 hive_flutter: ^1.1.0 @@ -47,8 +46,8 @@ dependencies: multi_split_view: ^3.2.2 multi_trigger_autocomplete: git: - url: https://github.com/foss42/multi_trigger_autocomplete.git - ref: cb22bab30dd14452d184bc6ad3bb41b612b22c70 + url: https://github.com/DenserMeerkat/multi_trigger_autocomplete.git + ref: feat-no-trigger-autocomplete package_info_plus: ^8.0.2 path: ^1.8.3 path_provider: ^2.1.2 From 3f94de6f1b1a2f22ccdf9bf663a09e85e9734b4b Mon Sep 17 00:00:00 2001 From: DenserMeerkat Date: Fri, 14 Feb 2025 18:58:14 +0530 Subject: [PATCH 02/32] fix: tests --- lib/widgets/field_header.dart | 2 +- .../env_trigger_field_test.dart | 84 +++---------- test/widgets/headerfield_test.dart | 116 +++++++++++++++--- 3 files changed, 115 insertions(+), 87 deletions(-) diff --git a/lib/widgets/field_header.dart b/lib/widgets/field_header.dart index 4c8892a9..09703340 100644 --- a/lib/widgets/field_header.dart +++ b/lib/widgets/field_header.dart @@ -32,7 +32,7 @@ class _HeaderFieldState extends State { return EnvCellField( keyId: widget.keyId, hintText: widget.hintText, - initialValue: widget.initialValue, + initialValue: widget.initialValue ?? "", focusNode: focusNode, onChanged: widget.onChanged, colorScheme: colorScheme, diff --git a/test/screens/common_widgets/env_trigger_field_test.dart b/test/screens/common_widgets/env_trigger_field_test.dart index c4ec063e..cfd1b894 100644 --- a/test/screens/common_widgets/env_trigger_field_test.dart +++ b/test/screens/common_widgets/env_trigger_field_test.dart @@ -1,90 +1,36 @@ import 'package:flutter/material.dart'; -import 'package:flutter_portal/flutter_portal.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:extended_text_field/extended_text_field.dart'; import 'package:apidash/screens/common_widgets/env_trigger_field.dart'; +import 'package:multi_trigger_autocomplete/multi_trigger_autocomplete.dart'; void main() { - testWidgets('Testing EnvironmentTriggerField updates the controller text', - (WidgetTester tester) async { - final fieldKey = GlobalKey(); - const initialValue = 'initial'; - const updatedValue = 'updated'; - - await tester.pumpWidget( - Portal( - child: MaterialApp( - home: Scaffold( - body: EnvironmentTriggerField( - key: fieldKey, - keyId: 'testKey', - initialValue: initialValue, - ), - ), - ), - ), - ); - - Finder field = find.byType(ExtendedTextField); - expect(field, findsOneWidget); - expect(fieldKey.currentState!.controller.text, initialValue); - - await tester.pumpWidget( - Portal( - child: MaterialApp( - home: Scaffold( - body: EnvironmentTriggerField( - key: fieldKey, - keyId: 'testKey', - initialValue: updatedValue, - ), - ), - ), - ), - ); - - expect(fieldKey.currentState!.controller.text, updatedValue); - }); - testWidgets( - 'Testing EnvironmentTriggerField with empty initialValue clears the controller text', - (WidgetTester tester) async { - final fieldKey = GlobalKey(); - const initialValue = 'initial'; - const emptyValue = ''; + 'EnvironmentTriggerField renders and displays MultiTriggerAutocomplete with triggers', + (tester) async { + final controller = TextEditingController(); + final focusNode = FocusNode(); await tester.pumpWidget( Portal( child: MaterialApp( home: Scaffold( body: EnvironmentTriggerField( - key: fieldKey, keyId: 'testKey', - initialValue: initialValue, + controller: controller, + focusNode: focusNode, ), ), ), ), ); - Finder field = find.byType(ExtendedTextField); - expect(field, findsOneWidget); - expect(fieldKey.currentState!.controller.text, initialValue); - - await tester.pumpWidget( - Portal( - child: MaterialApp( - home: Scaffold( - body: EnvironmentTriggerField( - key: fieldKey, - keyId: 'testKey', - initialValue: emptyValue, - ), - ), - ), - ), - ); - - expect(fieldKey.currentState!.controller.text, emptyValue); + final multiTriggerAutocomplete = find.byType(MultiTriggerAutocomplete); + expect(multiTriggerAutocomplete, findsOneWidget); + final triggers = tester + .widget(multiTriggerAutocomplete) + .autocompleteTriggers; + expect(triggers.length, 2); + expect(triggers.first.trigger, '{'); + expect(triggers.elementAt(1).trigger, '{{'); }); } diff --git a/test/widgets/headerfield_test.dart b/test/widgets/headerfield_test.dart index 4475058b..4215807d 100644 --- a/test/widgets/headerfield_test.dart +++ b/test/widgets/headerfield_test.dart @@ -1,24 +1,106 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; import 'package:apidash/widgets/field_header.dart'; +import 'package:extended_text_field/extended_text_field.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_portal/flutter_portal.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:spot/spot.dart'; void main() { - testWidgets('Testing Header Field', (tester) async { - await tester.pumpWidget( - const MaterialApp( - title: 'Header Field', - home: Scaffold( - body: Column(children: [ - HeaderField( - keyId: "1", - initialValue: "X", - ) - ]), + group('HeaderField Widget Tests', () { + testWidgets('HeaderField renders and displays ExtendedTextField', + (tester) async { + await tester.pumpWidget( + const Portal( + child: MaterialApp( + home: Scaffold( + body: HeaderField( + keyId: "testKey", + hintText: "Enter header", + ), + ), + ), ), - ), - ); + ); - expect(find.byKey(const Key("1")), findsOneWidget); - expect(find.text('X'), findsOneWidget); + spot().spot().existsOnce(); + }); + + testWidgets('HeaderField calls onChanged when text changes', + (tester) async { + String? changedText; + await tester.pumpWidget( + Portal( + child: MaterialApp( + home: Scaffold( + body: HeaderField( + keyId: "testKey", + hintText: "Enter header", + onChanged: (text) => changedText = text, + ), + ), + ), + ), + ); + + await act.tap(spot().spot()); + tester.testTextInput.enterText("new header"); + expect(changedText, "new header"); + }); + }); + + group('HeaderSuggestions Widget Tests', () { + testWidgets('HeaderSuggestions displays suggestions correctly', + (tester) async { + await tester.pumpWidget( + MaterialApp( + home: HeaderSuggestions( + query: "header", + suggestionsCallback: (query) async => ["header1", "header2"], + onSuggestionTap: (suggestion) { + expect(suggestion, "header1"); + }, + ), + ), + ); + + await tester.pumpAndSettle(); + expect(find.byType(ListTile), findsNWidgets(2)); + expect(find.text("header1"), findsOneWidget); + expect(find.text("header2"), findsOneWidget); + }); + + testWidgets('HeaderSuggestions calls onSuggestionTap when tapped', + (tester) async { + String? selectedSuggestion; + await tester.pumpWidget( + MaterialApp( + home: HeaderSuggestions( + query: "header", + suggestionsCallback: (query) async => ["header1"], + onSuggestionTap: (suggestion) => selectedSuggestion = suggestion, + ), + ), + ); + + await tester.pumpAndSettle(); + await tester.tap(find.text("header1")); + expect(selectedSuggestion, "header1"); + }); + + testWidgets('HeaderSuggestions shows no suggestions when list is empty', + (tester) async { + await tester.pumpWidget( + MaterialApp( + home: HeaderSuggestions( + query: "test", + suggestionsCallback: (query) async => [], + onSuggestionTap: (suggestion) {}, + ), + ), + ); + + await tester.pumpAndSettle(); + expect(find.byType(ListTile), findsNothing); + }); }); } From 957613d54b239f81ccbdfe850c10225b670f12d4 Mon Sep 17 00:00:00 2001 From: DenserMeerkat Date: Fri, 14 Feb 2025 19:09:11 +0530 Subject: [PATCH 03/32] refactor: abstract suggestions menu box --- .../common_widgets/env_trigger_field.dart | 2 +- .../common_widgets/env_trigger_options.dart | 53 ++++++------------- lib/widgets/field_header.dart | 49 +++++------------ lib/widgets/suggestions_menu_box.dart | 34 ++++++++++++ lib/widgets/widgets.dart | 1 + 5 files changed, 66 insertions(+), 73 deletions(-) create mode 100644 lib/widgets/suggestions_menu_box.dart diff --git a/lib/screens/common_widgets/env_trigger_field.dart b/lib/screens/common_widgets/env_trigger_field.dart index d662e13d..c617779d 100644 --- a/lib/screens/common_widgets/env_trigger_field.dart +++ b/lib/screens/common_widgets/env_trigger_field.dart @@ -34,7 +34,7 @@ class EnvironmentTriggerField extends StatelessWidget { key: Key(keyId), textEditingController: controller, focusNode: focusNode, - optionsWidthFactor: optionsWidthFactor, + optionsWidthFactor: optionsWidthFactor ?? 1, autocompleteTriggers: [ if (autocompleteNoTrigger != null) autocompleteNoTrigger!, AutocompleteTrigger( diff --git a/lib/screens/common_widgets/env_trigger_options.dart b/lib/screens/common_widgets/env_trigger_options.dart index 702209b8..bbe110a7 100644 --- a/lib/screens/common_widgets/env_trigger_options.dart +++ b/lib/screens/common_widgets/env_trigger_options.dart @@ -1,9 +1,8 @@ -import 'package:apidash/consts.dart'; -import 'package:apidash_design_system/apidash_design_system.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/widgets/widgets.dart'; import 'package:apidash/utils/utils.dart'; import 'envvar_indicator.dart'; @@ -26,41 +25,21 @@ class EnvironmentTriggerOptions extends ConsumerWidget { 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), - ); - }, - ), - ), - ), + : SuggestionsMenuBox( + 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/widgets/field_header.dart b/lib/widgets/field_header.dart index 09703340..7bdf9092 100644 --- a/lib/widgets/field_header.dart +++ b/lib/widgets/field_header.dart @@ -1,9 +1,8 @@ -import 'package:apidash_design_system/apidash_design_system.dart'; import 'package:flutter/material.dart'; import 'package:multi_trigger_autocomplete/multi_trigger_autocomplete.dart'; import 'package:apidash/utils/utils.dart'; import 'package:apidash/screens/common_widgets/common_widgets.dart'; -import 'package:apidash/consts.dart'; +import 'package:apidash/widgets/widgets.dart'; class HeaderField extends StatefulWidget { const HeaderField({ @@ -112,39 +111,19 @@ class _HeaderSuggestionsState extends State { } return 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, - title: Text(suggestion), - onTap: () => widget.onSuggestionTap(suggestion), - ); - }, - ), - ), - ), + : SuggestionsMenuBox( + 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, + title: Text(suggestion), + onTap: () => widget.onSuggestionTap(suggestion), + ); + }, ), ); } diff --git a/lib/widgets/suggestions_menu_box.dart b/lib/widgets/suggestions_menu_box.dart new file mode 100644 index 00000000..e4c5094c --- /dev/null +++ b/lib/widgets/suggestions_menu_box.dart @@ -0,0 +1,34 @@ +import 'package:apidash_design_system/apidash_design_system.dart'; +import 'package:flutter/material.dart'; +import 'package:apidash/consts.dart'; + +class SuggestionsMenuBox extends StatelessWidget { + final Widget child; + const SuggestionsMenuBox({super.key, required this.child}); + + @override + Widget build(BuildContext context) { + return 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: child, + ), + ), + ), + ); + } +} diff --git a/lib/widgets/widgets.dart b/lib/widgets/widgets.dart index 59b61f54..a9e5baaa 100644 --- a/lib/widgets/widgets.dart +++ b/lib/widgets/widgets.dart @@ -51,6 +51,7 @@ export 'splitview_drawer.dart'; export 'splitview_dashboard.dart'; export 'splitview_equal.dart'; export 'splitview_history.dart'; +export 'suggestions_menu_box.dart'; export 'tabbar_segmented.dart'; export 'table_map.dart'; export 'table_request_form.dart'; From b7e94baf4378396a4ed5cd205c265f6ab626d498 Mon Sep 17 00:00:00 2001 From: DenserMeerkat Date: Fri, 14 Feb 2025 20:41:25 +0530 Subject: [PATCH 04/32] refactor: controller --- .../common_widgets/env_trigger_field.dart | 81 ++++++++++++---- lib/screens/common_widgets/envfield_cell.dart | 62 ++---------- lib/screens/common_widgets/envfield_url.dart | 55 ++--------- pubspec.lock | 94 +++++++++---------- 4 files changed, 126 insertions(+), 166 deletions(-) diff --git a/lib/screens/common_widgets/env_trigger_field.dart b/lib/screens/common_widgets/env_trigger_field.dart index c617779d..20fcae9b 100644 --- a/lib/screens/common_widgets/env_trigger_field.dart +++ b/lib/screens/common_widgets/env_trigger_field.dart @@ -4,23 +4,28 @@ import 'package:extended_text_field/extended_text_field.dart'; import 'env_regexp_span_builder.dart'; import 'env_trigger_options.dart'; -class EnvironmentTriggerField extends StatelessWidget { +class EnvironmentTriggerField extends StatefulWidget { const EnvironmentTriggerField({ super.key, required this.keyId, - required this.controller, - required this.focusNode, + this.initialValue, + this.controller, + this.focusNode, this.onChanged, this.onFieldSubmitted, this.style, this.decoration, this.optionsWidthFactor, this.autocompleteNoTrigger, - }); + }) : assert( + !(controller != null && initialValue != null), + 'controller and initialValue cannot be simultaneously defined.', + ); final String keyId; - final TextEditingController controller; - final FocusNode focusNode; + final String? initialValue; + final TextEditingController? controller; + final FocusNode? focusNode; final void Function(String)? onChanged; final void Function(String)? onFieldSubmitted; final TextStyle? style; @@ -28,15 +33,55 @@ class EnvironmentTriggerField extends StatelessWidget { final double? optionsWidthFactor; final AutocompleteNoTrigger? autocompleteNoTrigger; + @override + State createState() => + _EnvironmentTriggerFieldState(); +} + +class _EnvironmentTriggerFieldState extends State { + late TextEditingController _controller; + late FocusNode _focusNode; + + @override + void initState() { + super.initState(); + _controller = widget.controller ?? + TextEditingController.fromValue(TextEditingValue( + text: widget.initialValue!, + selection: + TextSelection.collapsed(offset: widget.initialValue!.length))); + _focusNode = widget.focusNode ?? FocusNode(); + } + + @override + void dispose() { + _controller.dispose(); + _focusNode.dispose(); + super.dispose(); + } + + @override + void didUpdateWidget(EnvironmentTriggerField oldWidget) { + super.didUpdateWidget(oldWidget); + if ((oldWidget.keyId != widget.keyId) || + (oldWidget.initialValue != widget.initialValue)) { + _controller = widget.controller ?? + TextEditingController.fromValue(TextEditingValue( + text: widget.initialValue!, + selection: TextSelection.collapsed( + offset: widget.initialValue!.length))); + } + } + @override Widget build(BuildContext context) { return MultiTriggerAutocomplete( - key: Key(keyId), - textEditingController: controller, - focusNode: focusNode, - optionsWidthFactor: optionsWidthFactor ?? 1, + key: Key(widget.keyId), + textEditingController: _controller, + focusNode: _focusNode, + optionsWidthFactor: widget.optionsWidthFactor ?? 1, autocompleteTriggers: [ - if (autocompleteNoTrigger != null) autocompleteNoTrigger!, + if (widget.autocompleteNoTrigger != null) widget.autocompleteNoTrigger!, AutocompleteTrigger( trigger: '{', triggerEnd: "}}", @@ -49,7 +94,7 @@ class EnvironmentTriggerField extends StatelessWidget { autocomplete.acceptAutocompleteOption( '{${suggestion.variable.key}', ); - onChanged?.call(controller.text); + widget.onChanged?.call(controller.text); }); }), AutocompleteTrigger( @@ -64,7 +109,7 @@ class EnvironmentTriggerField extends StatelessWidget { autocomplete.acceptAutocompleteOption( suggestion.variable.key, ); - onChanged?.call(controller.text); + widget.onChanged?.call(controller.text); }); }), ], @@ -72,13 +117,13 @@ class EnvironmentTriggerField extends StatelessWidget { return ExtendedTextField( controller: textEditingController, focusNode: focusnode, - decoration: decoration, - style: style, - onChanged: onChanged, - onSubmitted: onFieldSubmitted, + decoration: widget.decoration, + style: widget.style, + onChanged: widget.onChanged, + onSubmitted: widget.onFieldSubmitted, specialTextSpanBuilder: EnvRegExpSpanBuilder(), onTapOutside: (event) { - focusNode.unfocus(); + _focusNode.unfocus(); }, ); }, diff --git a/lib/screens/common_widgets/envfield_cell.dart b/lib/screens/common_widgets/envfield_cell.dart index ce0f2f62..3b7d8081 100644 --- a/lib/screens/common_widgets/envfield_cell.dart +++ b/lib/screens/common_widgets/envfield_cell.dart @@ -3,7 +3,7 @@ import 'package:flutter/material.dart'; import 'package:multi_trigger_autocomplete/multi_trigger_autocomplete.dart'; import 'env_trigger_field.dart'; -class EnvCellField extends StatefulWidget { +class EnvCellField extends StatelessWidget { const EnvCellField({ super.key, required this.keyId, @@ -13,11 +13,7 @@ class EnvCellField extends StatefulWidget { this.colorScheme, this.autocompleteNoTrigger, this.focusNode, - this.controller, - }) : assert( - !(controller != null && initialValue != null), - 'controller and initialValue cannot be simultaneously defined.', - ); + }); final String keyId; final String? initialValue; @@ -26,54 +22,14 @@ class EnvCellField extends StatefulWidget { final ColorScheme? colorScheme; final AutocompleteNoTrigger? autocompleteNoTrigger; final FocusNode? focusNode; - final TextEditingController? controller; - - @override - State createState() => _EnvCellFieldState(); -} - -class _EnvCellFieldState extends State { - late TextEditingController _controller; - late FocusNode _focusNode; - - @override - void initState() { - super.initState(); - _controller = widget.controller ?? - TextEditingController.fromValue(TextEditingValue( - text: widget.initialValue!, - selection: - TextSelection.collapsed(offset: widget.initialValue!.length))); - _focusNode = widget.focusNode ?? FocusNode(); - } - - @override - void dispose() { - _controller.dispose(); - _focusNode.dispose(); - super.dispose(); - } - - @override - void didUpdateWidget(EnvCellField oldWidget) { - super.didUpdateWidget(oldWidget); - if ((oldWidget.keyId != widget.keyId) || - (oldWidget.initialValue != widget.initialValue)) { - _controller = widget.controller ?? - TextEditingController.fromValue(TextEditingValue( - text: widget.initialValue!, - selection: TextSelection.collapsed( - offset: widget.initialValue!.length))); - } - } @override Widget build(BuildContext context) { - var clrScheme = widget.colorScheme ?? Theme.of(context).colorScheme; + var clrScheme = colorScheme ?? Theme.of(context).colorScheme; return EnvironmentTriggerField( - keyId: widget.keyId, - controller: _controller, - focusNode: _focusNode, + keyId: keyId, + initialValue: initialValue, + focusNode: focusNode, style: kCodeStyle.copyWith( color: clrScheme.onSurface, ), @@ -83,7 +39,7 @@ class _EnvCellFieldState extends State { kHintOpacity, ), ), - hintText: widget.hintText, + hintText: hintText, contentPadding: const EdgeInsets.only(bottom: 12), focusedBorder: UnderlineInputBorder( borderSide: BorderSide( @@ -98,8 +54,8 @@ class _EnvCellFieldState extends State { ), ), ), - autocompleteNoTrigger: widget.autocompleteNoTrigger, - onChanged: widget.onChanged, + autocompleteNoTrigger: autocompleteNoTrigger, + onChanged: onChanged, ); } } diff --git a/lib/screens/common_widgets/envfield_url.dart b/lib/screens/common_widgets/envfield_url.dart index ab5569da..42f1a336 100644 --- a/lib/screens/common_widgets/envfield_url.dart +++ b/lib/screens/common_widgets/envfield_url.dart @@ -3,7 +3,7 @@ import 'package:flutter/material.dart'; import 'package:apidash/consts.dart'; import 'env_trigger_field.dart'; -class EnvURLField extends StatefulWidget { +class EnvURLField extends StatelessWidget { const EnvURLField({ super.key, required this.selectedId, @@ -11,61 +11,20 @@ class EnvURLField extends StatefulWidget { this.onChanged, this.onFieldSubmitted, this.focusNode, - this.controller, - }) : assert( - !(controller != null && initialValue != null), - 'controller and initialValue cannot be simultaneously defined.', - ); + }); final String selectedId; final String? initialValue; final void Function(String)? onChanged; final void Function(String)? onFieldSubmitted; final FocusNode? focusNode; - final TextEditingController? controller; - - @override - State createState() => _EnvURLFieldState(); -} - -class _EnvURLFieldState extends State { - late TextEditingController _controller; - late FocusNode _focusNode; - - @override - void initState() { - super.initState(); - _controller = widget.controller ?? - TextEditingController.fromValue(widget.initialValue != null - ? TextEditingValue(text: widget.initialValue!) - : TextEditingValue.empty); - _focusNode = widget.focusNode ?? FocusNode(); - } - - @override - void dispose() { - _controller.dispose(); - _focusNode.dispose(); - super.dispose(); - } - - @override - void didUpdateWidget(EnvURLField oldWidget) { - super.didUpdateWidget(oldWidget); - if ((oldWidget.initialValue != widget.initialValue)) { - _controller = widget.controller ?? - TextEditingController.fromValue(widget.initialValue != null - ? TextEditingValue(text: widget.initialValue!) - : TextEditingValue.empty); - } - } @override Widget build(BuildContext context) { return EnvironmentTriggerField( - keyId: "url-${widget.selectedId}", - controller: _controller, - focusNode: _focusNode, + keyId: "url-$selectedId", + initialValue: initialValue, + focusNode: focusNode, style: kCodeStyle, decoration: InputDecoration( hintText: kHintTextUrlCard, @@ -76,8 +35,8 @@ class _EnvURLFieldState extends State { ), border: InputBorder.none, ), - onChanged: widget.onChanged, - onFieldSubmitted: widget.onFieldSubmitted, + onChanged: onChanged, + onFieldSubmitted: onFieldSubmitted, optionsWidthFactor: 1, ); } diff --git a/pubspec.lock b/pubspec.lock index ef201514..ecff62e9 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -72,10 +72,10 @@ packages: dependency: transitive description: name: async - sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63 url: "https://pub.dev" source: hosted - version: "2.11.0" + version: "2.12.0" audio_session: dependency: transitive description: @@ -104,10 +104,10 @@ packages: dependency: transitive description: name: boolean_selector - sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" build: dependency: transitive description: @@ -176,10 +176,10 @@ packages: dependency: transitive description: name: characters - sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.4.0" charcode: dependency: transitive description: @@ -224,10 +224,10 @@ packages: dependency: transitive description: name: clock - sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.2" code_builder: dependency: "direct main" description: @@ -240,10 +240,10 @@ packages: dependency: transitive description: name: collection - sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" url: "https://pub.dev" source: hosted - version: "1.19.0" + version: "1.19.1" conventional_commit: dependency: transitive description: @@ -375,10 +375,10 @@ packages: dependency: transitive description: name: fake_async - sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc" url: "https://pub.dev" source: hosted - version: "1.3.1" + version: "1.3.2" ffi: dependency: transitive description: @@ -391,10 +391,10 @@ packages: dependency: transitive description: name: file - sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 url: "https://pub.dev" source: hosted - version: "7.0.0" + version: "7.0.1" file_selector: dependency: "direct main" description: @@ -820,18 +820,18 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06" + sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec url: "https://pub.dev" source: hosted - version: "10.0.7" + version: "10.0.8" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379" + sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 url: "https://pub.dev" source: hosted - version: "3.0.8" + version: "3.0.9" leak_tracker_testing: dependency: transitive description: @@ -884,10 +884,10 @@ packages: dependency: transitive description: name: matcher - sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 url: "https://pub.dev" source: hosted - version: "0.12.16+1" + version: "0.12.17" material_color_utilities: dependency: transitive description: @@ -908,10 +908,10 @@ packages: dependency: transitive description: name: meta - sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c url: "https://pub.dev" source: hosted - version: "1.15.0" + version: "1.16.0" mime: dependency: transitive description: @@ -1013,10 +1013,10 @@ packages: dependency: "direct main" description: name: path - sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" url: "https://pub.dev" source: hosted - version: "1.9.0" + version: "1.9.1" path_parsing: dependency: transitive description: @@ -1101,10 +1101,10 @@ packages: dependency: transitive description: name: platform - sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65" + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" url: "https://pub.dev" source: hosted - version: "3.1.5" + version: "3.1.6" plugin_platform_interface: dependency: transitive description: @@ -1140,10 +1140,10 @@ packages: dependency: transitive description: name: process - sha256: "21e54fd2faf1b5bdd5102afd25012184a6793927648ea81eea80552ac9405b32" + sha256: "107d8be718f120bbba9dcd1e95e3bd325b1b4a4f07db64154635ba03f2567a0d" url: "https://pub.dev" source: hosted - version: "5.0.2" + version: "5.0.3" prompts: dependency: transitive description: @@ -1401,10 +1401,10 @@ packages: dependency: transitive description: name: source_span - sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.10.1" spot: dependency: "direct dev" description: @@ -1425,10 +1425,10 @@ packages: dependency: transitive description: name: stack_trace - sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377" + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.12.1" state_notifier: dependency: transitive description: @@ -1441,10 +1441,10 @@ packages: dependency: transitive description: name: stream_channel - sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.4" stream_transform: dependency: transitive description: @@ -1457,10 +1457,10 @@ packages: dependency: transitive description: name: string_scanner - sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3" + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.4.1" sync_http: dependency: transitive description: @@ -1473,34 +1473,34 @@ packages: dependency: transitive description: name: term_glyph - sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.2.2" test: dependency: "direct dev" description: name: test - sha256: "713a8789d62f3233c46b4a90b174737b2c04cb6ae4500f2aa8b1be8f03f5e67f" + sha256: "301b213cd241ca982e9ba50266bd3f5bd1ea33f1455554c5abb85d1be0e2d87e" url: "https://pub.dev" source: hosted - version: "1.25.8" + version: "1.25.15" test_api: dependency: transitive description: name: test_api - sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c" + sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd url: "https://pub.dev" source: hosted - version: "0.7.3" + version: "0.7.4" test_core: dependency: transitive description: name: test_core - sha256: "12391302411737c176b0b5d6491f466b0dd56d4763e347b6714efbaa74d7953d" + sha256: "84d17c3486c8dfdbe5e12a50c8ae176d15e2a771b96909a9442b40173649ccaa" url: "https://pub.dev" source: hosted - version: "0.6.5" + version: "0.6.8" textwrap: dependency: transitive description: @@ -1689,10 +1689,10 @@ packages: dependency: transitive description: name: vm_service - sha256: f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b + sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14" url: "https://pub.dev" source: hosted - version: "14.3.0" + version: "14.3.1" watcher: dependency: transitive description: @@ -1799,5 +1799,5 @@ packages: source: hosted version: "2.2.1" sdks: - dart: ">=3.6.0 <3.999.0" + dart: ">=3.7.0-0 <3.999.0" flutter: ">=3.24.2" From 2d3dae35dfe9a0eb57150c21b238bf749a86f451 Mon Sep 17 00:00:00 2001 From: DenserMeerkat Date: Fri, 14 Feb 2025 20:52:24 +0530 Subject: [PATCH 05/32] test: revert test --- .../common_widgets/env_trigger_field.dart | 14 +-- .../env_trigger_field_test.dart | 86 +++++++++++++++---- 2 files changed, 77 insertions(+), 23 deletions(-) diff --git a/lib/screens/common_widgets/env_trigger_field.dart b/lib/screens/common_widgets/env_trigger_field.dart index 20fcae9b..96c54335 100644 --- a/lib/screens/common_widgets/env_trigger_field.dart +++ b/lib/screens/common_widgets/env_trigger_field.dart @@ -35,17 +35,17 @@ class EnvironmentTriggerField extends StatefulWidget { @override State createState() => - _EnvironmentTriggerFieldState(); + EnvironmentTriggerFieldState(); } -class _EnvironmentTriggerFieldState extends State { - late TextEditingController _controller; +class EnvironmentTriggerFieldState extends State { + late TextEditingController controller; late FocusNode _focusNode; @override void initState() { super.initState(); - _controller = widget.controller ?? + controller = widget.controller ?? TextEditingController.fromValue(TextEditingValue( text: widget.initialValue!, selection: @@ -55,7 +55,7 @@ class _EnvironmentTriggerFieldState extends State { @override void dispose() { - _controller.dispose(); + controller.dispose(); _focusNode.dispose(); super.dispose(); } @@ -65,7 +65,7 @@ class _EnvironmentTriggerFieldState extends State { super.didUpdateWidget(oldWidget); if ((oldWidget.keyId != widget.keyId) || (oldWidget.initialValue != widget.initialValue)) { - _controller = widget.controller ?? + controller = widget.controller ?? TextEditingController.fromValue(TextEditingValue( text: widget.initialValue!, selection: TextSelection.collapsed( @@ -77,7 +77,7 @@ class _EnvironmentTriggerFieldState extends State { Widget build(BuildContext context) { return MultiTriggerAutocomplete( key: Key(widget.keyId), - textEditingController: _controller, + textEditingController: controller, focusNode: _focusNode, optionsWidthFactor: widget.optionsWidthFactor ?? 1, autocompleteTriggers: [ diff --git a/test/screens/common_widgets/env_trigger_field_test.dart b/test/screens/common_widgets/env_trigger_field_test.dart index cfd1b894..c4ec063e 100644 --- a/test/screens/common_widgets/env_trigger_field_test.dart +++ b/test/screens/common_widgets/env_trigger_field_test.dart @@ -1,36 +1,90 @@ import 'package:flutter/material.dart'; +import 'package:flutter_portal/flutter_portal.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:extended_text_field/extended_text_field.dart'; import 'package:apidash/screens/common_widgets/env_trigger_field.dart'; -import 'package:multi_trigger_autocomplete/multi_trigger_autocomplete.dart'; void main() { - testWidgets( - 'EnvironmentTriggerField renders and displays MultiTriggerAutocomplete with triggers', - (tester) async { - final controller = TextEditingController(); - final focusNode = FocusNode(); + testWidgets('Testing EnvironmentTriggerField updates the controller text', + (WidgetTester tester) async { + final fieldKey = GlobalKey(); + const initialValue = 'initial'; + const updatedValue = 'updated'; await tester.pumpWidget( Portal( child: MaterialApp( home: Scaffold( body: EnvironmentTriggerField( + key: fieldKey, keyId: 'testKey', - controller: controller, - focusNode: focusNode, + initialValue: initialValue, ), ), ), ), ); - final multiTriggerAutocomplete = find.byType(MultiTriggerAutocomplete); - expect(multiTriggerAutocomplete, findsOneWidget); - final triggers = tester - .widget(multiTriggerAutocomplete) - .autocompleteTriggers; - expect(triggers.length, 2); - expect(triggers.first.trigger, '{'); - expect(triggers.elementAt(1).trigger, '{{'); + Finder field = find.byType(ExtendedTextField); + expect(field, findsOneWidget); + expect(fieldKey.currentState!.controller.text, initialValue); + + await tester.pumpWidget( + Portal( + child: MaterialApp( + home: Scaffold( + body: EnvironmentTriggerField( + key: fieldKey, + keyId: 'testKey', + initialValue: updatedValue, + ), + ), + ), + ), + ); + + expect(fieldKey.currentState!.controller.text, updatedValue); + }); + + testWidgets( + 'Testing EnvironmentTriggerField with empty initialValue clears the controller text', + (WidgetTester tester) async { + final fieldKey = GlobalKey(); + const initialValue = 'initial'; + const emptyValue = ''; + + await tester.pumpWidget( + Portal( + child: MaterialApp( + home: Scaffold( + body: EnvironmentTriggerField( + key: fieldKey, + keyId: 'testKey', + initialValue: initialValue, + ), + ), + ), + ), + ); + + Finder field = find.byType(ExtendedTextField); + expect(field, findsOneWidget); + expect(fieldKey.currentState!.controller.text, initialValue); + + await tester.pumpWidget( + Portal( + child: MaterialApp( + home: Scaffold( + body: EnvironmentTriggerField( + key: fieldKey, + keyId: 'testKey', + initialValue: emptyValue, + ), + ), + ), + ), + ); + + expect(fieldKey.currentState!.controller.text, emptyValue); }); } From 6b0f39f2aeba301626737c2c4c1b6de787356c45 Mon Sep 17 00:00:00 2001 From: DenserMeerkat Date: Mon, 17 Feb 2025 20:32:47 +0530 Subject: [PATCH 06/32] update imports --- lib/screens/common_widgets/envfield_cell.dart | 2 +- lib/widgets/field_header.dart | 2 +- .../example/pubspec.lock | 76 +++++++++---------- ...field_test.dart => field_header_test.dart} | 6 +- 4 files changed, 43 insertions(+), 43 deletions(-) rename test/widgets/{headerfield_test.dart => field_header_test.dart} (100%) diff --git a/lib/screens/common_widgets/envfield_cell.dart b/lib/screens/common_widgets/envfield_cell.dart index 3b7d8081..5c15d92e 100644 --- a/lib/screens/common_widgets/envfield_cell.dart +++ b/lib/screens/common_widgets/envfield_cell.dart @@ -1,6 +1,6 @@ import 'package:apidash_design_system/apidash_design_system.dart'; import 'package:flutter/material.dart'; -import 'package:multi_trigger_autocomplete/multi_trigger_autocomplete.dart'; +import 'package:multi_trigger_autocomplete_plus/multi_trigger_autocomplete_plus.dart'; import 'env_trigger_field.dart'; class EnvCellField extends StatelessWidget { diff --git a/lib/widgets/field_header.dart b/lib/widgets/field_header.dart index 7bdf9092..4415fff6 100644 --- a/lib/widgets/field_header.dart +++ b/lib/widgets/field_header.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:multi_trigger_autocomplete/multi_trigger_autocomplete.dart'; +import 'package:multi_trigger_autocomplete_plus/multi_trigger_autocomplete_plus.dart'; import 'package:apidash/utils/utils.dart'; import 'package:apidash/screens/common_widgets/common_widgets.dart'; import 'package:apidash/widgets/widgets.dart'; diff --git a/packages/multi_trigger_autocomplete_plus/example/pubspec.lock b/packages/multi_trigger_autocomplete_plus/example/pubspec.lock index e3f64383..5bfc05a8 100644 --- a/packages/multi_trigger_autocomplete_plus/example/pubspec.lock +++ b/packages/multi_trigger_autocomplete_plus/example/pubspec.lock @@ -5,42 +5,42 @@ packages: dependency: transitive description: name: async - sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63 url: "https://pub.dev" source: hosted - version: "2.11.0" + version: "2.12.0" boolean_selector: dependency: transitive description: name: boolean_selector - sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" characters: dependency: transitive description: name: characters - sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.4.0" clock: dependency: transitive description: name: clock - sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.2" collection: dependency: transitive description: name: collection - sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" url: "https://pub.dev" source: hosted - version: "1.18.0" + version: "1.19.1" crypto: dependency: transitive description: @@ -61,10 +61,10 @@ packages: dependency: transitive description: name: fake_async - sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc" url: "https://pub.dev" source: hosted - version: "1.3.1" + version: "1.3.2" ffi: dependency: transitive description: @@ -135,18 +135,18 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" + sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec url: "https://pub.dev" source: hosted - version: "10.0.5" + version: "10.0.8" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" + sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 url: "https://pub.dev" source: hosted - version: "3.0.5" + version: "3.0.9" leak_tracker_testing: dependency: transitive description: @@ -167,10 +167,10 @@ packages: dependency: transitive description: name: matcher - sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 url: "https://pub.dev" source: hosted - version: "0.12.16+1" + version: "0.12.17" material_color_utilities: dependency: transitive description: @@ -183,10 +183,10 @@ packages: dependency: transitive description: name: meta - sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c url: "https://pub.dev" source: hosted - version: "1.15.0" + version: "1.16.0" multi_trigger_autocomplete_plus: dependency: "direct main" description: @@ -198,10 +198,10 @@ packages: dependency: transitive description: name: path - sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" url: "https://pub.dev" source: hosted - version: "1.9.0" + version: "1.9.1" path_provider: dependency: transitive description: @@ -270,55 +270,55 @@ packages: dependency: transitive description: flutter source: sdk - version: "0.0.99" + version: "0.0.0" source_span: dependency: transitive description: name: source_span - sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.10.1" stack_trace: dependency: transitive description: name: stack_trace - sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" url: "https://pub.dev" source: hosted - version: "1.11.1" + version: "1.12.1" stream_channel: dependency: transitive description: name: stream_channel - sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.4" string_scanner: dependency: transitive description: name: string_scanner - sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.4.1" term_glyph: dependency: transitive description: name: term_glyph - sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.2.2" test_api: dependency: transitive description: name: test_api - sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" + sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd url: "https://pub.dev" source: hosted - version: "0.7.2" + version: "0.7.4" typed_data: dependency: transitive description: @@ -339,10 +339,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" + sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14" url: "https://pub.dev" source: hosted - version: "14.2.5" + version: "14.3.1" web: dependency: transitive description: @@ -360,5 +360,5 @@ packages: source: hosted version: "1.1.0" sdks: - dart: ">=3.5.0 <4.0.0" + dart: ">=3.7.0-0 <4.0.0" flutter: ">=3.24.0" diff --git a/test/widgets/headerfield_test.dart b/test/widgets/field_header_test.dart similarity index 100% rename from test/widgets/headerfield_test.dart rename to test/widgets/field_header_test.dart index 4215807d..89a5b8c4 100644 --- a/test/widgets/headerfield_test.dart +++ b/test/widgets/field_header_test.dart @@ -1,8 +1,8 @@ -import 'package:apidash/widgets/field_header.dart'; -import 'package:extended_text_field/extended_text_field.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_portal/flutter_portal.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:flutter_portal/flutter_portal.dart'; +import 'package:extended_text_field/extended_text_field.dart'; +import 'package:apidash/widgets/field_header.dart'; import 'package:spot/spot.dart'; void main() { From ce5278f02b659662c801cb0f0634f5bb4a0327b5 Mon Sep 17 00:00:00 2001 From: Mrudul-2 <147049798+Mrudul-2@users.noreply.github.com> Date: Sat, 22 Feb 2025 13:49:02 +0530 Subject: [PATCH 07/32] integrated a beautify button in JSON codegen and also highlighted the key and string text. --- .../history_widgets/his_request_pane.dart | 3 +- .../request_pane/request_body.dart | 3 +- lib/widgets/editor_json.dart | 132 ++++++++++-------- .../lib/tokens/colors.dart | 3 + 4 files changed, 79 insertions(+), 62 deletions(-) diff --git a/lib/screens/history/history_widgets/his_request_pane.dart b/lib/screens/history/history_widgets/his_request_pane.dart index dc618e77..f9d38300 100644 --- a/lib/screens/history/history_widgets/his_request_pane.dart +++ b/lib/screens/history/history_widgets/his_request_pane.dart @@ -140,12 +140,11 @@ class HisRequestBody extends ConsumerWidget { // TODO: Fix JsonTextFieldEditor & plug it here ContentType.json => Padding( padding: kPt5o10, - child: TextFieldEditor( + child: JsonTextFieldEditor( key: Key("${selectedHistoryModel?.historyId}-json-body"), fieldKey: "${selectedHistoryModel?.historyId}-json-body-viewer", initialValue: requestModel?.body, - readOnly: true, ), ), _ => Padding( diff --git a/lib/screens/home_page/editor_pane/details_card/request_pane/request_body.dart b/lib/screens/home_page/editor_pane/details_card/request_pane/request_body.dart index 26686e67..7e42c309 100644 --- a/lib/screens/home_page/editor_pane/details_card/request_pane/request_body.dart +++ b/lib/screens/home_page/editor_pane/details_card/request_pane/request_body.dart @@ -64,7 +64,7 @@ class EditRequestBody extends ConsumerWidget { // TODO: Fix JsonTextFieldEditor & plug it here ContentType.json => Padding( padding: kPt5o10, - child: TextFieldEditor( + child: JsonTextFieldEditor( key: Key("$selectedId-json-body"), fieldKey: "$selectedId-json-body-editor", initialValue: requestModel?.httpRequestModel?.body, @@ -74,7 +74,6 @@ class EditRequestBody extends ConsumerWidget { .read(collectionStateNotifierProvider.notifier) .update(body: value); }, - hintText: kHintJson, ), ), _ => Padding( diff --git a/lib/widgets/editor_json.dart b/lib/widgets/editor_json.dart index 3c0c2abf..8cc2b78f 100644 --- a/lib/widgets/editor_json.dart +++ b/lib/widgets/editor_json.dart @@ -1,5 +1,8 @@ +import 'package:apidash/widgets/widgets.dart'; import 'dart:math' as math; import 'package:apidash/consts.dart'; +import 'package:apidash/utils/utils.dart'; +import 'dart:convert'; import 'package:apidash_design_system/apidash_design_system.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -59,71 +62,84 @@ class _JsonTextFieldEditorState extends State { if (widget.initialValue != null) { controller.text = widget.initialValue!; } - return CallbackShortcuts( - bindings: { - const SingleActivator(LogicalKeyboardKey.tab): () { - insertTab(); - }, - }, - child: JsonTextField( - stringHighlightStyle: kCodeStyle.copyWith( - color: Theme.of(context).colorScheme.secondary, + return Column( + mainAxisSize: MainAxisSize.max, + children: [ + Align( + alignment: Alignment.topRight, + child: IconButton( + icon: const Icon(Icons.format_align_left), + onPressed: () { + controller.formatJson(sortJson: false); + }, + ), ), - keyHighlightStyle: kCodeStyle.copyWith( - color: Theme.of(context).colorScheme.primary, - fontWeight: FontWeight.bold, - ), - errorContainerDecoration: BoxDecoration( - color: Theme.of(context).colorScheme.error.withOpacity( - kForegroundOpacity, + Expanded( + child: CallbackShortcuts( + bindings: { + const SingleActivator(LogicalKeyboardKey.tab): () { + insertTab(); + }, + }, + child: JsonTextField( + stringHighlightStyle: kCodeStyle.copyWith( + color: kColorJSONString, ), - borderRadius: kBorderRadius8, - ), - showErrorMessage: true, - isFormatting: true, - key: Key(widget.fieldKey), - controller: controller, - focusNode: editorFocusNode, - keyboardType: TextInputType.multiline, - expands: true, - maxLines: null, - style: kCodeStyle, - textAlignVertical: TextAlignVertical.top, - onChanged: (value) { - controller.formatJson(sortJson: false); - widget.onChanged?.call(value); - }, - decoration: InputDecoration( - hintText: kHintJson, - hintStyle: TextStyle( - color: Theme.of(context).colorScheme.outline.withOpacity( - kHintOpacity, + keyHighlightStyle: kCodeStyle.copyWith( + color: kColorJSONKey, + fontWeight: FontWeight.bold, + ), + errorContainerDecoration: BoxDecoration( + color: Theme.of(context).colorScheme.error.withOpacity( + kForegroundOpacity, + ), + borderRadius: kBorderRadius8, + ), + showErrorMessage: true, + isFormatting: true, + key: Key(widget.fieldKey), + controller: controller, + focusNode: editorFocusNode, + keyboardType: TextInputType.multiline, + expands: true, + maxLines: null, + style: kCodeStyle, + textAlignVertical: TextAlignVertical.top, + decoration: InputDecoration( + hintText: kHintJson, + hintStyle: TextStyle( + color: Theme.of(context).colorScheme.outline.withOpacity( + kHintOpacity, + ), ), - ), - focusedBorder: OutlineInputBorder( - borderRadius: kBorderRadius8, - borderSide: BorderSide( - color: Theme.of(context).colorScheme.primary.withOpacity( - kHintOpacity, + focusedBorder: OutlineInputBorder( + borderRadius: kBorderRadius8, + borderSide: BorderSide( + color: Theme.of(context).colorScheme.primary.withOpacity( + kHintOpacity, + ), ), + ), + enabledBorder: OutlineInputBorder( + borderRadius: kBorderRadius8, + borderSide: BorderSide( + color: + Theme.of(context).colorScheme.surfaceContainerHighest, + ), + ), + filled: true, + hoverColor: kColorTransparent, + fillColor: Color.alphaBlend( + (Theme.of(context).brightness == Brightness.dark + ? Theme.of(context).colorScheme.onPrimaryContainer + : Theme.of(context).colorScheme.primaryContainer) + .withOpacity(kForegroundOpacity), + Theme.of(context).colorScheme.surface), + ), ), ), - enabledBorder: OutlineInputBorder( - borderRadius: kBorderRadius8, - borderSide: BorderSide( - color: Theme.of(context).colorScheme.surfaceContainerHighest, - ), - ), - filled: true, - hoverColor: kColorTransparent, - fillColor: Color.alphaBlend( - (Theme.of(context).brightness == Brightness.dark - ? Theme.of(context).colorScheme.onPrimaryContainer - : Theme.of(context).colorScheme.primaryContainer) - .withOpacity(kForegroundOpacity), - Theme.of(context).colorScheme.surface), ), - ), + ], ); } } diff --git a/packages/apidash_design_system/lib/tokens/colors.dart b/packages/apidash_design_system/lib/tokens/colors.dart index 7d873fcd..e6f99707 100644 --- a/packages/apidash_design_system/lib/tokens/colors.dart +++ b/packages/apidash_design_system/lib/tokens/colors.dart @@ -9,6 +9,9 @@ const kColorRed = Colors.red; final kColorLightDanger = Colors.red.withOpacity(0.9); const kColorDarkDanger = Color(0xffcf6679); +const kColorJSONKey = Color(0xFF1757BA); +const kColorJSONString = Color(0xFFA82323); + const kColorSchemeSeed = Colors.blue; final kColorStatusCodeDefault = Colors.grey.shade700; From c078a11c56c11d9c0ec39b98342f67f20201d327 Mon Sep 17 00:00:00 2001 From: Mrudul-2 <147049798+Mrudul-2@users.noreply.github.com> Date: Sat, 22 Feb 2025 22:43:01 +0530 Subject: [PATCH 08/32] used themes.dart file based colors --- lib/widgets/editor_json.dart | 9 +++++++-- packages/apidash_design_system/lib/tokens/colors.dart | 3 --- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/widgets/editor_json.dart b/lib/widgets/editor_json.dart index 8cc2b78f..3d13e439 100644 --- a/lib/widgets/editor_json.dart +++ b/lib/widgets/editor_json.dart @@ -83,10 +83,15 @@ class _JsonTextFieldEditorState extends State { }, child: JsonTextField( stringHighlightStyle: kCodeStyle.copyWith( - color: kColorJSONString, + color: Theme.of(context).brightness == Brightness.dark + ? kDarkCodeTheme['string']?.color + : kLightCodeTheme['string']?.color, ), + keyHighlightStyle: kCodeStyle.copyWith( - color: kColorJSONKey, + color: Theme.of(context).brightness == Brightness.dark + ? kDarkCodeTheme['attr']?.color + : kLightCodeTheme['attr']?.color, fontWeight: FontWeight.bold, ), errorContainerDecoration: BoxDecoration( diff --git a/packages/apidash_design_system/lib/tokens/colors.dart b/packages/apidash_design_system/lib/tokens/colors.dart index e6f99707..7d873fcd 100644 --- a/packages/apidash_design_system/lib/tokens/colors.dart +++ b/packages/apidash_design_system/lib/tokens/colors.dart @@ -9,9 +9,6 @@ const kColorRed = Colors.red; final kColorLightDanger = Colors.red.withOpacity(0.9); const kColorDarkDanger = Color(0xffcf6679); -const kColorJSONKey = Color(0xFF1757BA); -const kColorJSONString = Color(0xFFA82323); - const kColorSchemeSeed = Colors.blue; final kColorStatusCodeDefault = Colors.grey.shade700; From 26554e2476202ea412d143139954eafff417dcfb Mon Sep 17 00:00:00 2001 From: Mrudul-2 <147049798+Mrudul-2@users.noreply.github.com> Date: Sat, 22 Feb 2025 23:36:27 +0530 Subject: [PATCH 09/32] readOnly implementation in widget as well as history page --- lib/screens/history/history_widgets/his_request_pane.dart | 1 + lib/widgets/editor_json.dart | 3 +++ 2 files changed, 4 insertions(+) diff --git a/lib/screens/history/history_widgets/his_request_pane.dart b/lib/screens/history/history_widgets/his_request_pane.dart index f9d38300..a64b96f8 100644 --- a/lib/screens/history/history_widgets/his_request_pane.dart +++ b/lib/screens/history/history_widgets/his_request_pane.dart @@ -145,6 +145,7 @@ class HisRequestBody extends ConsumerWidget { fieldKey: "${selectedHistoryModel?.historyId}-json-body-viewer", initialValue: requestModel?.body, + readOnly: true, ), ), _ => Padding( diff --git a/lib/widgets/editor_json.dart b/lib/widgets/editor_json.dart index 3d13e439..b7e24dbe 100644 --- a/lib/widgets/editor_json.dart +++ b/lib/widgets/editor_json.dart @@ -14,11 +14,13 @@ class JsonTextFieldEditor extends StatefulWidget { required this.fieldKey, this.onChanged, this.initialValue, + this.readOnly = false, }); final String fieldKey; final Function(String)? onChanged; final String? initialValue; + final bool readOnly; @override State createState() => _JsonTextFieldEditorState(); } @@ -101,6 +103,7 @@ class _JsonTextFieldEditorState extends State { borderRadius: kBorderRadius8, ), showErrorMessage: true, + readOnly: widget.readOnly, isFormatting: true, key: Key(widget.fieldKey), controller: controller, From 8a996f76198f6cadb1a02b216d32c7e084e398a5 Mon Sep 17 00:00:00 2001 From: Mrudul-2 <147049798+Mrudul-2@users.noreply.github.com> Date: Sun, 23 Feb 2025 13:55:22 +0530 Subject: [PATCH 10/32] implementation of bool,null and number styles --- lib/widgets/editor_json.dart | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/lib/widgets/editor_json.dart b/lib/widgets/editor_json.dart index b7e24dbe..5dafaeb8 100644 --- a/lib/widgets/editor_json.dart +++ b/lib/widgets/editor_json.dart @@ -89,7 +89,21 @@ class _JsonTextFieldEditorState extends State { ? kDarkCodeTheme['string']?.color : kLightCodeTheme['string']?.color, ), - + numberHighlightStyle: kCodeStyle.copyWith( + color: Theme.of(context).brightness == Brightness.dark + ? kDarkCodeTheme['number']?.color + : kLightCodeTheme['number']?.color, + ), + boolHighlightStyle: kCodeStyle.copyWith( + color: Theme.of(context).brightness == Brightness.dark + ? kDarkCodeTheme['literal']?.color + : kLightCodeTheme['literal']?.color, + ), + nullHighlightStyle: kCodeStyle.copyWith( + color: Theme.of(context).brightness == Brightness.dark + ? kDarkCodeTheme['variable']?.color + : kLightCodeTheme['variable']?.color, + ), keyHighlightStyle: kCodeStyle.copyWith( color: Theme.of(context).brightness == Brightness.dark ? kDarkCodeTheme['attr']?.color @@ -105,6 +119,7 @@ class _JsonTextFieldEditorState extends State { showErrorMessage: true, readOnly: widget.readOnly, isFormatting: true, + key: Key(widget.fieldKey), controller: controller, focusNode: editorFocusNode, From 06a73ec596dbd59a16e450754d08e13c04e2c9a1 Mon Sep 17 00:00:00 2001 From: DenserMeerkat Date: Tue, 25 Feb 2025 08:12:08 +0530 Subject: [PATCH 11/32] fix import --- lib/screens/common_widgets/env_trigger_options.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/screens/common_widgets/env_trigger_options.dart b/lib/screens/common_widgets/env_trigger_options.dart index c0219440..26c87111 100644 --- a/lib/screens/common_widgets/env_trigger_options.dart +++ b/lib/screens/common_widgets/env_trigger_options.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:apidash/providers/providers.dart'; -import 'package:apidash/models/models.dart'; +import 'package:apidash_core/models/models.dart'; import 'package:apidash/widgets/widgets.dart'; import 'package:apidash/utils/utils.dart'; import 'envvar_indicator.dart'; From 163e5679ce53f8cd44e7b6ecb6b5c01140625820 Mon Sep 17 00:00:00 2001 From: Clasherzz Date: Wed, 26 Feb 2025 23:25:23 +0530 Subject: [PATCH 12/32] changed to set and removecancel request added --- .../lib/services/http_client_manager.dart | 24 +++-- .../lib/services/http_service.dart | 6 +- pubspec.lock | 88 +++++++++---------- 3 files changed, 60 insertions(+), 58 deletions(-) diff --git a/packages/apidash_core/lib/services/http_client_manager.dart b/packages/apidash_core/lib/services/http_client_manager.dart index bec23214..c240cf8c 100644 --- a/packages/apidash_core/lib/services/http_client_manager.dart +++ b/packages/apidash_core/lib/services/http_client_manager.dart @@ -1,13 +1,11 @@ import 'dart:io'; -import 'dart:collection'; import 'package:flutter/foundation.dart'; import 'package:http/http.dart' as http; import 'package:http/io_client.dart'; http.Client createHttpClientWithNoSSL() { var ioClient = HttpClient() - ..badCertificateCallback = - (X509Certificate cert, String host, int port) => true; + ..badCertificateCallback = (X509Certificate cert, String host, int port) => true; return IOClient(ioClient); } @@ -15,7 +13,7 @@ class HttpClientManager { static final HttpClientManager _instance = HttpClientManager._internal(); static const int _maxCancelledRequests = 100; final Map _clients = {}; - final Queue _cancelledRequests = Queue(); + final Set _cancelledRequests = {}; factory HttpClientManager() { return _instance; @@ -23,12 +21,8 @@ class HttpClientManager { HttpClientManager._internal(); - http.Client createClient( - String requestId, { - bool noSSL = false, - }) { - final client = - (noSSL && !kIsWeb) ? createHttpClientWithNoSSL() : http.Client(); + http.Client createClient(String requestId, {bool noSSL = false}) { + final client = (noSSL && !kIsWeb) ? createHttpClientWithNoSSL() : http.Client(); _clients[requestId] = client; return client; } @@ -38,9 +32,9 @@ class HttpClientManager { _clients[requestId]?.close(); _clients.remove(requestId); - _cancelledRequests.addLast(requestId); - while (_cancelledRequests.length > _maxCancelledRequests) { - _cancelledRequests.removeFirst(); + _cancelledRequests.add(requestId); + if (_cancelledRequests.length > _maxCancelledRequests) { + _cancelledRequests.remove(_cancelledRequests.first); } } } @@ -49,6 +43,10 @@ class HttpClientManager { return _cancelledRequests.contains(requestId); } + void removeCancelledRequest(String requestId) { + _cancelledRequests.remove(requestId); + } + void closeClient(String requestId) { if (_clients.containsKey(requestId)) { _clients[requestId]?.close(); diff --git a/packages/apidash_core/lib/services/http_service.dart b/packages/apidash_core/lib/services/http_service.dart index ad06a21d..e31e236e 100644 --- a/packages/apidash_core/lib/services/http_service.dart +++ b/packages/apidash_core/lib/services/http_service.dart @@ -19,6 +19,9 @@ Future<(HttpResponse?, Duration?, String?)> sendHttpRequest( SupportedUriSchemes defaultUriScheme = kDefaultUriScheme, bool noSSL = false, }) async { + if(httpClientManager.wasRequestCancelled(requestId)){ + httpClientManager.removeCancelledRequest(requestId); + } final client = httpClientManager.createClient(requestId, noSSL: noSSL); (Uri?, String?) uriRec = getValidRequestUri( @@ -71,7 +74,8 @@ Future<(HttpResponse?, Duration?, String?)> sendHttpRequest( } } http.StreamedResponse multiPartResponse = - await multiPartRequest.send(); + await client.send(multiPartRequest); + stopwatch.stop(); http.Response convertedMultiPartResponse = await convertStreamedResponse(multiPartResponse); diff --git a/pubspec.lock b/pubspec.lock index 0334f1e7..39708f28 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,23 +5,23 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: f256b0c0ba6c7577c15e2e4e114755640a875e885099367bf6e012b19314c834 + sha256: "16e298750b6d0af7ce8a3ba7c18c69c3785d11b15ec83f6dcd0ad2a0009b3cab" url: "https://pub.dev" source: hosted - version: "72.0.0" + version: "76.0.0" _macros: dependency: transitive description: dart source: sdk - version: "0.3.2" + version: "0.3.3" analyzer: dependency: transitive description: name: analyzer - sha256: b652861553cd3990d8ed361f7979dc6d7053a9ac8843fa73820ab68ce5410139 + sha256: "1f14db053a8c23e260789e9b0980fa27f2680dd640932cae5e1137cce0e46e1e" url: "https://pub.dev" source: hosted - version: "6.7.0" + version: "6.11.0" ansi_styles: dependency: transitive description: @@ -72,10 +72,10 @@ packages: dependency: transitive description: name: async - sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63 url: "https://pub.dev" source: hosted - version: "2.11.0" + version: "2.12.0" audio_session: dependency: transitive description: @@ -104,10 +104,10 @@ packages: dependency: transitive description: name: boolean_selector - sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" build: dependency: transitive description: @@ -224,10 +224,10 @@ packages: dependency: transitive description: name: clock - sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.2" code_builder: dependency: "direct main" description: @@ -240,10 +240,10 @@ packages: dependency: transitive description: name: collection - sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf url: "https://pub.dev" source: hosted - version: "1.18.0" + version: "1.19.0" conventional_commit: dependency: transitive description: @@ -375,10 +375,10 @@ packages: dependency: transitive description: name: fake_async - sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc" url: "https://pub.dev" source: hosted - version: "1.3.1" + version: "1.3.2" ffi: dependency: transitive description: @@ -391,10 +391,10 @@ packages: dependency: transitive description: name: file - sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 url: "https://pub.dev" source: hosted - version: "7.0.0" + version: "7.0.1" file_selector: dependency: "direct main" description: @@ -883,18 +883,18 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" + sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec url: "https://pub.dev" source: hosted - version: "10.0.5" + version: "10.0.8" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" + sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 url: "https://pub.dev" source: hosted - version: "3.0.5" + version: "3.0.9" leak_tracker_testing: dependency: transitive description: @@ -931,10 +931,10 @@ packages: dependency: transitive description: name: macros - sha256: "0acaed5d6b7eab89f63350bccd82119e6c602df0f391260d0e32b5e23db79536" + sha256: "1d9e801cd66f7ea3663c45fc708450db1fa57f988142c64289142c9b7ee80656" url: "https://pub.dev" source: hosted - version: "0.1.2-main.4" + version: "0.1.3-main.0" markdown: dependency: "direct main" description: @@ -1074,10 +1074,10 @@ packages: dependency: "direct main" description: name: path - sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" url: "https://pub.dev" source: hosted - version: "1.9.0" + version: "1.9.1" path_parsing: dependency: transitive description: @@ -1162,10 +1162,10 @@ packages: dependency: transitive description: name: platform - sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65" + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" url: "https://pub.dev" source: hosted - version: "3.1.5" + version: "3.1.6" plugin_platform_interface: dependency: transitive description: @@ -1233,10 +1233,10 @@ packages: dependency: transitive description: name: process - sha256: "21e54fd2faf1b5bdd5102afd25012184a6793927648ea81eea80552ac9405b32" + sha256: "107d8be718f120bbba9dcd1e95e3bd325b1b4a4f07db64154635ba03f2567a0d" url: "https://pub.dev" source: hosted - version: "5.0.2" + version: "5.0.3" prompts: dependency: transitive description: @@ -1489,7 +1489,7 @@ packages: dependency: transitive description: flutter source: sdk - version: "0.0.99" + version: "0.0.0" source_gen: dependency: transitive description: @@ -1550,10 +1550,10 @@ packages: dependency: transitive description: name: stack_trace - sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377" url: "https://pub.dev" source: hosted - version: "1.11.1" + version: "1.12.0" state_notifier: dependency: transitive description: @@ -1582,10 +1582,10 @@ packages: dependency: transitive description: name: string_scanner - sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + sha256: "0bd04f5bb74fcd6ff0606a888a30e917af9bd52820b178eaa464beb11dca84b6" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.4.0" sync_http: dependency: transitive description: @@ -1606,26 +1606,26 @@ packages: dependency: "direct dev" description: name: test - sha256: "7ee44229615f8f642b68120165ae4c2a75fe77ae2065b1e55ae4711f6cf0899e" + sha256: "713a8789d62f3233c46b4a90b174737b2c04cb6ae4500f2aa8b1be8f03f5e67f" url: "https://pub.dev" source: hosted - version: "1.25.7" + version: "1.25.8" test_api: dependency: transitive description: name: test_api - sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" + sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c" url: "https://pub.dev" source: hosted - version: "0.7.2" + version: "0.7.3" test_core: dependency: transitive description: name: test_core - sha256: "55ea5a652e38a1dfb32943a7973f3681a60f872f8c3a05a14664ad54ef9c6696" + sha256: "12391302411737c176b0b5d6491f466b0dd56d4763e347b6714efbaa74d7953d" url: "https://pub.dev" source: hosted - version: "0.6.4" + version: "0.6.5" textwrap: dependency: transitive description: @@ -1822,10 +1822,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" + sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14" url: "https://pub.dev" source: hosted - version: "14.2.5" + version: "14.3.1" watcher: dependency: transitive description: @@ -1862,10 +1862,10 @@ packages: dependency: transitive description: name: webdriver - sha256: "003d7da9519e1e5f329422b36c4dcdf18d7d2978d1ba099ea4e45ba490ed845e" + sha256: "3d773670966f02a646319410766d3b5e1037efb7f07cc68f844d5e06cd4d61c8" url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.4" webkit_inspection_protocol: dependency: transitive description: From eefea8644d3864aad99642f6657716d6a45cdc3a Mon Sep 17 00:00:00 2001 From: Clasherzz Date: Wed, 26 Feb 2025 23:39:08 +0530 Subject: [PATCH 13/32] final commit --- .../apidash_core/lib/services/http_client_manager.dart | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/apidash_core/lib/services/http_client_manager.dart b/packages/apidash_core/lib/services/http_client_manager.dart index c240cf8c..ad6a3a17 100644 --- a/packages/apidash_core/lib/services/http_client_manager.dart +++ b/packages/apidash_core/lib/services/http_client_manager.dart @@ -5,7 +5,8 @@ import 'package:http/io_client.dart'; http.Client createHttpClientWithNoSSL() { var ioClient = HttpClient() - ..badCertificateCallback = (X509Certificate cert, String host, int port) => true; + ..badCertificateCallback = + (X509Certificate cert, String host, int port) => true; return IOClient(ioClient); } @@ -21,8 +22,11 @@ class HttpClientManager { HttpClientManager._internal(); - http.Client createClient(String requestId, {bool noSSL = false}) { - final client = (noSSL && !kIsWeb) ? createHttpClientWithNoSSL() : http.Client(); + http.Client createClient( + String requestId, + {bool noSSL = false}) { + final client = + (noSSL && !kIsWeb) ? createHttpClientWithNoSSL() : http.Client(); _clients[requestId] = client; return client; } From 632186f73cac54c5d34b526ab15ad0f0e9708efa Mon Sep 17 00:00:00 2001 From: Ankit Mahato Date: Sat, 29 Mar 2025 11:40:57 +0530 Subject: [PATCH 14/32] Fix checkbox fill color --- packages/apidash_design_system/lib/widgets/checkbox.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/apidash_design_system/lib/widgets/checkbox.dart b/packages/apidash_design_system/lib/widgets/checkbox.dart index 1707b051..eca9f257 100644 --- a/packages/apidash_design_system/lib/widgets/checkbox.dart +++ b/packages/apidash_design_system/lib/widgets/checkbox.dart @@ -34,7 +34,7 @@ class ADCheckBox extends StatelessWidget { if (states.contains(WidgetState.selected)) { return colorScheme.primary; } - return null; + return colorScheme.surfaceContainerLowest; }, )); } From ed230e40f2e70567a5dcc1496dc032510b1f7a00 Mon Sep 17 00:00:00 2001 From: Ankit Mahato Date: Sat, 29 Mar 2025 12:20:37 +0530 Subject: [PATCH 15/32] Add ADListTile --- lib/screens/settings_page.dart | 31 +++++++------- .../lib/widgets/list_tile.dart | 42 +++++++++++++++++++ .../lib/widgets/widgets.dart | 1 + 3 files changed, 58 insertions(+), 16 deletions(-) create mode 100644 packages/apidash_design_system/lib/widgets/list_tile.dart diff --git a/lib/screens/settings_page.dart b/lib/screens/settings_page.dart index eb4b6008..ca72f886 100644 --- a/lib/screens/settings_page.dart +++ b/lib/screens/settings_page.dart @@ -40,21 +40,21 @@ class SettingsPage extends ConsumerWidget { child: ListView( shrinkWrap: true, children: [ - SwitchListTile( - hoverColor: kColorTransparent, - title: const Text('Switch Theme Mode'), - subtitle: Text( - 'Current selection: ${settings.isDark ? "Dark Mode" : "Light mode"}'), + ADListTile( + type: ListTileType.switchOnOff, + title: 'Switch Theme Mode', + subtitle: + 'Current selection: ${settings.isDark ? "Dark Mode" : "Light mode"}', value: settings.isDark, onChanged: (bool? value) { ref.read(settingsProvider.notifier).update(isDark: value); }, ), - SwitchListTile( - hoverColor: kColorTransparent, - title: const Text('Collection Pane Scrollbar Visiblity'), - subtitle: Text( - 'Current selection: ${settings.alwaysShowCollectionPaneScrollbar ? "Always show" : "Show only when scrolling"}'), + ADListTile( + type: ListTileType.switchOnOff, + title: 'Collection Pane Scrollbar Visiblity', + subtitle: + 'Current selection: ${settings.alwaysShowCollectionPaneScrollbar ? "Always show" : "Show only when scrolling"}', value: settings.alwaysShowCollectionPaneScrollbar, onChanged: (bool? value) { ref @@ -77,12 +77,11 @@ class SettingsPage extends ConsumerWidget { ), ), !kIsWeb - ? SwitchListTile( - hoverColor: kColorTransparent, - title: const Text('Disable SSL verification'), - subtitle: Text( - 'Current selection: ${settings.isSSLDisabled ? "SSL Verification Disabled" : "SSL Verification Enabled"}', - ), + ? ADListTile( + type: ListTileType.switchOnOff, + title: 'Disable SSL verification', + subtitle: + 'Current selection: ${settings.isSSLDisabled ? "SSL Verification Disabled" : "SSL Verification Enabled"}', value: settings.isSSLDisabled, onChanged: (bool? value) { ref diff --git a/packages/apidash_design_system/lib/widgets/list_tile.dart b/packages/apidash_design_system/lib/widgets/list_tile.dart new file mode 100644 index 00000000..2948958f --- /dev/null +++ b/packages/apidash_design_system/lib/widgets/list_tile.dart @@ -0,0 +1,42 @@ +import 'package:flutter/material.dart'; +import '../tokens/colors.dart'; + +enum ListTileType { switchOnOff, checkbox, button } + +class ADListTile extends StatelessWidget { + const ADListTile({ + super.key, + required this.type, + this.hoverColor = kColorTransparent, + required this.title, + this.subtitle, + this.value, + this.onChanged, + }); + + final ListTileType type; + final Color hoverColor; + final String title; + final String? subtitle; + // For Switch and checkbox tiles + final bool? value; + // For Switch and checkbox tiles + final Function(bool?)? onChanged; + + @override + Widget build(BuildContext context) { + return switch (type) { + ListTileType.switchOnOff => SwitchListTile( + hoverColor: hoverColor, + title: Text(title), + subtitle: subtitle == null ? null : Text(subtitle ?? ''), + value: value ?? false, + onChanged: onChanged, + ), + // TODO: Handle this case. + ListTileType.checkbox => throw UnimplementedError(), + // TODO: Handle this case. + ListTileType.button => throw UnimplementedError(), + }; + } +} diff --git a/packages/apidash_design_system/lib/widgets/widgets.dart b/packages/apidash_design_system/lib/widgets/widgets.dart index 1ebd0836..f0850e73 100644 --- a/packages/apidash_design_system/lib/widgets/widgets.dart +++ b/packages/apidash_design_system/lib/widgets/widgets.dart @@ -4,6 +4,7 @@ export 'button_text.dart'; export 'checkbox.dart'; export 'decoration_input_textfield.dart'; export 'dropdown.dart'; +export 'list_tile.dart'; export 'popup_menu.dart'; export 'snackbar.dart'; export 'textfield_outlined.dart'; From be7680e3d29d0ee6e81730d599b6e18239d3f1ad Mon Sep 17 00:00:00 2001 From: Ankit Mahato Date: Sat, 29 Mar 2025 12:57:40 +0530 Subject: [PATCH 16/32] Update Header field input decoration --- lib/widgets/field_header.dart | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/lib/widgets/field_header.dart b/lib/widgets/field_header.dart index 896fa416..a7beb8c8 100644 --- a/lib/widgets/field_header.dart +++ b/lib/widgets/field_header.dart @@ -79,20 +79,9 @@ class _HeaderFieldState extends State { style: kCodeStyle.copyWith( color: colorScheme.onSurface, ), - decoration: InputDecoration( - hintStyle: kCodeStyle.copyWith(color: colorScheme.outlineVariant), + decoration: getTextFieldInputDecoration( + colorScheme, hintText: widget.hintText, - contentPadding: const EdgeInsets.only(bottom: 12), - focusedBorder: UnderlineInputBorder( - borderSide: BorderSide( - color: colorScheme.outline, - ), - ), - enabledBorder: UnderlineInputBorder( - borderSide: BorderSide( - color: colorScheme.surfaceContainerHighest, - ), - ), ), ), ); From bdfb6d330cfcdf7b149109e2bd26c40b5d5a6082 Mon Sep 17 00:00:00 2001 From: Ankit Mahato Date: Sat, 29 Mar 2025 13:45:42 +0530 Subject: [PATCH 17/32] Refactoring and updates --- .gitignore | 2 + .../common_widgets/common_widgets.dart | 1 + .../common_widgets/env_trigger_options.dart | 2 +- lib/screens/common_widgets/field_header.dart | 61 ++++++++ .../request_pane/request_headers.dart | 1 - lib/widgets/field_header.dart | 130 ------------------ lib/widgets/menu_header_suggestions.dart | 71 ++++++++++ lib/widgets/widgets.dart | 3 +- .../lib}/widgets/suggestions_menu_box.dart | 18 ++- .../lib/widgets/widgets.dart | 1 + test/widgets/field_header_test.dart | 3 +- 11 files changed, 152 insertions(+), 141 deletions(-) create mode 100644 lib/screens/common_widgets/field_header.dart delete mode 100644 lib/widgets/field_header.dart create mode 100644 lib/widgets/menu_header_suggestions.dart rename {lib => packages/apidash_design_system/lib}/widgets/suggestions_menu_box.dart (69%) diff --git a/.gitignore b/.gitignore index 06d603f8..73c02ac9 100644 --- a/.gitignore +++ b/.gitignore @@ -5,9 +5,11 @@ *.swp .DS_Store .atom/ +.build/ .buildlog/ .history .svn/ +.swiftpm/ migrate_working_dir/ .fvm .fvmrc diff --git a/lib/screens/common_widgets/common_widgets.dart b/lib/screens/common_widgets/common_widgets.dart index d6932407..1d316fd7 100644 --- a/lib/screens/common_widgets/common_widgets.dart +++ b/lib/screens/common_widgets/common_widgets.dart @@ -10,6 +10,7 @@ export 'envvar_indicator.dart'; export 'envvar_span.dart'; export 'envvar_popover.dart'; export 'env_trigger_options.dart'; +export 'field_header.dart'; export 'sidebar_filter.dart'; export 'sidebar_header.dart'; export 'sidebar_save_button.dart'; diff --git a/lib/screens/common_widgets/env_trigger_options.dart b/lib/screens/common_widgets/env_trigger_options.dart index 26c87111..d6dcf553 100644 --- a/lib/screens/common_widgets/env_trigger_options.dart +++ b/lib/screens/common_widgets/env_trigger_options.dart @@ -1,8 +1,8 @@ +import 'package:apidash_design_system/apidash_design_system.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:apidash/providers/providers.dart'; import 'package:apidash_core/models/models.dart'; -import 'package:apidash/widgets/widgets.dart'; import 'package:apidash/utils/utils.dart'; import 'envvar_indicator.dart'; diff --git a/lib/screens/common_widgets/field_header.dart b/lib/screens/common_widgets/field_header.dart new file mode 100644 index 00000000..4096e915 --- /dev/null +++ b/lib/screens/common_widgets/field_header.dart @@ -0,0 +1,61 @@ +import 'package:apidash/widgets/widgets.dart'; +import 'package:flutter/material.dart'; +import 'package:multi_trigger_autocomplete_plus/multi_trigger_autocomplete_plus.dart'; +import 'package:apidash/utils/utils.dart'; +import 'envfield_cell.dart'; + +class HeaderField extends StatefulWidget { + const HeaderField({ + super.key, + required this.keyId, + this.hintText, + this.initialValue, + this.onChanged, + this.colorScheme, + }); + final String keyId; + final String? hintText; + final String? initialValue; + final void Function(String)? onChanged; + final ColorScheme? colorScheme; + + @override + State createState() => _HeaderFieldState(); +} + +class _HeaderFieldState extends State { + final FocusNode focusNode = FocusNode(); + @override + Widget build(BuildContext context) { + var colorScheme = widget.colorScheme ?? Theme.of(context).colorScheme; + return EnvCellField( + keyId: widget.keyId, + hintText: widget.hintText, + initialValue: widget.initialValue ?? "", + focusNode: focusNode, + onChanged: widget.onChanged, + colorScheme: colorScheme, + autocompleteNoTrigger: AutocompleteNoTrigger( + optionsViewBuilder: (context, autocompleteQuery, controller) { + return HeaderSuggestions( + suggestionsCallback: headerSuggestionCallback, + query: autocompleteQuery.query, + onSuggestionTap: (suggestion) { + controller.text = suggestion; + widget.onChanged?.call(controller.text); + focusNode.unfocus(); + }); + }), + ); + } + + Future?> headerSuggestionCallback(String pattern) async { + if (pattern.isEmpty) { + return null; + } + return getHeaderSuggestions(pattern) + .where( + (suggestion) => suggestion.toLowerCase() != pattern.toLowerCase()) + .toList(); + } +} diff --git a/lib/screens/home_page/editor_pane/details_card/request_pane/request_headers.dart b/lib/screens/home_page/editor_pane/details_card/request_pane/request_headers.dart index ecb3f6da..0f762611 100644 --- a/lib/screens/home_page/editor_pane/details_card/request_pane/request_headers.dart +++ b/lib/screens/home_page/editor_pane/details_card/request_pane/request_headers.dart @@ -5,7 +5,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:data_table_2/data_table_2.dart'; import 'package:apidash/providers/providers.dart'; -import 'package:apidash/widgets/widgets.dart'; import 'package:apidash/consts.dart'; import 'package:apidash/screens/common_widgets/common_widgets.dart'; diff --git a/lib/widgets/field_header.dart b/lib/widgets/field_header.dart deleted file mode 100644 index 4415fff6..00000000 --- a/lib/widgets/field_header.dart +++ /dev/null @@ -1,130 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:multi_trigger_autocomplete_plus/multi_trigger_autocomplete_plus.dart'; -import 'package:apidash/utils/utils.dart'; -import 'package:apidash/screens/common_widgets/common_widgets.dart'; -import 'package:apidash/widgets/widgets.dart'; - -class HeaderField extends StatefulWidget { - const HeaderField({ - super.key, - required this.keyId, - this.hintText, - this.initialValue, - this.onChanged, - this.colorScheme, - }); - final String keyId; - final String? hintText; - final String? initialValue; - final void Function(String)? onChanged; - final ColorScheme? colorScheme; - - @override - State createState() => _HeaderFieldState(); -} - -class _HeaderFieldState extends State { - final FocusNode focusNode = FocusNode(); - @override - Widget build(BuildContext context) { - var colorScheme = widget.colorScheme ?? Theme.of(context).colorScheme; - return EnvCellField( - keyId: widget.keyId, - hintText: widget.hintText, - initialValue: widget.initialValue ?? "", - focusNode: focusNode, - onChanged: widget.onChanged, - colorScheme: colorScheme, - autocompleteNoTrigger: AutocompleteNoTrigger( - optionsViewBuilder: (context, autocompleteQuery, controller) { - return HeaderSuggestions( - suggestionsCallback: headerSuggestionCallback, - query: autocompleteQuery.query, - onSuggestionTap: (suggestion) { - controller.text = suggestion; - widget.onChanged?.call(controller.text); - focusNode.unfocus(); - }); - }), - ); - } - - Future?> headerSuggestionCallback(String pattern) async { - if (pattern.isEmpty) { - return null; - } - return getHeaderSuggestions(pattern) - .where( - (suggestion) => suggestion.toLowerCase() != pattern.toLowerCase()) - .toList(); - } -} - -class HeaderSuggestions extends StatefulWidget { - const HeaderSuggestions({ - super.key, - required this.suggestionsCallback, - required this.query, - required this.onSuggestionTap, - }); - final Future?> Function(String) suggestionsCallback; - final String query; - final ValueSetter onSuggestionTap; - - @override - State createState() => _HeaderSuggestionsState(); -} - -class _HeaderSuggestionsState extends State { - List? suggestions; - - @override - void initState() { - super.initState(); - widget.suggestionsCallback(widget.query).then((value) { - if (mounted) { - setState(() { - suggestions = value; - }); - } - }); - } - - @override - void didUpdateWidget(HeaderSuggestions oldWidget) { - super.didUpdateWidget(oldWidget); - if (oldWidget.query != widget.query) { - widget.suggestionsCallback(widget.query).then((value) { - if (mounted) { - setState(() { - suggestions = value; - }); - } - }); - } - } - - @override - Widget build(BuildContext context) { - if (suggestions == null) { - return const SizedBox.shrink(); - } - return suggestions!.isEmpty - ? const SizedBox.shrink() - : SuggestionsMenuBox( - 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, - title: Text(suggestion), - onTap: () => widget.onSuggestionTap(suggestion), - ); - }, - ), - ); - } -} diff --git a/lib/widgets/menu_header_suggestions.dart b/lib/widgets/menu_header_suggestions.dart new file mode 100644 index 00000000..6aec9aa5 --- /dev/null +++ b/lib/widgets/menu_header_suggestions.dart @@ -0,0 +1,71 @@ +import 'package:flutter/material.dart'; +import 'package:apidash_design_system/apidash_design_system.dart'; + +class HeaderSuggestions extends StatefulWidget { + const HeaderSuggestions({ + super.key, + required this.suggestionsCallback, + required this.query, + required this.onSuggestionTap, + }); + final Future?> Function(String) suggestionsCallback; + final String query; + final ValueSetter onSuggestionTap; + + @override + State createState() => _HeaderSuggestionsState(); +} + +class _HeaderSuggestionsState extends State { + List? suggestions; + + @override + void initState() { + super.initState(); + widget.suggestionsCallback(widget.query).then((value) { + if (mounted) { + setState(() { + suggestions = value; + }); + } + }); + } + + @override + void didUpdateWidget(HeaderSuggestions oldWidget) { + super.didUpdateWidget(oldWidget); + if (oldWidget.query != widget.query) { + widget.suggestionsCallback(widget.query).then((value) { + if (mounted) { + setState(() { + suggestions = value; + }); + } + }); + } + } + + @override + Widget build(BuildContext context) { + if (suggestions == null) { + return const SizedBox.shrink(); + } + return suggestions!.isEmpty + ? const SizedBox.shrink() + : SuggestionsMenuBox( + 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, + title: Text(suggestion), + onTap: () => widget.onSuggestionTap(suggestion), + ); + }, + ), + ); + } +} diff --git a/lib/widgets/widgets.dart b/lib/widgets/widgets.dart index bb584942..634ab77e 100644 --- a/lib/widgets/widgets.dart +++ b/lib/widgets/widgets.dart @@ -30,13 +30,13 @@ export 'editor.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_read_only.dart'; export 'field_url.dart'; export 'intro_message.dart'; export 'json_previewer.dart'; export 'markdown.dart'; +export 'menu_header_suggestions.dart'; export 'menu_item_card.dart'; export 'menu_sidebar_top.dart'; export 'overlay_widget.dart'; @@ -52,7 +52,6 @@ export 'splitview_drawer.dart'; export 'splitview_dashboard.dart'; export 'splitview_equal.dart'; export 'splitview_history.dart'; -export 'suggestions_menu_box.dart'; export 'tabbar_segmented.dart'; export 'table_map.dart'; export 'table_request_form.dart'; diff --git a/lib/widgets/suggestions_menu_box.dart b/packages/apidash_design_system/lib/widgets/suggestions_menu_box.dart similarity index 69% rename from lib/widgets/suggestions_menu_box.dart rename to packages/apidash_design_system/lib/widgets/suggestions_menu_box.dart index e4c5094c..5d0be2c2 100644 --- a/lib/widgets/suggestions_menu_box.dart +++ b/packages/apidash_design_system/lib/widgets/suggestions_menu_box.dart @@ -1,10 +1,17 @@ -import 'package:apidash_design_system/apidash_design_system.dart'; import 'package:flutter/material.dart'; -import 'package:apidash/consts.dart'; +import '../tokens/tokens.dart'; class SuggestionsMenuBox extends StatelessWidget { + const SuggestionsMenuBox({ + super.key, + required this.child, + this.width, + this.maxHeight, + }); + final Widget child; - const SuggestionsMenuBox({super.key, required this.child}); + final double? width; + final double? maxHeight; @override Widget build(BuildContext context) { @@ -14,10 +21,9 @@ class SuggestionsMenuBox extends StatelessWidget { type: MaterialType.card, elevation: 8, child: ConstrainedBox( - constraints: - const BoxConstraints(maxHeight: kSuggestionsMenuMaxHeight), + constraints: BoxConstraints(maxHeight: maxHeight ?? 200.0), child: Ink( - width: kSuggestionsMenuWidth, + width: width ?? 300.0, decoration: BoxDecoration( color: Theme.of(context).colorScheme.surface, borderRadius: kBorderRadius8, diff --git a/packages/apidash_design_system/lib/widgets/widgets.dart b/packages/apidash_design_system/lib/widgets/widgets.dart index 955babce..b9f832f0 100644 --- a/packages/apidash_design_system/lib/widgets/widgets.dart +++ b/packages/apidash_design_system/lib/widgets/widgets.dart @@ -5,5 +5,6 @@ export 'checkbox.dart'; export 'dropdown.dart'; export 'popup_menu.dart'; export 'snackbar.dart'; +export 'suggestions_menu_box.dart'; export 'textfield_outlined.dart'; export 'textfield_raw.dart'; diff --git a/test/widgets/field_header_test.dart b/test/widgets/field_header_test.dart index 89a5b8c4..cea49206 100644 --- a/test/widgets/field_header_test.dart +++ b/test/widgets/field_header_test.dart @@ -1,8 +1,9 @@ +import 'package:apidash/screens/common_widgets/field_header.dart'; +import 'package:apidash/widgets/menu_header_suggestions.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_portal/flutter_portal.dart'; import 'package:extended_text_field/extended_text_field.dart'; -import 'package:apidash/widgets/field_header.dart'; import 'package:spot/spot.dart'; void main() { From 81acb5818f6188c0021970b94453196e223d77af Mon Sep 17 00:00:00 2001 From: Ankit Mahato Date: Sat, 29 Mar 2025 13:49:20 +0530 Subject: [PATCH 18/32] Create field_header.dart --- lib/widgets/field_header.dart | 116 ++++++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 lib/widgets/field_header.dart diff --git a/lib/widgets/field_header.dart b/lib/widgets/field_header.dart new file mode 100644 index 00000000..12e1e2aa --- /dev/null +++ b/lib/widgets/field_header.dart @@ -0,0 +1,116 @@ +// Deprecated but kept as a backup + +/* +import 'package:apidash_design_system/apidash_design_system.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_typeahead/flutter_typeahead.dart'; +import 'package:apidash/utils/utils.dart'; + +class HeaderField extends StatefulWidget { + const HeaderField({ + super.key, + required this.keyId, + this.hintText, + this.initialValue, + this.onChanged, + this.colorScheme, + }); + final String keyId; + final String? hintText; + final String? initialValue; + final void Function(String)? onChanged; + final ColorScheme? colorScheme; + + @override + State createState() => _HeaderFieldState(); +} + +class _HeaderFieldState extends State { + final TextEditingController controller = TextEditingController(); + + @override + void initState() { + super.initState(); + controller.text = widget.initialValue ?? ""; + controller.selection = + TextSelection.collapsed(offset: controller.text.length); + } + + @override + void dispose() { + controller.dispose(); + super.dispose(); + } + + @override + void didUpdateWidget(HeaderField 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) { + var colorScheme = widget.colorScheme ?? Theme.of(context).colorScheme; + return TypeAheadField( + key: Key(widget.keyId), + hideOnEmpty: true, + controller: controller, + onSelected: (value) { + setState(() { + controller.text = value; + }); + widget.onChanged!.call(value); + }, + itemBuilder: (context, String suggestion) { + return ListTile( + dense: true, + title: Text(suggestion), + ); + }, + suggestionsCallback: headerSuggestionCallback, + decorationBuilder: (context, child) => + suggestionBoxDecorations(context, child, colorScheme), + constraints: const BoxConstraints(maxHeight: 400), + builder: (context, controller, focusNode) => TextField( + onChanged: widget.onChanged, + controller: controller, + focusNode: focusNode, + style: kCodeStyle.copyWith( + color: colorScheme.onSurface, + ), + decoration: getTextFieldInputDecoration( + colorScheme, + hintText: widget.hintText, + ), + ), + ); + } + + Theme suggestionBoxDecorations( + BuildContext context, Widget child, ColorScheme colorScheme) { + return Theme( + data: ThemeData(colorScheme: colorScheme), + child: Material( + elevation: 4, + shape: RoundedRectangleBorder( + side: BorderSide(color: Theme.of(context).dividerColor, width: 1.2), + borderRadius: const BorderRadius.vertical(bottom: Radius.circular(8)), + ), + clipBehavior: Clip.hardEdge, + child: child, + ), + ); + } + + Future?> headerSuggestionCallback(String pattern) async { + if (pattern.isEmpty) { + return null; + } + return getHeaderSuggestions(pattern); + } +} +*/ From a4dcda7e66995602977a5006b464ae2d809bd54f Mon Sep 17 00:00:00 2001 From: Ankit Mahato Date: Sat, 29 Mar 2025 16:04:58 +0530 Subject: [PATCH 19/32] Create headerfield_test.dart --- test/widgets/headerfield_test.dart | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 test/widgets/headerfield_test.dart diff --git a/test/widgets/headerfield_test.dart b/test/widgets/headerfield_test.dart new file mode 100644 index 00000000..fe87e8e0 --- /dev/null +++ b/test/widgets/headerfield_test.dart @@ -0,0 +1,26 @@ +/* This HeaderField is deprecated +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:apidash/widgets/field_header.dart'; + +void main() { + testWidgets('Testing Header Field', (tester) async { + await tester.pumpWidget( + const MaterialApp( + title: 'Header Field', + home: Scaffold( + body: Column(children: [ + HeaderField( + keyId: "1", + initialValue: "X", + ) + ]), + ), + ), + ); + + expect(find.byKey(const Key("1")), findsOneWidget); + expect(find.text('X'), findsOneWidget); + }); +} +*/ From c4ab4aaa3d19c353f204e99e5409487d07b071c5 Mon Sep 17 00:00:00 2001 From: Ankit Mahato Date: Sat, 29 Mar 2025 16:07:48 +0530 Subject: [PATCH 20/32] Update pubspec.lock --- pubspec.lock | 48 ------------------------------------------------ 1 file changed, 48 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index db83b04d..031b2434 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -536,54 +536,6 @@ packages: url: "https://pub.dev" source: hosted version: "0.21.2" - flutter_keyboard_visibility: - dependency: transitive - description: - name: flutter_keyboard_visibility - sha256: "98664be7be0e3ffca00de50f7f6a287ab62c763fc8c762e0a21584584a3ff4f8" - url: "https://pub.dev" - source: hosted - version: "6.0.0" - flutter_keyboard_visibility_linux: - dependency: transitive - description: - name: flutter_keyboard_visibility_linux - sha256: "6fba7cd9bb033b6ddd8c2beb4c99ad02d728f1e6e6d9b9446667398b2ac39f08" - url: "https://pub.dev" - source: hosted - version: "1.0.0" - flutter_keyboard_visibility_macos: - dependency: transitive - description: - name: flutter_keyboard_visibility_macos - sha256: c5c49b16fff453dfdafdc16f26bdd8fb8d55812a1d50b0ce25fc8d9f2e53d086 - url: "https://pub.dev" - source: hosted - version: "1.0.0" - flutter_keyboard_visibility_platform_interface: - dependency: transitive - description: - name: flutter_keyboard_visibility_platform_interface - sha256: e43a89845873f7be10cb3884345ceb9aebf00a659f479d1c8f4293fcb37022a4 - url: "https://pub.dev" - source: hosted - version: "2.0.0" - flutter_keyboard_visibility_web: - dependency: transitive - description: - name: flutter_keyboard_visibility_web - sha256: d3771a2e752880c79203f8d80658401d0c998e4183edca05a149f5098ce6e3d1 - url: "https://pub.dev" - source: hosted - version: "2.0.0" - flutter_keyboard_visibility_windows: - dependency: transitive - description: - name: flutter_keyboard_visibility_windows - sha256: fc4b0f0b6be9b93ae527f3d527fb56ee2d918cd88bbca438c478af7bcfd0ef73 - url: "https://pub.dev" - source: hosted - version: "1.0.0" flutter_launcher_icons: dependency: "direct dev" description: From 48d54e26a04f182ad5fca6622f4d870344da42b8 Mon Sep 17 00:00:00 2001 From: Ankit Mahato Date: Sat, 29 Mar 2025 16:11:35 +0530 Subject: [PATCH 21/32] Update headerfield_test.dart --- test/widgets/headerfield_test.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/widgets/headerfield_test.dart b/test/widgets/headerfield_test.dart index fe87e8e0..8071ea4f 100644 --- a/test/widgets/headerfield_test.dart +++ b/test/widgets/headerfield_test.dart @@ -1,9 +1,9 @@ -/* This HeaderField is deprecated import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:apidash/widgets/field_header.dart'; void main() { + /* This HeaderField is deprecated testWidgets('Testing Header Field', (tester) async { await tester.pumpWidget( const MaterialApp( @@ -22,5 +22,5 @@ void main() { expect(find.byKey(const Key("1")), findsOneWidget); expect(find.text('X'), findsOneWidget); }); + */ } -*/ From a562fc71a13eda2ee49b94adcf99323d31bfeaf4 Mon Sep 17 00:00:00 2001 From: WannaCry016 <166608111+WannaCry016@users.noreply.github.com> Date: Sat, 29 Mar 2025 19:12:09 +0530 Subject: [PATCH 22/32] Update request_pane.dart --- lib/widgets/request_pane.dart | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/lib/widgets/request_pane.dart b/lib/widgets/request_pane.dart index 95871d53..2ff82fb0 100644 --- a/lib/widgets/request_pane.dart +++ b/lib/widgets/request_pane.dart @@ -57,20 +57,19 @@ class _RequestPaneState extends State style: FilledButton.styleFrom( padding: kPh12, minimumSize: const Size(44, 44), + visualDensity: VisualDensity.compact, + alignment: Alignment.center, ), onPressed: widget.onPressedCodeButton, icon: Icon( - widget.codePaneVisible - ? Icons.code_off_rounded - : Icons.code_rounded, + widget.codePaneVisible ? Icons.code_off_rounded : Icons.code_rounded, + size: 20, ), - label: SizedBox( - width: 75, - child: Text( - widget.codePaneVisible - ? kLabelHideCode - : kLabelViewCode, - ), + label: Text( + widget.codePaneVisible ? kLabelHideCode : kLabelViewCode, + textAlign: TextAlign.center, + overflow: TextOverflow.ellipsis, + maxLines: 1, ), ), ], From d03eb12e5a9862e2b0aea95e7e8fad9a39d0ff1a Mon Sep 17 00:00:00 2001 From: Ankit Mahato Date: Sat, 29 Mar 2025 19:23:19 +0530 Subject: [PATCH 23/32] JsonTextFieldEditor fixes --- .../request_pane/request_body.dart | 5 +- lib/widgets/editor_json.dart | 214 ++++++++++-------- 2 files changed, 120 insertions(+), 99 deletions(-) diff --git a/lib/screens/home_page/editor_pane/details_card/request_pane/request_body.dart b/lib/screens/home_page/editor_pane/details_card/request_pane/request_body.dart index 7e42c309..1113a1f0 100644 --- a/lib/screens/home_page/editor_pane/details_card/request_pane/request_body.dart +++ b/lib/screens/home_page/editor_pane/details_card/request_pane/request_body.dart @@ -20,6 +20,9 @@ class EditRequestBody extends ConsumerWidget { .select((value) => value?.httpRequestModel?.bodyContentType)); final apiType = ref .watch(selectedRequestModelProvider.select((value) => value?.apiType)); + final mode = ref.watch(settingsProvider.select( + (value) => value.isDark, + )); // TODO: #178 GET->POST Currently switches to POST everytime user edits body even if the user intentionally chooses GET // final sm = ScaffoldMessenger.of(context); @@ -66,7 +69,7 @@ class EditRequestBody extends ConsumerWidget { padding: kPt5o10, child: JsonTextFieldEditor( key: Key("$selectedId-json-body"), - fieldKey: "$selectedId-json-body-editor", + fieldKey: "$selectedId-json-body-editor-$mode", initialValue: requestModel?.httpRequestModel?.body, onChanged: (String value) { // changeToPostMethod(); diff --git a/lib/widgets/editor_json.dart b/lib/widgets/editor_json.dart index 5dafaeb8..161c4dae 100644 --- a/lib/widgets/editor_json.dart +++ b/lib/widgets/editor_json.dart @@ -1,12 +1,9 @@ -import 'package:apidash/widgets/widgets.dart'; import 'dart:math' as math; -import 'package:apidash/consts.dart'; -import 'package:apidash/utils/utils.dart'; -import 'dart:convert'; import 'package:apidash_design_system/apidash_design_system.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:json_text_field/json_text_field.dart'; +import 'package:apidash/consts.dart'; class JsonTextFieldEditor extends StatefulWidget { const JsonTextFieldEditor({ @@ -49,6 +46,9 @@ class _JsonTextFieldEditorState extends State { @override void initState() { super.initState(); + if (widget.initialValue != null) { + controller.text = widget.initialValue!; + } controller.formatJson(sortJson: false); editorFocusNode = FocusNode(debugLabel: "Editor Focus Node"); } @@ -60,106 +60,124 @@ class _JsonTextFieldEditorState extends State { } @override - Widget build(BuildContext context) { - if (widget.initialValue != null) { - controller.text = widget.initialValue!; + void didUpdateWidget(JsonTextFieldEditor oldWidget) { + super.didUpdateWidget(oldWidget); + if (oldWidget.initialValue != widget.initialValue) { + controller.text = widget.initialValue ?? ""; + controller.selection = + TextSelection.collapsed(offset: controller.text.length); } - return Column( - mainAxisSize: MainAxisSize.max, + if (oldWidget.fieldKey != widget.fieldKey) { + // TODO: JsonTextField uses ExtendedTextField which does + // not rebuild because no key is provided + // so light mode to dark mode switching leads to incorrect color. + setState(() {}); + } + } + + @override + Widget build(BuildContext context) { + return Stack( children: [ - Align( - alignment: Alignment.topRight, - child: IconButton( - icon: const Icon(Icons.format_align_left), - onPressed: () { - controller.formatJson(sortJson: false); + CallbackShortcuts( + bindings: { + const SingleActivator(LogicalKeyboardKey.tab): () { + insertTab(); }, + }, + child: JsonTextField( + key: Key(widget.fieldKey), + commonTextStyle: kCodeStyle.copyWith( + color: Theme.of(context).brightness == Brightness.dark + ? kDarkCodeTheme['root']?.color + : kLightCodeTheme['root']?.color, + ), + specialCharHighlightStyle: kCodeStyle.copyWith( + color: Theme.of(context).brightness == Brightness.dark + ? kDarkCodeTheme['root']?.color + : kLightCodeTheme['root']?.color, + ), + stringHighlightStyle: kCodeStyle.copyWith( + color: Theme.of(context).brightness == Brightness.dark + ? kDarkCodeTheme['string']?.color + : kLightCodeTheme['string']?.color, + ), + numberHighlightStyle: kCodeStyle.copyWith( + color: Theme.of(context).brightness == Brightness.dark + ? kDarkCodeTheme['number']?.color + : kLightCodeTheme['number']?.color, + ), + boolHighlightStyle: kCodeStyle.copyWith( + color: Theme.of(context).brightness == Brightness.dark + ? kDarkCodeTheme['literal']?.color + : kLightCodeTheme['literal']?.color, + ), + nullHighlightStyle: kCodeStyle.copyWith( + color: Theme.of(context).brightness == Brightness.dark + ? kDarkCodeTheme['variable']?.color + : kLightCodeTheme['variable']?.color, + ), + keyHighlightStyle: kCodeStyle.copyWith( + color: Theme.of(context).brightness == Brightness.dark + ? kDarkCodeTheme['attr']?.color + : kLightCodeTheme['attr']?.color, + fontWeight: FontWeight.bold, + ), + // errorContainerDecoration: BoxDecoration( + // color: Theme.of(context).colorScheme.error.withOpacity( + // kForegroundOpacity, + // ), + // borderRadius: kBorderRadius8, + // ), + // TODO: Show error message in Global Status bar + // showErrorMessage: true, + isFormatting: true, + controller: controller, + focusNode: editorFocusNode, + keyboardType: TextInputType.multiline, + expands: true, + maxLines: null, + readOnly: widget.readOnly, + style: kCodeStyle.copyWith( + fontSize: Theme.of(context).textTheme.bodyMedium?.fontSize, + ), + textAlignVertical: TextAlignVertical.top, + onChanged: widget.onChanged, + onTapOutside: (PointerDownEvent event) { + editorFocusNode.unfocus(); + }, + decoration: InputDecoration( + hintText: kHintJson, + hintStyle: TextStyle( + color: Theme.of(context).colorScheme.outlineVariant, + ), + focusedBorder: OutlineInputBorder( + borderRadius: kBorderRadius8, + borderSide: BorderSide( + color: Theme.of(context).colorScheme.outlineVariant, + ), + ), + enabledBorder: OutlineInputBorder( + borderRadius: kBorderRadius8, + borderSide: BorderSide( + color: Theme.of(context).colorScheme.surfaceContainerHighest, + ), + ), + filled: true, + hoverColor: kColorTransparent, + fillColor: Theme.of(context).colorScheme.surfaceContainerLow, + ), ), ), - Expanded( - child: CallbackShortcuts( - bindings: { - const SingleActivator(LogicalKeyboardKey.tab): () { - insertTab(); - }, + Align( + alignment: Alignment.topRight, + child: ADIconButton( + icon: Icons.format_align_left, + tooltip: "Format JSON", + onPressed: () { + controller.formatJson(sortJson: false); + widget.onChanged?.call(controller.text); }, - child: JsonTextField( - stringHighlightStyle: kCodeStyle.copyWith( - color: Theme.of(context).brightness == Brightness.dark - ? kDarkCodeTheme['string']?.color - : kLightCodeTheme['string']?.color, - ), - numberHighlightStyle: kCodeStyle.copyWith( - color: Theme.of(context).brightness == Brightness.dark - ? kDarkCodeTheme['number']?.color - : kLightCodeTheme['number']?.color, - ), - boolHighlightStyle: kCodeStyle.copyWith( - color: Theme.of(context).brightness == Brightness.dark - ? kDarkCodeTheme['literal']?.color - : kLightCodeTheme['literal']?.color, - ), - nullHighlightStyle: kCodeStyle.copyWith( - color: Theme.of(context).brightness == Brightness.dark - ? kDarkCodeTheme['variable']?.color - : kLightCodeTheme['variable']?.color, - ), - keyHighlightStyle: kCodeStyle.copyWith( - color: Theme.of(context).brightness == Brightness.dark - ? kDarkCodeTheme['attr']?.color - : kLightCodeTheme['attr']?.color, - fontWeight: FontWeight.bold, - ), - errorContainerDecoration: BoxDecoration( - color: Theme.of(context).colorScheme.error.withOpacity( - kForegroundOpacity, - ), - borderRadius: kBorderRadius8, - ), - showErrorMessage: true, - readOnly: widget.readOnly, - isFormatting: true, - - key: Key(widget.fieldKey), - controller: controller, - focusNode: editorFocusNode, - keyboardType: TextInputType.multiline, - expands: true, - maxLines: null, - style: kCodeStyle, - textAlignVertical: TextAlignVertical.top, - decoration: InputDecoration( - hintText: kHintJson, - hintStyle: TextStyle( - color: Theme.of(context).colorScheme.outline.withOpacity( - kHintOpacity, - ), - ), - focusedBorder: OutlineInputBorder( - borderRadius: kBorderRadius8, - borderSide: BorderSide( - color: Theme.of(context).colorScheme.primary.withOpacity( - kHintOpacity, - ), - ), - ), - enabledBorder: OutlineInputBorder( - borderRadius: kBorderRadius8, - borderSide: BorderSide( - color: - Theme.of(context).colorScheme.surfaceContainerHighest, - ), - ), - filled: true, - hoverColor: kColorTransparent, - fillColor: Color.alphaBlend( - (Theme.of(context).brightness == Brightness.dark - ? Theme.of(context).colorScheme.onPrimaryContainer - : Theme.of(context).colorScheme.primaryContainer) - .withOpacity(kForegroundOpacity), - Theme.of(context).colorScheme.surface), - ), - ), ), ), ], From b9cd3e56847fa9fe14acc45129f7cb7280b696a2 Mon Sep 17 00:00:00 2001 From: Ankit Mahato Date: Sat, 29 Mar 2025 19:29:29 +0530 Subject: [PATCH 24/32] Add hintText --- .../editor_pane/details_card/request_pane/request_body.dart | 1 + lib/widgets/editor_json.dart | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/screens/home_page/editor_pane/details_card/request_pane/request_body.dart b/lib/screens/home_page/editor_pane/details_card/request_pane/request_body.dart index 1113a1f0..651e527e 100644 --- a/lib/screens/home_page/editor_pane/details_card/request_pane/request_body.dart +++ b/lib/screens/home_page/editor_pane/details_card/request_pane/request_body.dart @@ -77,6 +77,7 @@ class EditRequestBody extends ConsumerWidget { .read(collectionStateNotifierProvider.notifier) .update(body: value); }, + hintText: kHintJson, ), ), _ => Padding( diff --git a/lib/widgets/editor_json.dart b/lib/widgets/editor_json.dart index 161c4dae..33f026c5 100644 --- a/lib/widgets/editor_json.dart +++ b/lib/widgets/editor_json.dart @@ -11,12 +11,14 @@ class JsonTextFieldEditor extends StatefulWidget { required this.fieldKey, this.onChanged, this.initialValue, + this.hintText, this.readOnly = false, }); final String fieldKey; final Function(String)? onChanged; final String? initialValue; + final String? hintText; final bool readOnly; @override State createState() => _JsonTextFieldEditorState(); @@ -147,7 +149,7 @@ class _JsonTextFieldEditorState extends State { editorFocusNode.unfocus(); }, decoration: InputDecoration( - hintText: kHintJson, + hintText: widget.hintText ?? kHintContent, hintStyle: TextStyle( color: Theme.of(context).colorScheme.outlineVariant, ), From b0c0dd0646ef3d6ffbc4b31e0441e33151167db5 Mon Sep 17 00:00:00 2001 From: Ankit Mahato Date: Sat, 29 Mar 2025 20:32:29 +0530 Subject: [PATCH 25/32] Update request_body.dart --- .../editor_pane/details_card/request_pane/request_body.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/screens/home_page/editor_pane/details_card/request_pane/request_body.dart b/lib/screens/home_page/editor_pane/details_card/request_pane/request_body.dart index f6165122..8c5a805d 100644 --- a/lib/screens/home_page/editor_pane/details_card/request_pane/request_body.dart +++ b/lib/screens/home_page/editor_pane/details_card/request_pane/request_body.dart @@ -45,7 +45,6 @@ class EditRequestBody extends ConsumerWidget { child: switch (contentType) { ContentType.formdata => const Padding(padding: kPh4, child: FormDataWidget()), - // TODO: Fix JsonTextFieldEditor & plug it here ContentType.json => Padding( padding: kPt5o10, child: JsonTextFieldEditor( From c4905a24902ff44138679db1bf1fd257b6c18ce6 Mon Sep 17 00:00:00 2001 From: Ankit Mahato Date: Sat, 29 Mar 2025 20:33:42 +0530 Subject: [PATCH 26/32] Update request_pane.dart --- lib/widgets/request_pane.dart | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/lib/widgets/request_pane.dart b/lib/widgets/request_pane.dart index 2ff82fb0..9cd08996 100644 --- a/lib/widgets/request_pane.dart +++ b/lib/widgets/request_pane.dart @@ -57,19 +57,23 @@ class _RequestPaneState extends State style: FilledButton.styleFrom( padding: kPh12, minimumSize: const Size(44, 44), - visualDensity: VisualDensity.compact, - alignment: Alignment.center, + visualDensity: VisualDensity.compact, + alignment: Alignment.center, ), onPressed: widget.onPressedCodeButton, icon: Icon( - widget.codePaneVisible ? Icons.code_off_rounded : Icons.code_rounded, - size: 20, + widget.codePaneVisible + ? Icons.code_off_rounded + : Icons.code_rounded, + size: 20, ), label: Text( - widget.codePaneVisible ? kLabelHideCode : kLabelViewCode, + widget.codePaneVisible + ? kLabelHideCode + : kLabelViewCode, textAlign: TextAlign.center, - overflow: TextOverflow.ellipsis, - maxLines: 1, + overflow: TextOverflow.ellipsis, + maxLines: 1, ), ), ], From db4a5314ab7291a5393ae2c0e6c057f5370cd148 Mon Sep 17 00:00:00 2001 From: Ankit Mahato Date: Sat, 29 Mar 2025 20:36:50 +0530 Subject: [PATCH 27/32] Update request_pane.dart --- lib/widgets/request_pane.dart | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/widgets/request_pane.dart b/lib/widgets/request_pane.dart index 9cd08996..a9dfbeef 100644 --- a/lib/widgets/request_pane.dart +++ b/lib/widgets/request_pane.dart @@ -57,8 +57,6 @@ class _RequestPaneState extends State style: FilledButton.styleFrom( padding: kPh12, minimumSize: const Size(44, 44), - visualDensity: VisualDensity.compact, - alignment: Alignment.center, ), onPressed: widget.onPressedCodeButton, icon: Icon( From ea0034e31eaaa11f6d08df522674a4b2c76b5ad9 Mon Sep 17 00:00:00 2001 From: Ankit Mahato Date: Sat, 29 Mar 2025 20:40:59 +0530 Subject: [PATCH 28/32] Update request_pane.dart --- lib/widgets/request_pane.dart | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/lib/widgets/request_pane.dart b/lib/widgets/request_pane.dart index a9dfbeef..09ebd0a3 100644 --- a/lib/widgets/request_pane.dart +++ b/lib/widgets/request_pane.dart @@ -63,15 +63,17 @@ class _RequestPaneState extends State widget.codePaneVisible ? Icons.code_off_rounded : Icons.code_rounded, - size: 20, + size: 18, ), - label: Text( - widget.codePaneVisible - ? kLabelHideCode - : kLabelViewCode, - textAlign: TextAlign.center, - overflow: TextOverflow.ellipsis, - maxLines: 1, + label: SizedBox( + width: 80, + child: Text( + widget.codePaneVisible + ? kLabelHideCode + : kLabelViewCode, + overflow: TextOverflow.ellipsis, + maxLines: 1, + ), ), ), ], From 362cbb9e08098a84e7c1e0991d8e85de128fd785 Mon Sep 17 00:00:00 2001 From: Ankit Mahato Date: Sun, 30 Mar 2025 00:48:46 +0530 Subject: [PATCH 29/32] Update http_client_manager.dart --- packages/apidash_core/lib/services/http_client_manager.dart | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/apidash_core/lib/services/http_client_manager.dart b/packages/apidash_core/lib/services/http_client_manager.dart index ad6a3a17..7b815413 100644 --- a/packages/apidash_core/lib/services/http_client_manager.dart +++ b/packages/apidash_core/lib/services/http_client_manager.dart @@ -23,8 +23,9 @@ class HttpClientManager { HttpClientManager._internal(); http.Client createClient( - String requestId, - {bool noSSL = false}) { + String requestId, { + bool noSSL = false, + }) { final client = (noSSL && !kIsWeb) ? createHttpClientWithNoSSL() : http.Client(); _clients[requestId] = client; From e93c611a95c0d0faa961983953a7588d6680f51d Mon Sep 17 00:00:00 2001 From: Ankit Mahato Date: Sun, 30 Mar 2025 03:21:21 +0530 Subject: [PATCH 30/32] refactoring --- integration_test/req_helper.dart | 4 +- .../common_widgets/common_widgets.dart | 12 +++-- ...field_header.dart => envfield_header.dart} | 8 +-- .../request_pane/request_headers.dart | 2 +- .../lib/services/http_service.dart | 39 ++++++--------- .../common_widgets/envfield_header_test.dart | 50 +++++++++++++++++++ ...dart => menu_header_suggestions_test.dart} | 46 ----------------- 7 files changed, 78 insertions(+), 83 deletions(-) rename lib/screens/common_widgets/{field_header.dart => envfield_header.dart} (89%) create mode 100644 test/screens/common_widgets/envfield_header_test.dart rename test/widgets/{field_header_test.dart => menu_header_suggestions_test.dart} (57%) diff --git a/integration_test/req_helper.dart b/integration_test/req_helper.dart index 4a18745a..b16a89a8 100644 --- a/integration_test/req_helper.dart +++ b/integration_test/req_helper.dart @@ -84,7 +84,7 @@ class ApidashTestRequestHelper { var headerCells = find.descendant( of: find.byType(EditRequestHeaders), - matching: find.byType(HeaderField)); + matching: find.byType(EnvHeaderField)); var valueCells = find.descendant( of: find.byType(EditRequestHeaders), matching: find.byType(EnvCellField)); @@ -95,7 +95,7 @@ class ApidashTestRequestHelper { tester.testTextInput.enterText(keyValuePairs[i].$2); headerCells = find.descendant( of: find.byType(EditRequestHeaders), - matching: find.byType(HeaderField)); + matching: find.byType(EnvHeaderField)); valueCells = find.descendant( of: find.byType(EditRequestHeaders), matching: find.byType(EnvCellField)); diff --git a/lib/screens/common_widgets/common_widgets.dart b/lib/screens/common_widgets/common_widgets.dart index 1d316fd7..fc4c66b8 100644 --- a/lib/screens/common_widgets/common_widgets.dart +++ b/lib/screens/common_widgets/common_widgets.dart @@ -1,16 +1,18 @@ export 'api_type_dropdown.dart'; export 'button_navbar.dart'; export 'code_pane.dart'; -export 'editor_title.dart'; export 'editor_title_actions.dart'; -export 'envfield_url.dart'; +export 'editor_title.dart'; +export 'env_regexp_span_builder.dart'; +export 'env_trigger_field.dart'; +export 'env_trigger_options.dart'; export 'envfield_cell.dart'; +export 'envfield_header.dart'; +export 'envfield_url.dart'; export 'environment_dropdown.dart'; export 'envvar_indicator.dart'; -export 'envvar_span.dart'; export 'envvar_popover.dart'; -export 'env_trigger_options.dart'; -export 'field_header.dart'; +export 'envvar_span.dart'; export 'sidebar_filter.dart'; export 'sidebar_header.dart'; export 'sidebar_save_button.dart'; diff --git a/lib/screens/common_widgets/field_header.dart b/lib/screens/common_widgets/envfield_header.dart similarity index 89% rename from lib/screens/common_widgets/field_header.dart rename to lib/screens/common_widgets/envfield_header.dart index 4096e915..5975f1ad 100644 --- a/lib/screens/common_widgets/field_header.dart +++ b/lib/screens/common_widgets/envfield_header.dart @@ -4,8 +4,8 @@ import 'package:multi_trigger_autocomplete_plus/multi_trigger_autocomplete_plus. import 'package:apidash/utils/utils.dart'; import 'envfield_cell.dart'; -class HeaderField extends StatefulWidget { - const HeaderField({ +class EnvHeaderField extends StatefulWidget { + const EnvHeaderField({ super.key, required this.keyId, this.hintText, @@ -20,10 +20,10 @@ class HeaderField extends StatefulWidget { final ColorScheme? colorScheme; @override - State createState() => _HeaderFieldState(); + State createState() => _EnvHeaderFieldState(); } -class _HeaderFieldState extends State { +class _EnvHeaderFieldState extends State { final FocusNode focusNode = FocusNode(); @override Widget build(BuildContext context) { diff --git a/lib/screens/home_page/editor_pane/details_card/request_pane/request_headers.dart b/lib/screens/home_page/editor_pane/details_card/request_pane/request_headers.dart index 0f762611..658789a0 100644 --- a/lib/screens/home_page/editor_pane/details_card/request_pane/request_headers.dart +++ b/lib/screens/home_page/editor_pane/details_card/request_pane/request_headers.dart @@ -103,7 +103,7 @@ class EditRequestHeadersState extends ConsumerState { ), ), DataCell( - HeaderField( + EnvHeaderField( keyId: "$selectedId-$index-headers-k-$seed", initialValue: headerRows[index].name, hintText: kHintAddName, diff --git a/packages/apidash_core/lib/services/http_service.dart b/packages/apidash_core/lib/services/http_service.dart index e31e236e..73f82d9a 100644 --- a/packages/apidash_core/lib/services/http_service.dart +++ b/packages/apidash_core/lib/services/http_service.dart @@ -19,7 +19,7 @@ Future<(HttpResponse?, Duration?, String?)> sendHttpRequest( SupportedUriSchemes defaultUriScheme = kDefaultUriScheme, bool noSSL = false, }) async { - if(httpClientManager.wasRequestCancelled(requestId)){ + if (httpClientManager.wasRequestCancelled(requestId)) { httpClientManager.removeCancelledRequest(requestId); } final client = httpClientManager.createClient(requestId, noSSL: noSSL); @@ -82,30 +82,19 @@ Future<(HttpResponse?, Duration?, String?)> sendHttpRequest( return (convertedMultiPartResponse, stopwatch.elapsed, null); } } - switch (requestModel.method) { - case HTTPVerb.get: - response = await client.get(requestUrl, headers: headers); - break; - case HTTPVerb.head: - response = await client.head(requestUrl, headers: headers); - break; - case HTTPVerb.post: - response = - await client.post(requestUrl, headers: headers, body: body); - break; - case HTTPVerb.put: - response = - await client.put(requestUrl, headers: headers, body: body); - break; - case HTTPVerb.patch: - response = - await client.patch(requestUrl, headers: headers, body: body); - break; - case HTTPVerb.delete: - response = - await client.delete(requestUrl, headers: headers, body: body); - break; - } + response = switch (requestModel.method) { + HTTPVerb.get => await client.get(requestUrl, headers: headers), + HTTPVerb.head => response = + await client.head(requestUrl, headers: headers), + HTTPVerb.post => response = + await client.post(requestUrl, headers: headers, body: body), + HTTPVerb.put => response = + await client.put(requestUrl, headers: headers, body: body), + HTTPVerb.patch => response = + await client.patch(requestUrl, headers: headers, body: body), + HTTPVerb.delete => response = + await client.delete(requestUrl, headers: headers, body: body), + }; } if (apiType == APIType.graphql) { var requestBody = getGraphQLBody(requestModel); diff --git a/test/screens/common_widgets/envfield_header_test.dart b/test/screens/common_widgets/envfield_header_test.dart new file mode 100644 index 00000000..776648dd --- /dev/null +++ b/test/screens/common_widgets/envfield_header_test.dart @@ -0,0 +1,50 @@ +import 'package:apidash/screens/common_widgets/envfield_header.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:flutter_portal/flutter_portal.dart'; +import 'package:extended_text_field/extended_text_field.dart'; +import 'package:spot/spot.dart'; + +void main() { + group('HeaderField Widget Tests', () { + testWidgets('HeaderField renders and displays ExtendedTextField', + (tester) async { + await tester.pumpWidget( + const Portal( + child: MaterialApp( + home: Scaffold( + body: EnvHeaderField( + keyId: "testKey", + hintText: "Enter header", + ), + ), + ), + ), + ); + + spot().spot().existsOnce(); + }); + + testWidgets('HeaderField calls onChanged when text changes', + (tester) async { + String? changedText; + await tester.pumpWidget( + Portal( + child: MaterialApp( + home: Scaffold( + body: EnvHeaderField( + keyId: "testKey", + hintText: "Enter header", + onChanged: (text) => changedText = text, + ), + ), + ), + ), + ); + + await act.tap(spot().spot()); + tester.testTextInput.enterText("new header"); + expect(changedText, "new header"); + }); + }); +} diff --git a/test/widgets/field_header_test.dart b/test/widgets/menu_header_suggestions_test.dart similarity index 57% rename from test/widgets/field_header_test.dart rename to test/widgets/menu_header_suggestions_test.dart index cea49206..1c8ed3bf 100644 --- a/test/widgets/field_header_test.dart +++ b/test/widgets/menu_header_suggestions_test.dart @@ -1,54 +1,8 @@ -import 'package:apidash/screens/common_widgets/field_header.dart'; import 'package:apidash/widgets/menu_header_suggestions.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:flutter_portal/flutter_portal.dart'; -import 'package:extended_text_field/extended_text_field.dart'; -import 'package:spot/spot.dart'; void main() { - group('HeaderField Widget Tests', () { - testWidgets('HeaderField renders and displays ExtendedTextField', - (tester) async { - await tester.pumpWidget( - const Portal( - child: MaterialApp( - home: Scaffold( - body: HeaderField( - keyId: "testKey", - hintText: "Enter header", - ), - ), - ), - ), - ); - - spot().spot().existsOnce(); - }); - - testWidgets('HeaderField calls onChanged when text changes', - (tester) async { - String? changedText; - await tester.pumpWidget( - Portal( - child: MaterialApp( - home: Scaffold( - body: HeaderField( - keyId: "testKey", - hintText: "Enter header", - onChanged: (text) => changedText = text, - ), - ), - ), - ), - ); - - await act.tap(spot().spot()); - tester.testTextInput.enterText("new header"); - expect(changedText, "new header"); - }); - }); - group('HeaderSuggestions Widget Tests', () { testWidgets('HeaderSuggestions displays suggestions correctly', (tester) async { From b040e226ba2d4282cd9ac5cd1883662eae9ecefc Mon Sep 17 00:00:00 2001 From: Ankit Mahato Date: Sun, 30 Mar 2025 03:25:41 +0530 Subject: [PATCH 31/32] Update envfield_cell.dart --- lib/screens/common_widgets/envfield_cell.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/screens/common_widgets/envfield_cell.dart b/lib/screens/common_widgets/envfield_cell.dart index b0d7a45e..ececb8a2 100644 --- a/lib/screens/common_widgets/envfield_cell.dart +++ b/lib/screens/common_widgets/envfield_cell.dart @@ -32,6 +32,7 @@ class EnvCellField extends StatelessWidget { focusNode: focusNode, style: kCodeStyle.copyWith( color: clrScheme.onSurface, + fontSize: Theme.of(context).textTheme.bodyMedium?.fontSize, ), decoration: getTextFieldInputDecoration( clrScheme, From b7f3b81fe901442fa6bc167a0ac02f8778b0dd16 Mon Sep 17 00:00:00 2001 From: Ankit Mahato Date: Sun, 30 Mar 2025 03:55:46 +0530 Subject: [PATCH 32/32] Style fixes --- lib/widgets/editor.dart | 2 +- lib/widgets/editor_json.dart | 2 +- packages/apidash_design_system/lib/widgets/dropdown.dart | 3 +++ 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/widgets/editor.dart b/lib/widgets/editor.dart index a9f96732..87720f61 100644 --- a/lib/widgets/editor.dart +++ b/lib/widgets/editor.dart @@ -102,7 +102,7 @@ class _TextFieldEditorState extends State { ), filled: true, hoverColor: kColorTransparent, - fillColor: Theme.of(context).colorScheme.surfaceContainerLow, + fillColor: Theme.of(context).colorScheme.surfaceContainerLowest, ), ), ); diff --git a/lib/widgets/editor_json.dart b/lib/widgets/editor_json.dart index 33f026c5..3e4c2425 100644 --- a/lib/widgets/editor_json.dart +++ b/lib/widgets/editor_json.dart @@ -167,7 +167,7 @@ class _JsonTextFieldEditorState extends State { ), filled: true, hoverColor: kColorTransparent, - fillColor: Theme.of(context).colorScheme.surfaceContainerLow, + fillColor: Theme.of(context).colorScheme.surfaceContainerLowest, ), ), ), diff --git a/packages/apidash_design_system/lib/widgets/dropdown.dart b/packages/apidash_design_system/lib/widgets/dropdown.dart index 8aa90590..5cd88c8a 100644 --- a/packages/apidash_design_system/lib/widgets/dropdown.dart +++ b/packages/apidash_design_system/lib/widgets/dropdown.dart @@ -10,6 +10,7 @@ class ADDropdownButton extends StatelessWidget { this.isExpanded = false, this.isDense = false, this.iconSize, + this.fontSize, this.dropdownMenuItemPadding = kPs8, this.dropdownMenuItemtextStyle, }); @@ -20,6 +21,7 @@ class ADDropdownButton extends StatelessWidget { final bool isExpanded; final bool isDense; final double? iconSize; + final double? fontSize; final EdgeInsetsGeometry dropdownMenuItemPadding; final TextStyle? Function(T)? dropdownMenuItemtextStyle; @@ -38,6 +40,7 @@ class ADDropdownButton extends StatelessWidget { elevation: 4, style: kCodeStyle.copyWith( color: Theme.of(context).colorScheme.primary, + fontSize: fontSize ?? Theme.of(context).textTheme.bodyMedium?.fontSize, ), underline: Container( height: 0,