mirror of
https://github.com/foss42/apidash.git
synced 2025-12-01 18:28:25 +08:00
Merge branch 'foss42:main' into har_importer
This commit is contained in:
@@ -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));
|
||||
|
||||
@@ -1,15 +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 'envvar_span.dart';
|
||||
export 'sidebar_filter.dart';
|
||||
export 'sidebar_header.dart';
|
||||
export 'sidebar_save_button.dart';
|
||||
|
||||
@@ -9,20 +9,29 @@ class EnvironmentTriggerField extends StatefulWidget {
|
||||
super.key,
|
||||
required this.keyId,
|
||||
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 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;
|
||||
final AutocompleteNoTrigger? autocompleteNoTrigger;
|
||||
|
||||
@override
|
||||
State<EnvironmentTriggerField> createState() =>
|
||||
@@ -30,21 +39,24 @@ class EnvironmentTriggerField extends StatefulWidget {
|
||||
}
|
||||
|
||||
class EnvironmentTriggerFieldState extends State<EnvironmentTriggerField> {
|
||||
final TextEditingController controller = TextEditingController();
|
||||
final FocusNode focusNode = FocusNode();
|
||||
late TextEditingController controller;
|
||||
late FocusNode _focusNode;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
controller.text = widget.initialValue ?? '';
|
||||
controller.selection =
|
||||
TextSelection.collapsed(offset: controller.text.length);
|
||||
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();
|
||||
_focusNode.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@@ -53,9 +65,11 @@ class EnvironmentTriggerFieldState extends State<EnvironmentTriggerField> {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if ((oldWidget.keyId != widget.keyId) ||
|
||||
(oldWidget.initialValue != widget.initialValue)) {
|
||||
controller.text = widget.initialValue ?? "";
|
||||
controller.selection =
|
||||
TextSelection.collapsed(offset: controller.text.length);
|
||||
controller = widget.controller ??
|
||||
TextEditingController.fromValue(TextEditingValue(
|
||||
text: widget.initialValue!,
|
||||
selection: TextSelection.collapsed(
|
||||
offset: widget.initialValue!.length)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,9 +78,10 @@ class EnvironmentTriggerFieldState extends State<EnvironmentTriggerField> {
|
||||
return MultiTriggerAutocomplete(
|
||||
key: Key(widget.keyId),
|
||||
textEditingController: controller,
|
||||
focusNode: focusNode,
|
||||
optionsWidthFactor: widget.optionsWidthFactor,
|
||||
focusNode: _focusNode,
|
||||
optionsWidthFactor: widget.optionsWidthFactor ?? 1,
|
||||
autocompleteTriggers: [
|
||||
if (widget.autocompleteNoTrigger != null) widget.autocompleteNoTrigger!,
|
||||
AutocompleteTrigger(
|
||||
trigger: '{',
|
||||
triggerEnd: "}}",
|
||||
@@ -108,7 +123,7 @@ class EnvironmentTriggerFieldState extends State<EnvironmentTriggerField> {
|
||||
onSubmitted: widget.onFieldSubmitted,
|
||||
specialTextSpanBuilder: EnvRegExpSpanBuilder(),
|
||||
onTapOutside: (event) {
|
||||
focusNode.unfocus();
|
||||
_focusNode.unfocus();
|
||||
},
|
||||
);
|
||||
},
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import 'package:apidash/consts.dart';
|
||||
import 'package:apidash_core/apidash_core.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/providers/providers.dart';
|
||||
import 'package:apidash_core/models/models.dart';
|
||||
import 'package:apidash/utils/utils.dart';
|
||||
|
||||
import 'envvar_indicator.dart';
|
||||
|
||||
class EnvironmentTriggerOptions extends ConsumerWidget {
|
||||
@@ -26,41 +24,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),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:apidash_design_system/apidash_design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:multi_trigger_autocomplete_plus/multi_trigger_autocomplete_plus.dart';
|
||||
import 'env_trigger_field.dart';
|
||||
|
||||
class EnvCellField extends StatelessWidget {
|
||||
@@ -10,6 +11,8 @@ class EnvCellField extends StatelessWidget {
|
||||
this.hintText,
|
||||
this.onChanged,
|
||||
this.colorScheme,
|
||||
this.autocompleteNoTrigger,
|
||||
this.focusNode,
|
||||
});
|
||||
|
||||
final String keyId;
|
||||
@@ -17,6 +20,8 @@ class EnvCellField extends StatelessWidget {
|
||||
final String? hintText;
|
||||
final void Function(String)? onChanged;
|
||||
final ColorScheme? colorScheme;
|
||||
final AutocompleteNoTrigger? autocompleteNoTrigger;
|
||||
final FocusNode? focusNode;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -24,13 +29,16 @@ class EnvCellField extends StatelessWidget {
|
||||
return EnvironmentTriggerField(
|
||||
keyId: keyId,
|
||||
initialValue: initialValue,
|
||||
focusNode: focusNode,
|
||||
style: kCodeStyle.copyWith(
|
||||
color: clrScheme.onSurface,
|
||||
fontSize: Theme.of(context).textTheme.bodyMedium?.fontSize,
|
||||
),
|
||||
decoration: getTextFieldInputDecoration(
|
||||
clrScheme,
|
||||
hintText: hintText,
|
||||
),
|
||||
autocompleteNoTrigger: autocompleteNoTrigger,
|
||||
onChanged: onChanged,
|
||||
);
|
||||
}
|
||||
|
||||
61
lib/screens/common_widgets/envfield_header.dart
Normal file
61
lib/screens/common_widgets/envfield_header.dart
Normal file
@@ -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 EnvHeaderField extends StatefulWidget {
|
||||
const EnvHeaderField({
|
||||
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<EnvHeaderField> createState() => _EnvHeaderFieldState();
|
||||
}
|
||||
|
||||
class _EnvHeaderFieldState extends State<EnvHeaderField> {
|
||||
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<List<String>?> headerSuggestionCallback(String pattern) async {
|
||||
if (pattern.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
return getHeaderSuggestions(pattern)
|
||||
.where(
|
||||
(suggestion) => suggestion.toLowerCase() != pattern.toLowerCase())
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
@@ -10,18 +10,21 @@ class EnvURLField extends StatelessWidget {
|
||||
this.initialValue,
|
||||
this.onChanged,
|
||||
this.onFieldSubmitted,
|
||||
this.focusNode,
|
||||
});
|
||||
|
||||
final String selectedId;
|
||||
final String? initialValue;
|
||||
final void Function(String)? onChanged;
|
||||
final void Function(String)? onFieldSubmitted;
|
||||
final FocusNode? focusNode;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return EnvironmentTriggerField(
|
||||
keyId: "url-$selectedId",
|
||||
initialValue: initialValue,
|
||||
focusNode: focusNode,
|
||||
style: kCodeStyle,
|
||||
decoration: InputDecoration(
|
||||
hintText: kHintTextUrlCard,
|
||||
|
||||
@@ -140,7 +140,7 @@ 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",
|
||||
|
||||
@@ -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,
|
||||
));
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
@@ -42,12 +45,11 @@ 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: TextFieldEditor(
|
||||
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) {
|
||||
ref
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -104,7 +103,7 @@ class EditRequestHeadersState extends ConsumerState<EditRequestHeaders> {
|
||||
),
|
||||
),
|
||||
DataCell(
|
||||
HeaderField(
|
||||
EnvHeaderField(
|
||||
keyId: "$selectedId-$index-headers-k-$seed",
|
||||
initialValue: headerRows[index].name,
|
||||
hintText: kHintAddName,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -102,7 +102,7 @@ class _TextFieldEditorState extends State<TextFieldEditor> {
|
||||
),
|
||||
filled: true,
|
||||
hoverColor: kColorTransparent,
|
||||
fillColor: Theme.of(context).colorScheme.surfaceContainerLow,
|
||||
fillColor: Theme.of(context).colorScheme.surfaceContainerLowest,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import 'dart:math' as math;
|
||||
import 'package:apidash/consts.dart';
|
||||
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({
|
||||
@@ -11,11 +11,15 @@ 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<JsonTextFieldEditor> createState() => _JsonTextFieldEditorState();
|
||||
}
|
||||
@@ -44,6 +48,9 @@ class _JsonTextFieldEditorState extends State<JsonTextFieldEditor> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
if (widget.initialValue != null) {
|
||||
controller.text = widget.initialValue!;
|
||||
}
|
||||
controller.formatJson(sortJson: false);
|
||||
editorFocusNode = FocusNode(debugLabel: "Editor Focus Node");
|
||||
}
|
||||
@@ -55,75 +62,127 @@ class _JsonTextFieldEditorState extends State<JsonTextFieldEditor> {
|
||||
}
|
||||
|
||||
@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 CallbackShortcuts(
|
||||
bindings: <ShortcutActivator, VoidCallback>{
|
||||
const SingleActivator(LogicalKeyboardKey.tab): () {
|
||||
insertTab();
|
||||
},
|
||||
},
|
||||
child: JsonTextField(
|
||||
stringHighlightStyle: kCodeStyle.copyWith(
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
keyHighlightStyle: kCodeStyle.copyWith(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
errorContainerDecoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.error.withOpacity(
|
||||
kForegroundOpacity,
|
||||
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: [
|
||||
CallbackShortcuts(
|
||||
bindings: <ShortcutActivator, VoidCallback>{
|
||||
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: widget.hintText ?? kHintContent,
|
||||
hintStyle: TextStyle(
|
||||
color: Theme.of(context).colorScheme.outlineVariant,
|
||||
),
|
||||
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,
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: kBorderRadius8,
|
||||
borderSide: BorderSide(
|
||||
color: Theme.of(context).colorScheme.outlineVariant,
|
||||
),
|
||||
),
|
||||
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: Theme.of(context).colorScheme.surfaceContainerLowest,
|
||||
),
|
||||
),
|
||||
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),
|
||||
),
|
||||
),
|
||||
Align(
|
||||
alignment: Alignment.topRight,
|
||||
child: ADIconButton(
|
||||
icon: Icons.format_align_left,
|
||||
tooltip: "Format JSON",
|
||||
onPressed: () {
|
||||
controller.formatJson(sortJson: false);
|
||||
widget.onChanged?.call(controller.text);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// 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';
|
||||
@@ -79,20 +82,9 @@ class _HeaderFieldState extends State<HeaderField> {
|
||||
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,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -121,3 +113,4 @@ class _HeaderFieldState extends State<HeaderField> {
|
||||
return getHeaderSuggestions(pattern);
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
71
lib/widgets/menu_header_suggestions.dart
Normal file
71
lib/widgets/menu_header_suggestions.dart
Normal file
@@ -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<List<String>?> Function(String) suggestionsCallback;
|
||||
final String query;
|
||||
final ValueSetter<String> onSuggestionTap;
|
||||
|
||||
@override
|
||||
State<HeaderSuggestions> createState() => _HeaderSuggestionsState();
|
||||
}
|
||||
|
||||
class _HeaderSuggestionsState extends State<HeaderSuggestions> {
|
||||
List<String>? 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),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -63,13 +63,16 @@ class _RequestPaneState extends State<RequestPane>
|
||||
widget.codePaneVisible
|
||||
? Icons.code_off_rounded
|
||||
: Icons.code_rounded,
|
||||
size: 18,
|
||||
),
|
||||
label: SizedBox(
|
||||
width: 75,
|
||||
width: 80,
|
||||
child: Text(
|
||||
widget.codePaneVisible
|
||||
? kLabelHideCode
|
||||
: kLabelViewCode,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 1,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import 'dart:io';
|
||||
import 'dart:collection';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:http/io_client.dart';
|
||||
@@ -15,7 +14,7 @@ class HttpClientManager {
|
||||
static final HttpClientManager _instance = HttpClientManager._internal();
|
||||
static const int _maxCancelledRequests = 100;
|
||||
final Map<String, http.Client> _clients = {};
|
||||
final Queue<String> _cancelledRequests = Queue();
|
||||
final Set<String> _cancelledRequests = {};
|
||||
|
||||
factory HttpClientManager() {
|
||||
return _instance;
|
||||
@@ -38,9 +37,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 +48,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();
|
||||
|
||||
@@ -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,37 +74,27 @@ Future<(HttpResponse?, Duration?, String?)> sendHttpRequest(
|
||||
}
|
||||
}
|
||||
http.StreamedResponse multiPartResponse =
|
||||
await multiPartRequest.send();
|
||||
await client.send(multiPartRequest);
|
||||
|
||||
stopwatch.stop();
|
||||
http.Response convertedMultiPartResponse =
|
||||
await convertStreamedResponse(multiPartResponse);
|
||||
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);
|
||||
|
||||
@@ -34,7 +34,7 @@ class ADCheckBox extends StatelessWidget {
|
||||
if (states.contains(WidgetState.selected)) {
|
||||
return colorScheme.primary;
|
||||
}
|
||||
return null;
|
||||
return colorScheme.surfaceContainerLowest;
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ class ADDropdownButton<T> extends StatelessWidget {
|
||||
this.isExpanded = false,
|
||||
this.isDense = false,
|
||||
this.iconSize,
|
||||
this.fontSize,
|
||||
this.dropdownMenuItemPadding = kPs8,
|
||||
this.dropdownMenuItemtextStyle,
|
||||
});
|
||||
@@ -20,6 +21,7 @@ class ADDropdownButton<T> 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<T> 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,
|
||||
|
||||
42
packages/apidash_design_system/lib/widgets/list_tile.dart
Normal file
42
packages/apidash_design_system/lib/widgets/list_tile.dart
Normal file
@@ -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(),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../tokens/tokens.dart';
|
||||
|
||||
class SuggestionsMenuBox extends StatelessWidget {
|
||||
const SuggestionsMenuBox({
|
||||
super.key,
|
||||
required this.child,
|
||||
this.width,
|
||||
this.maxHeight,
|
||||
});
|
||||
|
||||
final Widget child;
|
||||
final double? width;
|
||||
final double? maxHeight;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ClipRRect(
|
||||
borderRadius: kBorderRadius8,
|
||||
child: Material(
|
||||
type: MaterialType.card,
|
||||
elevation: 8,
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(maxHeight: maxHeight ?? 200.0),
|
||||
child: Ink(
|
||||
width: width ?? 300.0,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
borderRadius: kBorderRadius8,
|
||||
border: Border.all(
|
||||
color: Theme.of(context).colorScheme.outlineVariant,
|
||||
),
|
||||
),
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,9 @@ 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 'suggestions_menu_box.dart';
|
||||
export 'textfield_outlined.dart';
|
||||
export 'textfield_raw.dart';
|
||||
|
||||
88
pubspec.lock
88
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:
|
||||
@@ -645,14 +597,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
|
||||
@@ -1232,38 +1176,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:
|
||||
|
||||
@@ -28,7 +28,6 @@ dependencies:
|
||||
flutter_portal: ^1.1.4
|
||||
flutter_riverpod: ^2.5.1
|
||||
flutter_svg: ^2.0.17
|
||||
flutter_typeahead: ^5.2.0
|
||||
fvp: ^0.30.0
|
||||
highlighter: ^0.1.1
|
||||
hive_flutter: ^1.1.0
|
||||
|
||||
50
test/screens/common_widgets/envfield_header_test.dart
Normal file
50
test/screens/common_widgets/envfield_header_test.dart
Normal file
@@ -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<EnvHeaderField>().spot<ExtendedTextField>().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<EnvHeaderField>().spot<ExtendedTextField>());
|
||||
tester.testTextInput.enterText("new header");
|
||||
expect(changedText, "new header");
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -3,6 +3,7 @@ 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(
|
||||
@@ -21,4 +22,5 @@ void main() {
|
||||
expect(find.byKey(const Key("1")), findsOneWidget);
|
||||
expect(find.text('X'), findsOneWidget);
|
||||
});
|
||||
*/
|
||||
}
|
||||
|
||||
61
test/widgets/menu_header_suggestions_test.dart
Normal file
61
test/widgets/menu_header_suggestions_test.dart
Normal file
@@ -0,0 +1,61 @@
|
||||
import 'package:apidash/widgets/menu_header_suggestions.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
void main() {
|
||||
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);
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user