fix: review changes

This commit is contained in:
DenserMeerkat
2024-06-24 23:41:52 +05:30
parent 7852fe98e5
commit 2f8a1ef9b2
74 changed files with 1929 additions and 1811 deletions

View File

@ -157,6 +157,12 @@ const kTextButtonMinWidth = 44.0;
const kRandMax = 100000;
const kSuggestionsMenuWidth = 300.0;
const kSuggestionsMenuMaxHeight = 200.0;
const kReqResTabWidth = 280.0;
const kReqResTabHeight = 32.0;
const kDataTableScrollbarTheme = ScrollbarThemeData(
crossAxisMargin: -4,
);

View File

@ -15,6 +15,26 @@ final selectedEnvironmentModelProvider =
return selectedId != null ? environments![selectedId] : null;
});
final availableEnvironmentVariablesStateProvider =
StateProvider<Map<String, List<EnvironmentVariableModel>>>((ref) {
Map<String, List<EnvironmentVariableModel>> result = {};
final environments = ref.watch(environmentsStateNotifierProvider);
final activeEnviormentId = ref.watch(activeEnvironmentIdStateProvider);
if (activeEnviormentId != null) {
result[activeEnviormentId] = environments?[activeEnviormentId]
?.values
.where((element) => element.enabled)
.toList() ??
[];
}
result[kGlobalEnvironmentId] = environments?[kGlobalEnvironmentId]
?.values
.where((element) => element.enabled)
.toList() ??
[];
return result;
});
final StateNotifierProvider<EnvironmentsStateNotifier,
Map<String, EnvironmentModel>?> environmentsStateNotifierProvider =
StateNotifierProvider((ref) => EnvironmentsStateNotifier(ref, hiveHandler));

View File

@ -1,4 +1,4 @@
export 'collection_providers.dart';
export 'environment_provider.dart';
export 'environment_providers.dart';
export 'settings_providers.dart';
export 'ui_providers.dart';

View File

@ -1,7 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../models/models.dart';
import 'environment_provider.dart';
import '../services/services.dart' show hiveHandler, HiveHandler;
import '../consts.dart';
@ -11,26 +10,6 @@ final codegenLanguageStateProvider = StateProvider<CodegenLanguage>((ref) =>
final activeEnvironmentIdStateProvider = StateProvider<String?>((ref) =>
ref.watch(settingsProvider.select((value) => value.activeEnvironmentId)));
final availableEnvironmentVariablesStateProvider =
StateProvider<Map<String, List<EnvironmentVariableModel>>>((ref) {
Map<String, List<EnvironmentVariableModel>> result = {};
final environments = ref.watch(environmentsStateNotifierProvider);
final activeEnviormentId = ref.watch(activeEnvironmentIdStateProvider);
if (activeEnviormentId != null) {
result[activeEnviormentId] = environments?[activeEnviormentId]
?.values
.where((element) => element.enabled)
.toList() ??
[];
}
result[kGlobalEnvironmentId] = environments?[kGlobalEnvironmentId]
?.values
.where((element) => element.enabled)
.toList() ??
[];
return result;
});
final StateNotifierProvider<ThemeStateNotifier, SettingsModel>
settingsProvider =
StateNotifierProvider((ref) => ThemeStateNotifier(hiveHandler));

View File

@ -1,200 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_portal/flutter_portal.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:apidash/consts.dart';
import 'package:apidash/models/environment_model.dart';
import 'package:apidash/providers/providers.dart';
import 'package:apidash/utils/envvar_utils.dart';
import 'package:apidash/widgets/widgets.dart';
class EnvironmentAutocompleteField extends HookConsumerWidget {
const EnvironmentAutocompleteField({
super.key,
required this.keyId,
this.initialValue,
this.onChanged,
this.onFieldSubmitted,
this.style,
this.decoration,
});
final String keyId;
final String? initialValue;
final void Function(String)? onChanged;
final void Function(String)? onFieldSubmitted;
final TextStyle? style;
final InputDecoration? decoration;
@override
Widget build(BuildContext context, WidgetRef ref) {
final mentionValue = ref.watch(environmentFieldEditStateProvider);
final envMap = ref.watch(availableEnvironmentVariablesStateProvider);
final activeEnvironmentId = ref.watch(activeEnvironmentIdStateProvider);
final initialMentions =
getMentions(initialValue, envMap, activeEnvironmentId);
final suggestions = getEnvironmentVariableSuggestions(
mentionValue, envMap, activeEnvironmentId);
return EnvironmentAutocompleteFieldBase(
key: Key(keyId),
mentionValue: mentionValue,
onMentionValueChanged: (value) {
ref.read(environmentFieldEditStateProvider.notifier).state = value;
},
initialValue: initialValue,
initialMentions: initialMentions,
suggestions: suggestions,
onChanged: onChanged,
onFieldSubmitted: onFieldSubmitted,
style: style,
decoration: decoration,
);
}
}
class EnvironmentVariableSpan extends HookConsumerWidget {
const EnvironmentVariableSpan({
super.key,
required this.suggestion,
});
final EnvironmentVariableSuggestion suggestion;
@override
Widget build(BuildContext context, WidgetRef ref) {
final environments = ref.watch(environmentsStateNotifierProvider);
final envMap = ref.watch(availableEnvironmentVariablesStateProvider);
final activeEnvironmentId = ref.watch(activeEnvironmentIdStateProvider);
final currentSuggestion =
getCurrentVariableStatus(suggestion, envMap, activeEnvironmentId);
final showPopover = useState(false);
final isMissingVariable = currentSuggestion.isUnknown;
final String scope = isMissingVariable
? 'unknown'
: getEnvironmentTitle(
environments?[currentSuggestion.environmentId]?.name);
final colorScheme = Theme.of(context).colorScheme;
var text = Text(
'{{${currentSuggestion.variable.key}}}',
style: TextStyle(
color: isMissingVariable ? colorScheme.error : colorScheme.primary,
fontWeight: FontWeight.w600),
);
return PortalTarget(
visible: showPopover.value,
portalFollower: MouseRegion(
onEnter: (_) {
showPopover.value = true;
},
onExit: (_) {
showPopover.value = false;
},
child:
EnvironmentPopoverCard(suggestion: currentSuggestion, scope: scope),
),
anchor: const Aligned(
follower: Alignment.bottomCenter,
target: Alignment.topCenter,
backup: Aligned(
follower: Alignment.topCenter,
target: Alignment.bottomCenter,
),
),
child: kIsMobile
? TapRegion(
onTapInside: (_) {
showPopover.value = true;
},
onTapOutside: (_) {
showPopover.value = false;
},
child: text,
)
: MouseRegion(
onEnter: (_) {
showPopover.value = true;
},
onExit: (_) {
showPopover.value = false;
},
child: text,
),
);
}
}
class EnvironmentPopoverCard extends StatelessWidget {
const EnvironmentPopoverCard({
super.key,
required this.suggestion,
required this.scope,
});
final EnvironmentVariableSuggestion suggestion;
final String scope;
@override
Widget build(BuildContext context) {
return Material(
type: MaterialType.card,
borderRadius: kBorderRadius8,
elevation: 8,
child: ConstrainedBox(
constraints: const BoxConstraints(minWidth: 200),
child: Ink(
padding: kP8,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surface,
borderRadius: kBorderRadius8,
border: Border.all(
color: Theme.of(context).colorScheme.outlineVariant,
),
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisSize: MainAxisSize.min,
children: [
EnvironmentIndicator(suggestion: suggestion),
kHSpacer10,
Text(suggestion.variable.key),
],
),
kVSpacer5,
Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
'VALUE',
style: Theme.of(context).textTheme.labelSmall,
),
kHSpacer10,
Text(suggestion.variable.value),
],
),
kVSpacer5,
Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
'SCOPE',
style: Theme.of(context).textTheme.labelSmall,
),
kHSpacer10,
Text(scope),
],
),
],
),
),
),
);
}
}

View File

@ -1,218 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:apidash/providers/providers.dart';
import 'package:apidash/widgets/widgets.dart';
import 'package:apidash/consts.dart';
class ScaffoldTitle extends StatelessWidget {
const ScaffoldTitle({
super.key,
required this.title,
this.showMenu = true,
this.onSelected,
});
final String title;
final bool showMenu;
final Function(ItemMenuOption)? onSelected;
@override
Widget build(BuildContext context) {
return IgnorePointer(
ignoring: !showMenu,
child: ClipRRect(
borderRadius: BorderRadius.circular(8),
child: Material(
color: Colors.transparent,
child: ItemCardMenu(
offset: const Offset(0, 40),
shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
splashRadius: 0,
tooltip: title,
onSelected: onSelected,
child: Ink(
color: Theme.of(context)
.colorScheme
.secondaryContainer
.withOpacity(0.3),
padding:
const EdgeInsets.symmetric(horizontal: 12.0, vertical: 6),
child: Row(
children: [
Expanded(
child: Text(
title,
overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.bodyLarge,
maxLines: 1,
),
),
showMenu
? const Icon(
Icons.more_vert_rounded,
size: 20,
)
: const SizedBox.shrink(),
],
),
),
),
),
),
);
}
}
class EnvironmentDropdown extends ConsumerWidget {
const EnvironmentDropdown({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final environments = ref.watch(environmentsStateNotifierProvider);
final environmentsList = environments?.values.toList();
environmentsList
?.removeWhere((element) => element.id == kGlobalEnvironmentId);
final activeEnvironment = ref.watch(activeEnvironmentIdStateProvider);
return Container(
decoration: BoxDecoration(
border: Border.all(
color: Theme.of(context).colorScheme.outlineVariant,
),
borderRadius: kBorderRadius8,
),
child: EnvironmentPopupMenu(
activeEnvironment: environments?[activeEnvironment],
environments: environmentsList,
onChanged: (value) {
ref.read(activeEnvironmentIdStateProvider.notifier).state =
value?.id;
ref
.read(settingsProvider.notifier)
.update(activeEnvironmentId: value?.id);
ref.read(hasUnsavedChangesProvider.notifier).state = true;
},
));
}
}
class TitleActionsArray extends StatelessWidget {
const TitleActionsArray({
super.key,
this.onRenamePressed,
this.onDuplicatePressed,
this.onDeletePressed,
});
final void Function()? onRenamePressed;
final void Function()? onDuplicatePressed;
final void Function()? onDeletePressed;
@override
Widget build(BuildContext context) {
var verticalDivider = VerticalDivider(
width: 2,
color: Theme.of(context).colorScheme.outlineVariant,
);
return ClipRRect(
borderRadius: kBorderRadius20,
child: Material(
color: Colors.transparent,
child: Ink(
decoration: BoxDecoration(
border: Border.all(
color: Theme.of(context).colorScheme.outlineVariant,
),
borderRadius: kBorderRadius20,
),
child: SizedBox(
height: 32,
child: IntrinsicHeight(
child: Row(
children: [
iconButton(
"Rename",
Icons.edit_rounded,
onRenamePressed,
padding: const EdgeInsets.only(left: 4),
),
verticalDivider,
iconButton(
"Delete",
Icons.delete_rounded,
onDeletePressed,
),
verticalDivider,
iconButton(
"Duplicate",
Icons.copy_rounded,
onDuplicatePressed,
padding: const EdgeInsets.only(right: 4),
),
],
),
),
),
),
),
);
}
Widget iconButton(
String tooltip, IconData iconData, void Function()? onPressed,
{EdgeInsets padding = const EdgeInsets.all(0)}) {
return Tooltip(
message: tooltip,
child: IconButton(
style: ButtonStyle(
padding: MaterialStateProperty.all(const EdgeInsets.all(0) + padding),
shape: MaterialStateProperty.all(const ContinuousRectangleBorder()),
),
onPressed: onPressed,
icon: Icon(
iconData,
size: 16,
),
),
);
}
}
showRenameDialog(
BuildContext context,
String dialogTitle,
String? name,
Function(String) onRename,
) {
showDialog(
context: context,
builder: (context) {
final controller = TextEditingController(text: name ?? "");
controller.selection =
TextSelection(baseOffset: 0, extentOffset: controller.text.length);
return AlertDialog(
title: Text(dialogTitle),
content: TextField(
autofocus: true,
controller: controller,
decoration: const InputDecoration(hintText: "Enter new name"),
),
actions: <Widget>[
OutlinedButton(
onPressed: () {
Navigator.pop(context);
},
child: const Text('CANCEL')),
FilledButton(
onPressed: () {
final val = controller.text.trim();
onRename(val);
Navigator.pop(context);
Future.delayed(const Duration(milliseconds: 100), () {
controller.dispose();
});
},
child: const Text('OK')),
],
);
});
}

View File

@ -1,124 +0,0 @@
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:apidash/providers/providers.dart';
import 'package:apidash/extensions/extensions.dart';
import 'package:apidash/widgets/widgets.dart';
import 'package:apidash/consts.dart';
class SidebarHeader extends ConsumerWidget {
const SidebarHeader({super.key, this.onAddNew});
final Function()? onAddNew;
@override
Widget build(BuildContext context, WidgetRef ref) {
final mobileScaffoldKey = ref.read(mobileScaffoldKeyStateProvider);
return Padding(
padding: kPe8,
child: Row(
children: [
const SaveButton(),
const Spacer(),
ElevatedButton(
onPressed: onAddNew,
child: const Text(
kLabelPlusNew,
style: kTextStyleButton,
),
),
context.width <= kMinWindowSize.width
? IconButton(
style: IconButton.styleFrom(
padding: const EdgeInsets.all(4),
minimumSize: const Size(30, 30)),
onPressed: () {
mobileScaffoldKey.currentState?.closeDrawer();
},
icon: const Icon(Icons.chevron_left),
)
: const SizedBox.shrink(),
],
),
);
}
}
class SidebarFilter extends StatelessWidget {
const SidebarFilter({
super.key,
this.onFilterFieldChanged,
this.filterHintText,
});
final Function(String)? onFilterFieldChanged;
final String? filterHintText;
@override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.only(right: 8),
decoration: BoxDecoration(
borderRadius: kBorderRadius8,
border: Border.all(
color: Theme.of(context).colorScheme.surfaceVariant,
),
),
child: Row(
children: [
kHSpacer5,
Icon(
Icons.filter_alt,
size: 18,
color: Theme.of(context).colorScheme.secondary,
),
kHSpacer5,
Expanded(
child: RawTextField(
style: Theme.of(context).textTheme.bodyMedium,
hintText: filterHintText ?? "Filter by name",
onChanged: onFilterFieldChanged,
),
),
],
),
);
}
}
class SaveButton extends ConsumerWidget {
const SaveButton({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final overlayWidget = OverlayWidgetTemplate(context: context);
final savingData = ref.watch(saveDataStateProvider);
final hasUnsavedChanges = ref.watch(hasUnsavedChangesProvider);
return TextButton.icon(
onPressed: (savingData || !hasUnsavedChanges)
? null
: () async {
overlayWidget.show(
widget: const SavingOverlay(saveCompleted: false));
await ref
.read(collectionStateNotifierProvider.notifier)
.saveData();
await ref
.read(environmentsStateNotifierProvider.notifier)
.saveEnvironments();
overlayWidget.hide();
overlayWidget.show(
widget: const SavingOverlay(saveCompleted: true));
await Future.delayed(const Duration(seconds: 1));
overlayWidget.hide();
},
icon: const Icon(
Icons.save,
size: 20,
),
label: const Text(
kLabelSave,
style: kTextStyleButton,
),
);
}
}

View File

@ -0,0 +1,11 @@
export 'editor_title.dart';
export 'editor_title_actions.dart';
export 'envfield_url.dart';
export 'envfield_cell.dart';
export 'environment_dropdown.dart';
export 'envvar_indicator.dart';
export 'envvar_span.dart';
export 'envvar_popover.dart';
export 'sidebar_filter.dart';
export 'sidebar_header.dart';
export 'sidebar_save_button.dart';

View File

@ -0,0 +1,62 @@
import 'package:flutter/material.dart';
import 'package:apidash/widgets/widgets.dart';
import 'package:apidash/consts.dart';
class EditorTitle extends StatelessWidget {
const EditorTitle({
super.key,
required this.title,
this.showMenu = true,
this.onSelected,
});
final String title;
final bool showMenu;
final Function(ItemMenuOption)? onSelected;
@override
Widget build(BuildContext context) {
return IgnorePointer(
ignoring: !showMenu,
child: ClipRRect(
borderRadius: BorderRadius.circular(8),
child: Material(
color: Colors.transparent,
child: ItemCardMenu(
offset: const Offset(0, 40),
shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
splashRadius: 0,
tooltip: title,
onSelected: onSelected,
child: Ink(
color: Theme.of(context)
.colorScheme
.secondaryContainer
.withOpacity(0.3),
padding:
const EdgeInsets.symmetric(horizontal: 12.0, vertical: 6),
child: Row(
children: [
Expanded(
child: Text(
title,
overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.bodyLarge,
maxLines: 1,
),
),
showMenu
? const Icon(
Icons.more_vert_rounded,
size: 20,
)
: const SizedBox.shrink(),
],
),
),
),
),
),
);
}
}

View File

@ -0,0 +1,84 @@
import 'package:flutter/material.dart';
import 'package:apidash/consts.dart';
class EditorTitleActions extends StatelessWidget {
const EditorTitleActions({
super.key,
this.onRenamePressed,
this.onDuplicatePressed,
this.onDeletePressed,
});
final void Function()? onRenamePressed;
final void Function()? onDuplicatePressed;
final void Function()? onDeletePressed;
@override
Widget build(BuildContext context) {
var verticalDivider = VerticalDivider(
width: 2,
color: Theme.of(context).colorScheme.outlineVariant,
);
return ClipRRect(
borderRadius: kBorderRadius20,
child: Material(
color: Colors.transparent,
child: Ink(
decoration: BoxDecoration(
border: Border.all(
color: Theme.of(context).colorScheme.outlineVariant,
),
borderRadius: kBorderRadius20,
),
child: SizedBox(
height: 32,
child: IntrinsicHeight(
child: Row(
children: [
iconButton(
"Rename",
Icons.edit_rounded,
onRenamePressed,
padding: const EdgeInsets.only(left: 4),
),
verticalDivider,
iconButton(
"Delete",
Icons.delete_rounded,
onDeletePressed,
),
verticalDivider,
iconButton(
"Duplicate",
Icons.copy_rounded,
onDuplicatePressed,
padding: const EdgeInsets.only(right: 4),
),
],
),
),
),
),
),
);
}
Widget iconButton(
String tooltip, IconData iconData, void Function()? onPressed,
{EdgeInsets padding = const EdgeInsets.all(0)}) {
return Tooltip(
message: tooltip,
child: IconButton(
style: ButtonStyle(
padding: MaterialStateProperty.all(const EdgeInsets.all(0) + padding),
shape: MaterialStateProperty.all(const ContinuousRectangleBorder()),
),
onPressed: onPressed,
icon: Icon(
iconData,
size: 16,
),
),
);
}
}

View File

@ -1,41 +1,6 @@
import 'package:flutter/material.dart';
import 'package:apidash/consts.dart';
import 'environment_autocomplete.dart';
class EnvURLField extends StatelessWidget {
const EnvURLField({
super.key,
required this.selectedId,
this.initialValue,
this.onChanged,
this.onFieldSubmitted,
});
final String selectedId;
final String? initialValue;
final void Function(String)? onChanged;
final void Function(String)? onFieldSubmitted;
@override
Widget build(BuildContext context) {
return EnvironmentAutocompleteField(
keyId: "url-$selectedId",
initialValue: initialValue,
style: kCodeStyle,
decoration: InputDecoration(
hintText: kHintTextUrlCard,
hintStyle: kCodeStyle.copyWith(
color: Theme.of(context).colorScheme.outline.withOpacity(
kHintOpacity,
),
),
border: InputBorder.none,
),
onChanged: onChanged,
onFieldSubmitted: onFieldSubmitted,
);
}
}
import 'environment_field.dart';
class EnvCellField extends StatelessWidget {
const EnvCellField({
@ -56,7 +21,7 @@ class EnvCellField extends StatelessWidget {
@override
Widget build(BuildContext context) {
var clrScheme = colorScheme ?? Theme.of(context).colorScheme;
return EnvironmentAutocompleteField(
return EnvironmentField(
keyId: keyId,
initialValue: initialValue,
style: kCodeStyle.copyWith(

View File

@ -0,0 +1,38 @@
import 'package:flutter/material.dart';
import 'package:apidash/consts.dart';
import 'environment_field.dart';
class EnvURLField extends StatelessWidget {
const EnvURLField({
super.key,
required this.selectedId,
this.initialValue,
this.onChanged,
this.onFieldSubmitted,
});
final String selectedId;
final String? initialValue;
final void Function(String)? onChanged;
final void Function(String)? onFieldSubmitted;
@override
Widget build(BuildContext context) {
return EnvironmentField(
keyId: "url-$selectedId",
initialValue: initialValue,
style: kCodeStyle,
decoration: InputDecoration(
hintText: kHintTextUrlCard,
hintStyle: kCodeStyle.copyWith(
color: Theme.of(context).colorScheme.outline.withOpacity(
kHintOpacity,
),
),
border: InputBorder.none,
),
onChanged: onChanged,
onFieldSubmitted: onFieldSubmitted,
);
}
}

View File

@ -0,0 +1,37 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:apidash/providers/providers.dart';
import 'package:apidash/widgets/widgets.dart';
import 'package:apidash/consts.dart';
class EnvironmentDropdown extends ConsumerWidget {
const EnvironmentDropdown({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final environments = ref.watch(environmentsStateNotifierProvider);
final environmentsList = environments?.values.toList();
environmentsList
?.removeWhere((element) => element.id == kGlobalEnvironmentId);
final activeEnvironment = ref.watch(activeEnvironmentIdStateProvider);
return Container(
decoration: BoxDecoration(
border: Border.all(
color: Theme.of(context).colorScheme.outlineVariant,
),
borderRadius: kBorderRadius8,
),
child: EnvironmentPopupMenu(
activeEnvironment: environments?[activeEnvironment],
environments: environmentsList,
onChanged: (value) {
ref.read(activeEnvironmentIdStateProvider.notifier).state =
value?.id;
ref
.read(settingsProvider.notifier)
.update(activeEnvironmentId: value?.id);
ref.read(hasUnsavedChangesProvider.notifier).state = true;
},
));
}
}

View File

@ -0,0 +1,49 @@
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:apidash/providers/providers.dart';
import 'package:apidash/utils/envvar_utils.dart';
import 'package:apidash/widgets/widgets.dart';
class EnvironmentField extends HookConsumerWidget {
const EnvironmentField({
super.key,
required this.keyId,
this.initialValue,
this.onChanged,
this.onFieldSubmitted,
this.style,
this.decoration,
});
final String keyId;
final String? initialValue;
final void Function(String)? onChanged;
final void Function(String)? onFieldSubmitted;
final TextStyle? style;
final InputDecoration? decoration;
@override
Widget build(BuildContext context, WidgetRef ref) {
final mentionValue = ref.watch(environmentFieldEditStateProvider);
final envMap = ref.watch(availableEnvironmentVariablesStateProvider);
final activeEnvironmentId = ref.watch(activeEnvironmentIdStateProvider);
final initialMentions =
getMentions(initialValue, envMap, activeEnvironmentId);
final suggestions = getEnvironmentVariableSuggestions(
mentionValue, envMap, activeEnvironmentId);
return EnvironmentFieldBase(
key: Key(keyId),
mentionValue: mentionValue,
onMentionValueChanged: (value) {
ref.read(environmentFieldEditStateProvider.notifier).state = value;
},
initialValue: initialValue,
initialMentions: initialMentions,
suggestions: suggestions,
onChanged: onChanged,
onFieldSubmitted: onFieldSubmitted,
style: style,
decoration: decoration,
);
}
}

View File

@ -0,0 +1,34 @@
import 'package:flutter/material.dart';
import 'package:apidash/models/models.dart';
import 'package:apidash/consts.dart';
class EnvVarIndicator extends StatelessWidget {
const EnvVarIndicator({super.key, required this.suggestion});
final EnvironmentVariableSuggestion suggestion;
@override
Widget build(BuildContext context) {
final isUnknown = suggestion.isUnknown;
final isGlobal = suggestion.environmentId == kGlobalEnvironmentId;
return Container(
padding: kP4,
decoration: BoxDecoration(
color: isUnknown
? Theme.of(context).colorScheme.errorContainer
: isGlobal
? Theme.of(context).colorScheme.secondaryContainer
: Theme.of(context).colorScheme.primaryContainer,
borderRadius: kBorderRadius4,
),
child: Icon(
isUnknown
? Icons.block
: isGlobal
? Icons.public
: Icons.computer,
size: 16,
),
);
}
}

View File

@ -0,0 +1,75 @@
import 'package:flutter/material.dart';
import 'package:apidash/consts.dart';
import 'package:apidash/models/models.dart';
import 'common_widgets.dart';
class EnvVarPopover extends StatelessWidget {
const EnvVarPopover({
super.key,
required this.suggestion,
required this.scope,
});
final EnvironmentVariableSuggestion suggestion;
final String scope;
@override
Widget build(BuildContext context) {
return Material(
type: MaterialType.card,
borderRadius: kBorderRadius8,
elevation: 8,
child: ConstrainedBox(
constraints: const BoxConstraints(minWidth: 200),
child: Ink(
padding: kP8,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surface,
borderRadius: kBorderRadius8,
border: Border.all(
color: Theme.of(context).colorScheme.outlineVariant,
),
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisSize: MainAxisSize.min,
children: [
EnvVarIndicator(suggestion: suggestion),
kHSpacer10,
Text(suggestion.variable.key),
],
),
kVSpacer5,
Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
'VALUE',
style: Theme.of(context).textTheme.labelSmall,
),
kHSpacer10,
Text(suggestion.variable.value),
],
),
kVSpacer5,
Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
'SCOPE',
style: Theme.of(context).textTheme.labelSmall,
),
kHSpacer10,
Text(scope),
],
),
],
),
),
),
);
}
}

View File

@ -0,0 +1,84 @@
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_portal/flutter_portal.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:apidash/consts.dart';
import 'package:apidash/models/models.dart';
import 'package:apidash/providers/providers.dart';
import 'package:apidash/utils/utils.dart';
import 'envvar_popover.dart';
class EnvVarSpan extends HookConsumerWidget {
const EnvVarSpan({
super.key,
required this.suggestion,
});
final EnvironmentVariableSuggestion suggestion;
@override
Widget build(BuildContext context, WidgetRef ref) {
final environments = ref.watch(environmentsStateNotifierProvider);
final envMap = ref.watch(availableEnvironmentVariablesStateProvider);
final activeEnvironmentId = ref.watch(activeEnvironmentIdStateProvider);
final currentSuggestion =
getCurrentVariableStatus(suggestion, envMap, activeEnvironmentId);
final showPopover = useState(false);
final isMissingVariable = currentSuggestion.isUnknown;
final String scope = isMissingVariable
? 'unknown'
: getEnvironmentTitle(
environments?[currentSuggestion.environmentId]?.name);
final colorScheme = Theme.of(context).colorScheme;
var text = Text(
'{{${currentSuggestion.variable.key}}}',
style: TextStyle(
color: isMissingVariable ? colorScheme.error : colorScheme.primary,
fontWeight: FontWeight.w600),
);
return PortalTarget(
visible: showPopover.value,
portalFollower: MouseRegion(
onEnter: (_) {
showPopover.value = true;
},
onExit: (_) {
showPopover.value = false;
},
child: EnvVarPopover(suggestion: currentSuggestion, scope: scope),
),
anchor: const Aligned(
follower: Alignment.bottomCenter,
target: Alignment.topCenter,
backup: Aligned(
follower: Alignment.topCenter,
target: Alignment.bottomCenter,
),
),
child: kIsMobile
? TapRegion(
onTapInside: (_) {
showPopover.value = true;
},
onTapOutside: (_) {
showPopover.value = false;
},
child: text,
)
: MouseRegion(
onEnter: (_) {
showPopover.value = true;
},
onExit: (_) {
showPopover.value = false;
},
child: text,
),
);
}
}

View File

@ -0,0 +1,45 @@
import 'package:flutter/material.dart';
import 'package:apidash/widgets/widgets.dart';
import 'package:apidash/consts.dart';
class SidebarFilter extends StatelessWidget {
const SidebarFilter({
super.key,
this.onFilterFieldChanged,
this.filterHintText,
});
final Function(String)? onFilterFieldChanged;
final String? filterHintText;
@override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.only(right: 8),
decoration: BoxDecoration(
borderRadius: kBorderRadius8,
border: Border.all(
color: Theme.of(context).colorScheme.surfaceVariant,
),
),
child: Row(
children: [
kHSpacer5,
Icon(
Icons.filter_alt,
size: 18,
color: Theme.of(context).colorScheme.secondary,
),
kHSpacer5,
Expanded(
child: RawTextField(
style: Theme.of(context).textTheme.bodyMedium,
hintText: filterHintText ?? "Filter by name",
onChanged: onFilterFieldChanged,
),
),
],
),
);
}
}

View File

@ -0,0 +1,43 @@
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:apidash/providers/providers.dart';
import 'package:apidash/extensions/extensions.dart';
import 'package:apidash/consts.dart';
import 'sidebar_save_button.dart';
class SidebarHeader extends ConsumerWidget {
const SidebarHeader({super.key, this.onAddNew});
final Function()? onAddNew;
@override
Widget build(BuildContext context, WidgetRef ref) {
final mobileScaffoldKey = ref.read(mobileScaffoldKeyStateProvider);
return Padding(
padding: kPe8,
child: Row(
children: [
const SaveButton(),
const Spacer(),
ElevatedButton(
onPressed: onAddNew,
child: const Text(
kLabelPlusNew,
style: kTextStyleButton,
),
),
context.width <= kMinWindowSize.width
? IconButton(
style: IconButton.styleFrom(
padding: const EdgeInsets.all(4),
minimumSize: const Size(30, 30)),
onPressed: () {
mobileScaffoldKey.currentState?.closeDrawer();
},
icon: const Icon(Icons.chevron_left),
)
: const SizedBox.shrink(),
],
),
);
}
}

View File

@ -0,0 +1,44 @@
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:apidash/consts.dart';
import 'package:apidash/providers/providers.dart';
import 'package:apidash/widgets/widgets.dart';
class SaveButton extends ConsumerWidget {
const SaveButton({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final overlayWidget = OverlayWidgetTemplate(context: context);
final savingData = ref.watch(saveDataStateProvider);
final hasUnsavedChanges = ref.watch(hasUnsavedChangesProvider);
return TextButton.icon(
onPressed: (savingData || !hasUnsavedChanges)
? null
: () async {
overlayWidget.show(
widget: const SavingOverlay(saveCompleted: false));
await ref
.read(collectionStateNotifierProvider.notifier)
.saveData();
await ref
.read(environmentsStateNotifierProvider.notifier)
.saveEnvironments();
overlayWidget.hide();
overlayWidget.show(
widget: const SavingOverlay(saveCompleted: true));
await Future.delayed(const Duration(seconds: 1));
overlayWidget.hide();
},
icon: const Icon(
Icons.save,
size: 20,
),
label: const Text(
kLabelSave,
style: kTextStyleButton,
),
);
}
}

View File

@ -2,8 +2,9 @@ import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:apidash/providers/providers.dart';
import 'package:apidash/extensions/extensions.dart';
import 'package:apidash/widgets/widgets.dart';
import 'package:apidash/consts.dart';
import '../common/main_editor_widgets.dart';
import '../common_widgets/common_widgets.dart';
import './editor_pane/variables_pane.dart';
class EnvironmentEditor extends ConsumerWidget {
@ -39,7 +40,7 @@ class EnvironmentEditor extends ConsumerWidget {
const SizedBox(
width: 6,
),
TitleActionsArray(
EditorTitleActions(
onRenamePressed: () {
showRenameDialog(context, "Rename Environment", name,
(val) {

View File

@ -5,7 +5,7 @@ import 'package:apidash/extensions/extensions.dart';
import 'package:apidash/providers/providers.dart';
import 'package:apidash/utils/utils.dart';
import 'package:apidash/consts.dart';
import '../common/main_editor_widgets.dart';
import '../common_widgets/common_widgets.dart';
import 'environments_pane.dart';
import 'environment_editor.dart';
@ -22,10 +22,10 @@ class EnvironmentPage extends ConsumerWidget {
final name = getEnvironmentTitle(ref.watch(
selectedEnvironmentModelProvider.select((value) => value?.name)));
if (context.isMediumWindow) {
return TwoDrawerScaffold(
return DrawerSplitView(
scaffoldKey: scaffoldKey,
mainContent: const EnvironmentEditor(),
title: ScaffoldTitle(
title: EditorTitle(
title: name,
showMenu: id != kGlobalEnvironmentId,
onSelected: (ItemMenuOption item) {

View File

@ -6,7 +6,7 @@ import 'package:apidash/models/environment_model.dart';
import 'package:apidash/providers/providers.dart';
import 'package:apidash/widgets/widgets.dart';
import 'package:apidash/consts.dart';
import '../common/sidebar_widgets.dart';
import '../common_widgets/common_widgets.dart';
class EnvironmentsPane extends ConsumerWidget {
const EnvironmentsPane({

View File

@ -5,7 +5,7 @@ import 'package:apidash/extensions/extensions.dart';
import 'package:apidash/widgets/widgets.dart';
import 'package:apidash/models/models.dart';
import 'package:apidash/consts.dart';
import 'package:apidash/screens/common/sidebar_widgets.dart';
import '../common_widgets/common_widgets.dart';
class CollectionPane extends ConsumerWidget {
const CollectionPane({

View File

@ -6,7 +6,7 @@ import 'package:apidash/providers/providers.dart';
import 'package:apidash/widgets/widgets.dart';
import 'package:apidash/models/models.dart';
import 'package:apidash/consts.dart';
import 'package:apidash/screens/common/environment_textfields.dart';
import 'package:apidash/screens/common_widgets/common_widgets.dart';
class EditRequestHeaders extends ConsumerStatefulWidget {
const EditRequestHeaders({super.key});

View File

@ -6,7 +6,7 @@ import 'package:apidash/providers/providers.dart';
import 'package:apidash/widgets/widgets.dart';
import 'package:apidash/models/models.dart';
import 'package:apidash/consts.dart';
import 'package:apidash/screens/common/environment_textfields.dart';
import 'package:apidash/screens/common_widgets/common_widgets.dart';
class EditRequestURLParams extends ConsumerStatefulWidget {
const EditRequestURLParams({super.key});

View File

@ -2,10 +2,11 @@ import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:apidash/providers/providers.dart';
import 'package:apidash/extensions/extensions.dart';
import 'package:apidash/widgets/widgets.dart';
import 'package:apidash/consts.dart';
import 'details_card/details_card.dart';
import 'details_card/request_pane/request_pane.dart';
import '../../common/main_editor_widgets.dart';
import '../../common_widgets/common_widgets.dart';
import 'url_card.dart';
class RequestEditor extends StatelessWidget {
@ -69,7 +70,7 @@ class RequestEditorTopBar extends ConsumerWidget {
const SizedBox(
width: 6,
),
TitleActionsArray(
EditorTitleActions(
onRenamePressed: () {
showRenameDialog(context, "Rename Request", name, (val) {
ref

View File

@ -4,7 +4,7 @@ import 'package:apidash/providers/providers.dart';
import 'package:apidash/widgets/widgets.dart';
import 'package:apidash/consts.dart';
import 'package:apidash/extensions/extensions.dart';
import '../../common/environment_textfields.dart';
import '../../common_widgets/common_widgets.dart';
class EditorPaneRequestURLCard extends StatelessWidget {
const EditorPaneRequestURLCard({super.key});
@ -45,7 +45,7 @@ class EditorPaneRequestURLCard extends StatelessWidget {
kHSpacer20,
SizedBox(
height: 36,
child: SendButton(),
child: SendRequestButton(),
)
],
),
@ -104,9 +104,9 @@ class URLTextField extends ConsumerWidget {
}
}
class SendButton extends ConsumerWidget {
class SendRequestButton extends ConsumerWidget {
final Function()? onTap;
const SendButton({
const SendRequestButton({
super.key,
this.onTap,
});
@ -117,7 +117,7 @@ class SendButton extends ConsumerWidget {
final isWorking = ref.watch(
selectedRequestModelProvider.select((value) => value?.isWorking));
return SendRequestButton(
return SendButton(
isWorking: isWorking ?? false,
onTap: () {
onTap?.call();

View File

@ -42,16 +42,16 @@ class RequestResponseTabbar extends StatelessWidget {
Widget build(BuildContext context) {
return Center(
child: Container(
width: 280,
height: 32,
width: kReqResTabWidth,
height: kReqResTabHeight,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(50),
borderRadius: kBorderRadius20,
border: Border.all(
color: Theme.of(context).colorScheme.outlineVariant,
),
),
child: ClipRRect(
borderRadius: BorderRadius.circular(50),
borderRadius: kBorderRadius20,
child: TabBar(
dividerColor: Colors.transparent,
indicatorWeight: 0.0,
@ -63,18 +63,18 @@ class RequestResponseTabbar extends StatelessWidget {
color: Theme.of(context).colorScheme.onPrimary,
),
unselectedLabelStyle: kTextStyleTab,
splashBorderRadius: BorderRadius.circular(50),
splashBorderRadius: kBorderRadius20,
indicator: BoxDecoration(
borderRadius: BorderRadius.circular(50),
borderRadius: kBorderRadius20,
color: Theme.of(context).colorScheme.primary,
),
controller: controller,
tabs: const <Widget>[
Tab(
text: "Request",
text: kLabelRequest,
),
Tab(
text: "Response",
text: kLabelResponse,
),
],
),

View File

@ -9,7 +9,7 @@ import '../../home_page/collection_pane.dart';
import '../../home_page/editor_pane/url_card.dart';
import '../../home_page/editor_pane/details_card/code_pane.dart';
import '../../home_page/editor_pane/editor_default.dart';
import '../../common/main_editor_widgets.dart';
import '../../common_widgets/common_widgets.dart';
import '../widgets/page_base.dart';
import 'request_response_tabs.dart';
@ -35,9 +35,9 @@ class _RequestResponsePageState extends ConsumerState<RequestResponsePage>
ref.watch(selectedRequestModelProvider.select((value) => value?.name)));
final TabController requestResponseTabController =
useTabController(initialLength: 2, vsync: this);
return TwoDrawerScaffold(
return DrawerSplitView(
scaffoldKey: widget.scaffoldKey,
title: ScaffoldTitle(
title: EditorTitle(
title: name,
onSelected: (ItemMenuOption item) {
if (item == ItemMenuOption.edit) {
@ -125,12 +125,15 @@ class RequestResponsePageBottombar extends ConsumerWidget {
},
icon: const Icon(Icons.code_rounded),
),
SendButton(
onTap: () {
if (requestResponseTabController.index != 1) {
requestResponseTabController.animateTo(1);
}
},
SizedBox(
height: 36,
child: SendRequestButton(
onTap: () {
if (requestResponseTabController.index != 1) {
requestResponseTabController.animateTo(1);
}
},
),
),
],
),

View File

@ -1,8 +1,8 @@
import 'package:apidash/consts.dart';
import 'package:apidash/models/models.dart';
import 'package:apidash/screens/common/environment_autocomplete.dart';
import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
import 'package:apidash/consts.dart';
import 'package:apidash/models/models.dart';
import '../screens/common_widgets/common_widgets.dart';
String getEnvironmentTitle(String? name) {
if (name == null || name.trim() == "") {
@ -126,7 +126,7 @@ List<(String, Object?, Widget?)> getMentions(
mentions.add((
'{{${variable?.key ?? variableName}}}',
suggestion,
EnvironmentVariableSpan(suggestion: suggestion)
EnvVarSpan(suggestion: suggestion)
));
}

View File

@ -1,5 +1,6 @@
export 'ui_utils.dart';
export 'convert_utils.dart';
export 'header_utils.dart';
export 'http_utils.dart';
export 'file_utils.dart';
export 'window_utils.dart';

View File

@ -0,0 +1,23 @@
import 'package:flutter/material.dart';
import 'package:apidash/consts.dart';
class ClearResponseButton extends StatelessWidget {
const ClearResponseButton({
super.key,
this.onPressed,
});
final VoidCallback? onPressed;
@override
Widget build(BuildContext context) {
return IconButton(
tooltip: kTooltipClearResponse,
onPressed: onPressed,
icon: const Icon(
Icons.delete,
size: 16,
),
);
}
}

View File

@ -0,0 +1,43 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:apidash/consts.dart';
import "snackbars.dart";
class CopyButton extends StatelessWidget {
const CopyButton({
super.key,
required this.toCopy,
this.showLabel = true,
});
final String toCopy;
final bool showLabel;
@override
Widget build(BuildContext context) {
var sm = ScaffoldMessenger.of(context);
return Tooltip(
message: showLabel ? '' : kLabelCopy,
child: SizedBox(
width: showLabel ? null : kTextButtonMinWidth,
child: TextButton(
onPressed: () async {
await Clipboard.setData(ClipboardData(text: toCopy));
sm.hideCurrentSnackBar();
sm.showSnackBar(getSnackBar("Copied"));
},
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(
Icons.content_copy,
size: 20,
),
if (showLabel) const Text(kLabelCopy)
],
),
),
),
);
}
}

View File

@ -0,0 +1,30 @@
import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:apidash/consts.dart';
class DiscordButton extends StatelessWidget {
const DiscordButton({
super.key,
this.text,
});
final String? text;
@override
Widget build(BuildContext context) {
var label = text ?? 'Discord Server';
return FilledButton.icon(
onPressed: () {
launchUrl(Uri.parse(kDiscordUrl));
},
icon: const Icon(
Icons.discord,
size: 20.0,
),
label: Text(
label,
style: kTextStyleButton,
),
);
}
}

View File

@ -0,0 +1,43 @@
import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:apidash/consts.dart';
class RepoButton extends StatelessWidget {
const RepoButton({
super.key,
this.text,
this.icon,
});
final String? text;
final IconData? icon;
@override
Widget build(BuildContext context) {
var label = text ?? "GitHub";
if (icon == null) {
return FilledButton(
onPressed: () {
launchUrl(Uri.parse(kGitUrl));
},
child: Text(
label,
style: kTextStyleButton,
),
);
}
return FilledButton.icon(
onPressed: () {
launchUrl(Uri.parse(kGitUrl));
},
icon: Icon(
icon,
size: 20.0,
),
label: Text(
label,
style: kTextStyleButton,
),
);
}
}

View File

@ -0,0 +1,67 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:apidash/utils/utils.dart';
import 'package:apidash/consts.dart';
import "snackbars.dart";
class SaveInDownloadsButton extends StatelessWidget {
const SaveInDownloadsButton({
super.key,
this.content,
this.mimeType,
this.ext,
this.name,
this.showLabel = true,
});
final Uint8List? content;
final String? mimeType;
final String? ext;
final String? name;
final bool showLabel;
@override
Widget build(BuildContext context) {
var sm = ScaffoldMessenger.of(context);
return Tooltip(
message: showLabel ? '' : kLabelDownload,
child: SizedBox(
width: showLabel ? null : kTextButtonMinWidth,
child: TextButton(
onPressed: (content != null)
? () async {
var message = "";
var path = await getFileDownloadpath(
name,
ext ?? getFileExtension(mimeType),
);
if (path != null) {
try {
await saveFile(path, content!);
var sp = getShortPath(path);
message = 'Saved to $sp';
} catch (e) {
message = "An error occurred while saving file.";
}
} else {
message = "Unable to determine the download path.";
}
sm.hideCurrentSnackBar();
sm.showSnackBar(getSnackBar(message, small: false));
}
: null,
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(
Icons.download,
size: 20,
),
if (showLabel) const Text(kLabelDownload)
],
),
),
),
);
}
}

View File

@ -0,0 +1,41 @@
import 'package:flutter/material.dart';
import 'package:apidash/consts.dart';
class SendButton extends StatelessWidget {
const SendButton({
super.key,
required this.isWorking,
required this.onTap,
});
final bool isWorking;
final void Function() onTap;
@override
Widget build(BuildContext context) {
return FilledButton(
onPressed: isWorking ? null : onTap,
child: Row(
mainAxisSize: MainAxisSize.min,
children: isWorking
? const [
Text(
kLabelSending,
style: kTextStyleButton,
),
]
: const [
Text(
kLabelSend,
style: kTextStyleButton,
),
kHSpacer10,
Icon(
size: 16,
Icons.send,
),
],
),
);
}
}

View File

@ -1,234 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:apidash/utils/utils.dart';
import 'package:apidash/consts.dart';
import "snackbars.dart";
class CopyButton extends StatelessWidget {
const CopyButton({
super.key,
required this.toCopy,
this.showLabel = true,
});
final String toCopy;
final bool showLabel;
@override
Widget build(BuildContext context) {
var sm = ScaffoldMessenger.of(context);
return Tooltip(
message: showLabel ? '' : kLabelCopy,
child: SizedBox(
width: showLabel ? null : kTextButtonMinWidth,
child: TextButton(
onPressed: () async {
await Clipboard.setData(ClipboardData(text: toCopy));
sm.hideCurrentSnackBar();
sm.showSnackBar(getSnackBar("Copied"));
},
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(
Icons.content_copy,
size: 20,
),
if (showLabel) const Text(kLabelCopy)
],
),
),
),
);
}
}
class SendRequestButton extends StatelessWidget {
const SendRequestButton({
super.key,
required this.isWorking,
required this.onTap,
});
final bool isWorking;
final void Function() onTap;
@override
Widget build(BuildContext context) {
return FilledButton(
onPressed: isWorking ? null : onTap,
child: Row(
mainAxisSize: MainAxisSize.min,
children: isWorking
? const [
Text(
kLabelSending,
style: kTextStyleButton,
),
]
: const [
Text(
kLabelSend,
style: kTextStyleButton,
),
kHSpacer10,
Icon(
size: 16,
Icons.send,
),
],
),
);
}
}
class SaveInDownloadsButton extends StatelessWidget {
const SaveInDownloadsButton({
super.key,
this.content,
this.mimeType,
this.ext,
this.name,
this.showLabel = true,
});
final Uint8List? content;
final String? mimeType;
final String? ext;
final String? name;
final bool showLabel;
@override
Widget build(BuildContext context) {
var sm = ScaffoldMessenger.of(context);
return Tooltip(
message: showLabel ? '' : kLabelDownload,
child: SizedBox(
width: showLabel ? null : kTextButtonMinWidth,
child: TextButton(
onPressed: (content != null)
? () async {
var message = "";
var path = await getFileDownloadpath(
name,
ext ?? getFileExtension(mimeType),
);
if (path != null) {
try {
await saveFile(path, content!);
var sp = getShortPath(path);
message = 'Saved to $sp';
} catch (e) {
message = "An error occurred while saving file.";
}
} else {
message = "Unable to determine the download path.";
}
sm.hideCurrentSnackBar();
sm.showSnackBar(getSnackBar(message, small: false));
}
: null,
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(
Icons.download,
size: 20,
),
if (showLabel) const Text(kLabelDownload)
],
),
),
),
);
}
}
class RepoButton extends StatelessWidget {
const RepoButton({
super.key,
this.text,
this.icon,
});
final String? text;
final IconData? icon;
@override
Widget build(BuildContext context) {
var label = text ?? "GitHub";
if (icon == null) {
return FilledButton(
onPressed: () {
launchUrl(Uri.parse(kGitUrl));
},
child: Text(
label,
style: kTextStyleButton,
),
);
}
return FilledButton.icon(
onPressed: () {
launchUrl(Uri.parse(kGitUrl));
},
icon: Icon(
icon,
size: 20.0,
),
label: Text(
label,
style: kTextStyleButton,
),
);
}
}
class DiscordButton extends StatelessWidget {
const DiscordButton({
super.key,
this.text,
});
final String? text;
@override
Widget build(BuildContext context) {
var label = text ?? 'Discord Server';
return FilledButton.icon(
onPressed: () {
launchUrl(Uri.parse(kDiscordUrl));
},
icon: const Icon(
Icons.discord,
size: 20.0,
),
label: Text(
label,
style: kTextStyleButton,
),
);
}
}
class ClearResponseButton extends StatelessWidget {
const ClearResponseButton({
super.key,
this.onPressed,
});
final VoidCallback? onPressed;
@override
Widget build(BuildContext context) {
return IconButton(
tooltip: kTooltipClearResponse,
onPressed: onPressed,
icon: const Icon(
Icons.delete,
size: 16,
),
);
}
}

View File

@ -0,0 +1,22 @@
import 'package:flutter/material.dart';
import 'package:apidash/consts.dart';
class RequestDetailsCard extends StatelessWidget {
const RequestDetailsCard({super.key, this.child});
final Widget? child;
@override
@override
Widget build(BuildContext context) {
return Card(
shape: RoundedRectangleBorder(
side: BorderSide(
color: Theme.of(context).colorScheme.surfaceVariant,
),
borderRadius: kBorderRadius12,
),
elevation: 0,
child: child,
);
}
}

View File

@ -0,0 +1,127 @@
import 'package:flutter/material.dart';
import 'package:apidash/consts.dart';
import 'package:apidash/utils/utils.dart';
import 'menus.dart' show ItemCardMenu;
class SidebarEnvironmentCard extends StatelessWidget {
const SidebarEnvironmentCard({
super.key,
required this.id,
this.isGlobal = false,
this.isActive = false,
this.name,
this.selectedId,
this.editRequestId,
this.setActive,
this.onTap,
this.onDoubleTap,
this.onSecondaryTap,
this.onChangedNameEditor,
this.focusNode,
this.onTapOutsideNameEditor,
this.onMenuSelected,
});
final String id;
final bool isGlobal;
final bool isActive;
final String? name;
final String? selectedId;
final String? editRequestId;
final void Function(bool?)? setActive;
final void Function()? onTap;
final void Function()? onDoubleTap;
final void Function()? onSecondaryTap;
final Function(String)? onChangedNameEditor;
final FocusNode? focusNode;
final Function()? onTapOutsideNameEditor;
final Function(ItemMenuOption)? onMenuSelected;
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
final Color color =
isGlobal ? colorScheme.secondaryContainer : colorScheme.surface;
final Color colorVariant = colorScheme.surfaceVariant.withOpacity(0.5);
final Color surfaceTint = colorScheme.primary;
bool isSelected = selectedId == id;
bool inEditMode = editRequestId == id;
String nm = getEnvironmentTitle(name);
return Tooltip(
message: nm,
triggerMode: TooltipTriggerMode.manual,
waitDuration: const Duration(seconds: 1),
child: Card(
shape: const RoundedRectangleBorder(
borderRadius: kBorderRadius8,
),
elevation: isSelected ? 1 : 0,
surfaceTintColor: isSelected ? surfaceTint : null,
color: isSelected && !isGlobal
? colorScheme.brightness == Brightness.dark
? colorVariant
: color
: color,
margin: EdgeInsets.zero,
child: InkWell(
borderRadius: kBorderRadius8,
hoverColor: colorVariant,
focusColor: colorVariant.withOpacity(0.5),
onTap: inEditMode ? null : onTap,
onSecondaryTap: onSecondaryTap,
child: Padding(
padding: EdgeInsets.only(
left: 6,
right: isSelected ? 6 : 10,
top: 5,
bottom: 5,
),
child: SizedBox(
height: 20,
child: Row(
children: [
kHSpacer4,
Expanded(
child: inEditMode
? TextFormField(
key: ValueKey("$id-name"),
initialValue: name,
focusNode: focusNode,
style: Theme.of(context).textTheme.bodyMedium,
onTapOutside: (_) {
onTapOutsideNameEditor?.call();
},
onFieldSubmitted: (value) {
onTapOutsideNameEditor?.call();
},
onChanged: onChangedNameEditor,
decoration: const InputDecoration(
isCollapsed: true,
contentPadding: EdgeInsets.zero,
border: InputBorder.none,
),
)
: Text(
nm,
softWrap: false,
overflow: TextOverflow.fade,
),
),
Visibility(
visible: isSelected && !inEditMode && !isGlobal,
child: SizedBox(
width: 28,
child: ItemCardMenu(
onSelected: onMenuSelected,
),
),
),
],
),
),
),
),
),
);
}
}

View File

@ -132,146 +132,3 @@ class SidebarRequestCard extends StatelessWidget {
);
}
}
class RequestDetailsCard extends StatelessWidget {
const RequestDetailsCard({super.key, this.child});
final Widget? child;
@override
@override
Widget build(BuildContext context) {
return Card(
shape: RoundedRectangleBorder(
side: BorderSide(
color: Theme.of(context).colorScheme.surfaceVariant,
),
borderRadius: kBorderRadius12,
),
elevation: 0,
child: child,
);
}
}
class SidebarEnvironmentCard extends StatelessWidget {
const SidebarEnvironmentCard({
super.key,
required this.id,
this.isGlobal = false,
this.isActive = false,
this.name,
this.selectedId,
this.editRequestId,
this.setActive,
this.onTap,
this.onDoubleTap,
this.onSecondaryTap,
this.onChangedNameEditor,
this.focusNode,
this.onTapOutsideNameEditor,
this.onMenuSelected,
});
final String id;
final bool isGlobal;
final bool isActive;
final String? name;
final String? selectedId;
final String? editRequestId;
final void Function(bool?)? setActive;
final void Function()? onTap;
final void Function()? onDoubleTap;
final void Function()? onSecondaryTap;
final Function(String)? onChangedNameEditor;
final FocusNode? focusNode;
final Function()? onTapOutsideNameEditor;
final Function(ItemMenuOption)? onMenuSelected;
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
final Color color =
isGlobal ? colorScheme.secondaryContainer : colorScheme.surface;
final Color colorVariant = colorScheme.surfaceVariant.withOpacity(0.5);
final Color surfaceTint = colorScheme.primary;
bool isSelected = selectedId == id;
bool inEditMode = editRequestId == id;
String nm = getEnvironmentTitle(name);
return Tooltip(
message: nm,
triggerMode: TooltipTriggerMode.manual,
waitDuration: const Duration(seconds: 1),
child: Card(
shape: const RoundedRectangleBorder(
borderRadius: kBorderRadius8,
),
elevation: isSelected ? 1 : 0,
surfaceTintColor: isSelected ? surfaceTint : null,
color: isSelected && !isGlobal
? colorScheme.brightness == Brightness.dark
? colorVariant
: color
: color,
margin: EdgeInsets.zero,
child: InkWell(
borderRadius: kBorderRadius8,
hoverColor: colorVariant,
focusColor: colorVariant.withOpacity(0.5),
onTap: inEditMode ? null : onTap,
onSecondaryTap: onSecondaryTap,
child: Padding(
padding: EdgeInsets.only(
left: 6,
right: isSelected ? 6 : 10,
top: 5,
bottom: 5,
),
child: SizedBox(
height: 20,
child: Row(
children: [
kHSpacer4,
Expanded(
child: inEditMode
? TextFormField(
key: ValueKey("$id-name"),
initialValue: name,
focusNode: focusNode,
style: Theme.of(context).textTheme.bodyMedium,
onTapOutside: (_) {
onTapOutsideNameEditor?.call();
},
onFieldSubmitted: (value) {
onTapOutsideNameEditor?.call();
},
onChanged: onChangedNameEditor,
decoration: const InputDecoration(
isCollapsed: true,
contentPadding: EdgeInsets.zero,
border: InputBorder.none,
),
)
: Text(
nm,
softWrap: false,
overflow: TextOverflow.fade,
),
),
Visibility(
visible: isSelected && !inEditMode && !isGlobal,
child: SizedBox(
width: 28,
child: ItemCardMenu(
onSelected: onMenuSelected,
),
),
),
],
),
),
),
),
),
);
}
}

41
lib/widgets/dialogs.dart Normal file
View File

@ -0,0 +1,41 @@
import 'package:flutter/material.dart';
showRenameDialog(
BuildContext context,
String dialogTitle,
String? name,
Function(String) onRename,
) {
showDialog(
context: context,
builder: (context) {
final controller = TextEditingController(text: name ?? "");
controller.selection =
TextSelection(baseOffset: 0, extentOffset: controller.text.length);
return AlertDialog(
title: Text(dialogTitle),
content: TextField(
autofocus: true,
controller: controller,
decoration: const InputDecoration(hintText: "Enter new name"),
),
actions: <Widget>[
OutlinedButton(
onPressed: () {
Navigator.pop(context);
},
child: const Text('CANCEL')),
FilledButton(
onPressed: () {
final val = controller.text.trim();
onRename(val);
Navigator.pop(context);
Future.delayed(const Duration(milliseconds: 100), () {
controller.dispose();
});
},
child: const Text('OK')),
],
);
});
}

View File

@ -0,0 +1,51 @@
import 'package:flutter/material.dart';
import 'package:apidash/consts.dart';
class DropdownButtonCodegenLanguage extends StatelessWidget {
const DropdownButtonCodegenLanguage({
super.key,
this.codegenLanguage,
this.onChanged,
});
final CodegenLanguage? codegenLanguage;
final void Function(CodegenLanguage?)? onChanged;
@override
Widget build(BuildContext context) {
final surfaceColor = Theme.of(context).colorScheme.surface;
return DropdownButton<CodegenLanguage>(
isExpanded: true,
focusColor: surfaceColor,
value: codegenLanguage,
icon: const Icon(
Icons.unfold_more_rounded,
size: 16,
),
elevation: 4,
style: kCodeStyle.copyWith(
color: Theme.of(context).colorScheme.primary,
),
underline: Container(
height: 0,
),
onChanged: onChanged,
borderRadius: kBorderRadius12,
items: CodegenLanguage.values
.map<DropdownMenuItem<CodegenLanguage>>((CodegenLanguage value) {
return DropdownMenuItem<CodegenLanguage>(
value: value,
child: Padding(
padding: kPs8,
child: Text(
value.label,
style: kTextStyleButton,
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
),
);
}).toList(),
);
}
}

View File

@ -0,0 +1,48 @@
import 'package:flutter/material.dart';
import 'package:apidash/consts.dart';
class DropdownButtonContentType extends StatelessWidget {
const DropdownButtonContentType({
super.key,
this.contentType,
this.onChanged,
});
final ContentType? contentType;
final void Function(ContentType?)? onChanged;
@override
Widget build(BuildContext context) {
final surfaceColor = Theme.of(context).colorScheme.surface;
return DropdownButton<ContentType>(
focusColor: surfaceColor,
value: contentType,
icon: const Icon(
Icons.unfold_more_rounded,
size: 16,
),
elevation: 4,
style: kCodeStyle.copyWith(
color: Theme.of(context).colorScheme.primary,
),
underline: Container(
height: 0,
),
onChanged: onChanged,
borderRadius: kBorderRadius12,
items: ContentType.values
.map<DropdownMenuItem<ContentType>>((ContentType value) {
return DropdownMenuItem<ContentType>(
value: value,
child: Padding(
padding: kPs8,
child: Text(
value.name,
style: kTextStyleButton,
),
),
);
}).toList(),
);
}
}

View File

@ -0,0 +1,47 @@
import 'package:flutter/material.dart';
import 'package:apidash/consts.dart';
class DropdownButtonFormData extends StatelessWidget {
const DropdownButtonFormData({
super.key,
this.formDataType,
this.onChanged,
});
final FormDataType? formDataType;
final void Function(FormDataType?)? onChanged;
@override
Widget build(BuildContext context) {
final surfaceColor = Theme.of(context).colorScheme.surface;
return DropdownButton<FormDataType>(
dropdownColor: surfaceColor,
focusColor: surfaceColor,
value: formDataType,
icon: const Icon(
Icons.unfold_more_rounded,
size: 16,
),
elevation: 4,
style: kCodeStyle.copyWith(
color: Theme.of(context).colorScheme.primary,
),
underline: const IgnorePointer(),
onChanged: onChanged,
borderRadius: kBorderRadius12,
items: FormDataType.values
.map<DropdownMenuItem<FormDataType>>((FormDataType value) {
return DropdownMenuItem<FormDataType>(
value: value,
child: Padding(
padding: kPs8,
child: Text(
value.name,
style: kTextStyleButton,
),
),
);
}).toList(),
);
}
}

View File

@ -0,0 +1,49 @@
import 'package:flutter/material.dart';
import 'package:apidash/utils/utils.dart';
import 'package:apidash/consts.dart';
import 'package:apidash/extensions/extensions.dart';
class DropdownButtonHttpMethod extends StatelessWidget {
const DropdownButtonHttpMethod({
super.key,
this.method,
this.onChanged,
});
final HTTPVerb? method;
final void Function(HTTPVerb? value)? onChanged;
@override
Widget build(BuildContext context) {
final surfaceColor = Theme.of(context).colorScheme.surface;
return DropdownButton<HTTPVerb>(
focusColor: surfaceColor,
value: method,
icon: const Icon(Icons.unfold_more_rounded),
elevation: 4,
underline: Container(
height: 0,
),
borderRadius: kBorderRadius12,
onChanged: onChanged,
items: HTTPVerb.values.map<DropdownMenuItem<HTTPVerb>>((HTTPVerb value) {
return DropdownMenuItem<HTTPVerb>(
value: value,
child: Padding(
padding: EdgeInsets.only(left: context.isMediumWindow ? 8 : 16),
child: Text(
value.name.toUpperCase(),
style: kCodeStyle.copyWith(
fontWeight: FontWeight.bold,
color: getHTTPMethodColor(
value,
brightness: Theme.of(context).brightness,
),
),
),
),
);
}).toList(),
);
}
}

View File

@ -1,189 +0,0 @@
import 'package:flutter/material.dart';
import 'package:apidash/utils/utils.dart';
import 'package:apidash/consts.dart';
import 'package:apidash/extensions/extensions.dart';
class DropdownButtonHttpMethod extends StatelessWidget {
const DropdownButtonHttpMethod({
super.key,
this.method,
this.onChanged,
});
final HTTPVerb? method;
final void Function(HTTPVerb? value)? onChanged;
@override
Widget build(BuildContext context) {
final surfaceColor = Theme.of(context).colorScheme.surface;
return DropdownButton<HTTPVerb>(
focusColor: surfaceColor,
value: method,
icon: const Icon(Icons.unfold_more_rounded),
elevation: 4,
underline: Container(
height: 0,
),
borderRadius: kBorderRadius12,
onChanged: onChanged,
items: HTTPVerb.values.map<DropdownMenuItem<HTTPVerb>>((HTTPVerb value) {
return DropdownMenuItem<HTTPVerb>(
value: value,
child: Padding(
padding: EdgeInsets.only(left: context.isMediumWindow ? 8 : 16),
child: Text(
value.name.toUpperCase(),
style: kCodeStyle.copyWith(
fontWeight: FontWeight.bold,
color: getHTTPMethodColor(
value,
brightness: Theme.of(context).brightness,
),
),
),
),
);
}).toList(),
);
}
}
class DropdownButtonContentType extends StatelessWidget {
const DropdownButtonContentType({
super.key,
this.contentType,
this.onChanged,
});
final ContentType? contentType;
final void Function(ContentType?)? onChanged;
@override
Widget build(BuildContext context) {
final surfaceColor = Theme.of(context).colorScheme.surface;
return DropdownButton<ContentType>(
focusColor: surfaceColor,
value: contentType,
icon: const Icon(
Icons.unfold_more_rounded,
size: 16,
),
elevation: 4,
style: kCodeStyle.copyWith(
color: Theme.of(context).colorScheme.primary,
),
underline: Container(
height: 0,
),
onChanged: onChanged,
borderRadius: kBorderRadius12,
items: ContentType.values
.map<DropdownMenuItem<ContentType>>((ContentType value) {
return DropdownMenuItem<ContentType>(
value: value,
child: Padding(
padding: kPs8,
child: Text(
value.name,
style: kTextStyleButton,
),
),
);
}).toList(),
);
}
}
class DropdownButtonFormData extends StatelessWidget {
const DropdownButtonFormData({
super.key,
this.formDataType,
this.onChanged,
});
final FormDataType? formDataType;
final void Function(FormDataType?)? onChanged;
@override
Widget build(BuildContext context) {
final surfaceColor = Theme.of(context).colorScheme.surface;
return DropdownButton<FormDataType>(
dropdownColor: surfaceColor,
focusColor: surfaceColor,
value: formDataType,
icon: const Icon(
Icons.unfold_more_rounded,
size: 16,
),
elevation: 4,
style: kCodeStyle.copyWith(
color: Theme.of(context).colorScheme.primary,
),
underline: const IgnorePointer(),
onChanged: onChanged,
borderRadius: kBorderRadius12,
items: FormDataType.values
.map<DropdownMenuItem<FormDataType>>((FormDataType value) {
return DropdownMenuItem<FormDataType>(
value: value,
child: Padding(
padding: kPs8,
child: Text(
value.name,
style: kTextStyleButton,
),
),
);
}).toList(),
);
}
}
class DropdownButtonCodegenLanguage extends StatelessWidget {
const DropdownButtonCodegenLanguage({
super.key,
this.codegenLanguage,
this.onChanged,
});
final CodegenLanguage? codegenLanguage;
final void Function(CodegenLanguage?)? onChanged;
@override
Widget build(BuildContext context) {
final surfaceColor = Theme.of(context).colorScheme.surface;
return DropdownButton<CodegenLanguage>(
isExpanded: true,
focusColor: surfaceColor,
value: codegenLanguage,
icon: const Icon(
Icons.unfold_more_rounded,
size: 16,
),
elevation: 4,
style: kCodeStyle.copyWith(
color: Theme.of(context).colorScheme.primary,
),
underline: Container(
height: 0,
),
onChanged: onChanged,
borderRadius: kBorderRadius12,
items: CodegenLanguage.values
.map<DropdownMenuItem<CodegenLanguage>>((CodegenLanguage value) {
return DropdownMenuItem<CodegenLanguage>(
value: value,
child: Padding(
padding: kPs8,
child: Text(
value.label,
style: kTextStyleButton,
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
),
);
}).toList(),
);
}
}

View File

@ -1,261 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_portal/flutter_portal.dart';
import 'package:mention_tag_text_field/mention_tag_text_field.dart';
import 'package:apidash/models/models.dart';
import 'package:apidash/consts.dart';
import '../screens/common/environment_autocomplete.dart';
class EnvironmentAutocompleteFieldBase extends StatefulHookWidget {
const EnvironmentAutocompleteFieldBase({
super.key,
this.initialValue,
this.onChanged,
this.onFieldSubmitted,
this.style,
this.decoration,
this.initialMentions,
this.suggestions,
this.mentionValue,
required this.onMentionValueChanged,
});
final String? initialValue;
final void Function(String)? onChanged;
final void Function(String)? onFieldSubmitted;
final TextStyle? style;
final InputDecoration? decoration;
final List<(String, Object?, Widget?)>? initialMentions;
final List<EnvironmentVariableSuggestion>? suggestions;
final String? mentionValue;
final void Function(String?) onMentionValueChanged;
@override
State<EnvironmentAutocompleteFieldBase> createState() =>
_EnvironmentAutocompleteFieldBaseState();
}
class _EnvironmentAutocompleteFieldBaseState
extends State<EnvironmentAutocompleteFieldBase> {
final MentionTagTextEditingController controller =
MentionTagTextEditingController();
final FocusNode focusNode = FocusNode();
@override
void initState() {
super.initState();
controller.text = widget.initialValue ?? "";
}
@override
Widget build(BuildContext context) {
final isSuggestionsVisible = useState(false);
return PortalTarget(
visible: isSuggestionsVisible.value && focusNode.hasFocus,
portalFollower: EnvironmentSuggestionsMenu(
mentionController: controller,
suggestions: widget.suggestions,
onSelect: (suggestion) {
controller.addMention(
label: '{${suggestion.variable.key}}}',
data: suggestion,
stylingWidget: EnvironmentVariableSpan(suggestion: suggestion));
widget.onChanged?.call(controller.text);
widget.onMentionValueChanged.call(null);
isSuggestionsVisible.value = false;
var mentionsCharacters =
controller.mentions.fold<int>(0, (previousValue, element) {
return previousValue + element.variable.key.length + 4 as int;
});
controller.selection = TextSelection.collapsed(
offset: controller.text.length +
controller.mentions.length -
mentionsCharacters,
);
},
),
anchor: const Aligned(
follower: Alignment.topLeft,
target: Alignment.bottomLeft,
backup: Aligned(
follower: Alignment.bottomLeft,
target: Alignment.topLeft,
),
),
child: EnvironmentMentionField(
focusNode: focusNode,
controller: controller,
initialMentions: widget.initialMentions ?? [],
mentionValue: widget.mentionValue,
onMentionValueChanged: widget.onMentionValueChanged,
isSuggestionsVisible: isSuggestionsVisible,
onChanged: widget.onChanged,
onFieldSubmitted: widget.onFieldSubmitted,
style: widget.style,
decoration: widget.decoration,
),
);
}
}
class EnvironmentMentionField extends StatelessWidget {
const EnvironmentMentionField({
super.key,
required this.focusNode,
required this.controller,
required this.initialMentions,
required this.mentionValue,
required this.onMentionValueChanged,
required this.isSuggestionsVisible,
this.onChanged,
this.onFieldSubmitted,
this.style,
this.decoration,
});
final FocusNode focusNode;
final MentionTagTextEditingController controller;
final List<(String, Object?, Widget?)> initialMentions;
final String? mentionValue;
final void Function(String?) onMentionValueChanged;
final ValueNotifier<bool> isSuggestionsVisible;
final void Function(String)? onChanged;
final void Function(String)? onFieldSubmitted;
final TextStyle? style;
final InputDecoration? decoration;
void onMention(String? value) {
onMentionValueChanged.call(value);
if (value != null) {
isSuggestionsVisible.value = true;
} else {
isSuggestionsVisible.value = false;
}
}
@override
Widget build(BuildContext context) {
return MentionTagTextFormField(
focusNode: focusNode,
onTap: () {
focusNode.requestFocus();
},
onTapOutside: (event) {
focusNode.unfocus();
isSuggestionsVisible.value = false;
},
controller: controller,
style: style,
initialMentions: initialMentions,
onMention: onMention,
onChanged: (value) {
onChanged?.call(controller.text);
},
onFieldSubmitted: (value) {
onFieldSubmitted?.call(controller.text);
isSuggestionsVisible.value = false;
},
decoration: decoration,
mentionTagDecoration: const MentionTagDecoration(
mentionStart: ['{'],
mentionBreak:
" ", // This is a workaround for the exception but adds a space after the mention
maxWords: 1,
allowDecrement: false,
allowEmbedding: true,
showMentionStartSymbol: false,
),
);
}
}
class EnvironmentSuggestionsMenu extends StatelessWidget {
const EnvironmentSuggestionsMenu({
super.key,
required this.mentionController,
required this.suggestions,
this.onSelect,
});
final MentionTagTextEditingController mentionController;
final List<EnvironmentVariableSuggestion>? suggestions;
final Function(EnvironmentVariableSuggestion)? onSelect;
@override
Widget build(BuildContext context) {
return suggestions == null || suggestions!.isEmpty
? const SizedBox.shrink()
: ClipRRect(
borderRadius: kBorderRadius8,
child: Material(
type: MaterialType.card,
elevation: 8,
child: ConstrainedBox(
constraints: const BoxConstraints(maxHeight: 200),
child: Ink(
width: 300,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surface,
borderRadius: kBorderRadius8,
border: Border.all(
color: Theme.of(context).colorScheme.outlineVariant,
),
),
child: ListView.separated(
shrinkWrap: true,
itemCount: suggestions?.length ?? 0,
separatorBuilder: (context, index) => const Divider(
height: 2,
),
itemBuilder: (context, index) {
final suggestion = suggestions![index];
return ListTile(
dense: true,
leading: EnvironmentIndicator(suggestion: suggestion),
title: Text(suggestion.variable.key),
subtitle: Text(suggestion.variable.value),
onTap: () {
onSelect?.call(suggestions![index]);
},
);
},
),
),
),
),
);
}
}
class EnvironmentIndicator extends StatelessWidget {
const EnvironmentIndicator({super.key, required this.suggestion});
final EnvironmentVariableSuggestion suggestion;
@override
Widget build(BuildContext context) {
final isUnknown = suggestion.isUnknown;
final isGlobal = suggestion.environmentId == kGlobalEnvironmentId;
return Container(
padding: kP4,
decoration: BoxDecoration(
color: isUnknown
? Theme.of(context).colorScheme.errorContainer
: isGlobal
? Theme.of(context).colorScheme.secondaryContainer
: Theme.of(context).colorScheme.primaryContainer,
borderRadius: kBorderRadius4,
),
child: Icon(
isUnknown
? Icons.block
: isGlobal
? Icons.public
: Icons.computer,
size: 16,
),
);
}
}

View File

@ -0,0 +1,104 @@
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_portal/flutter_portal.dart';
import 'package:mention_tag_text_field/mention_tag_text_field.dart';
import 'package:apidash/models/models.dart';
import 'package:apidash/widgets/field_mention.dart';
import '../screens/common_widgets/common_widgets.dart';
import 'suggestions_menu.dart';
class EnvironmentFieldBase extends StatefulHookWidget {
const EnvironmentFieldBase({
super.key,
this.initialValue,
this.onChanged,
this.onFieldSubmitted,
this.style,
this.decoration,
this.initialMentions,
this.suggestions,
this.mentionValue,
required this.onMentionValueChanged,
});
final String? initialValue;
final void Function(String)? onChanged;
final void Function(String)? onFieldSubmitted;
final TextStyle? style;
final InputDecoration? decoration;
final List<(String, Object?, Widget?)>? initialMentions;
final List<EnvironmentVariableSuggestion>? suggestions;
final String? mentionValue;
final void Function(String?) onMentionValueChanged;
@override
State<EnvironmentFieldBase> createState() =>
_EnvironmentAutocompleteFieldBaseState();
}
class _EnvironmentAutocompleteFieldBaseState
extends State<EnvironmentFieldBase> {
final MentionTagTextEditingController controller =
MentionTagTextEditingController();
final FocusNode focusNode = FocusNode();
@override
void initState() {
super.initState();
controller.text = widget.initialValue ?? "";
}
@override
Widget build(BuildContext context) {
final isSuggestionsVisible = useState(false);
return PortalTarget(
visible: isSuggestionsVisible.value && focusNode.hasFocus,
portalFollower: SuggestionsMenu(
mentionController: controller,
suggestions: widget.suggestions,
suggestionBuilder: (context, index) {
final suggestion = widget.suggestions![index];
return ListTile(
dense: true,
leading: EnvVarIndicator(suggestion: suggestion),
title: Text(suggestion.variable.key),
subtitle: Text(suggestion.variable.value),
onTap: () {
controller.addMention(
label: '{${suggestion.variable.key}}}',
data: suggestion,
stylingWidget: EnvVarSpan(suggestion: suggestion));
widget.onChanged?.call(controller.text);
widget.onMentionValueChanged.call(null);
isSuggestionsVisible.value = false;
},
);
},
),
anchor: const Aligned(
follower: Alignment.topLeft,
target: Alignment.bottomLeft,
backup: Aligned(
follower: Alignment.bottomLeft,
target: Alignment.topLeft,
),
),
child: MentionField(
focusNode: focusNode,
controller: controller,
initialMentions: widget.initialMentions ?? [],
mentionValue: widget.mentionValue,
onMentionValueChanged: widget.onMentionValueChanged,
isSuggestionsVisible: isSuggestionsVisible,
onChanged: widget.onChanged,
onFieldSubmitted: widget.onFieldSubmitted,
style: widget.style,
decoration: widget.decoration,
mentionStart: const ['{'],
maxWords: 1,
),
);
}
}

View File

@ -0,0 +1,53 @@
import 'package:flutter/material.dart';
import 'package:apidash/consts.dart';
class CellField extends StatelessWidget {
const CellField({
super.key,
required this.keyId,
this.initialValue,
this.hintText,
this.onChanged,
this.colorScheme,
});
final String keyId;
final String? initialValue;
final String? hintText;
final void Function(String)? onChanged;
final ColorScheme? colorScheme;
@override
Widget build(BuildContext context) {
var clrScheme = colorScheme ?? Theme.of(context).colorScheme;
return TextFormField(
key: Key(keyId),
initialValue: initialValue,
style: kCodeStyle.copyWith(
color: clrScheme.onSurface,
),
decoration: InputDecoration(
hintStyle: kCodeStyle.copyWith(
color: clrScheme.outline.withOpacity(
kHintOpacity,
),
),
hintText: hintText,
contentPadding: const EdgeInsets.only(bottom: 12),
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: clrScheme.primary.withOpacity(
kHintOpacity,
),
),
),
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: clrScheme.surfaceVariant,
),
),
),
onChanged: onChanged,
);
}
}

View File

@ -0,0 +1,67 @@
import 'package:flutter/material.dart';
import 'package:apidash/consts.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
class ObscurableCellField extends HookWidget {
const ObscurableCellField({
super.key,
required this.keyId,
this.initialValue,
this.hintText,
this.onChanged,
this.colorScheme,
});
final String keyId;
final String? initialValue;
final String? hintText;
final void Function(String)? onChanged;
final ColorScheme? colorScheme;
@override
Widget build(BuildContext context) {
final obscureText = useState(true);
var clrScheme = colorScheme ?? Theme.of(context).colorScheme;
return TextFormField(
key: Key(keyId),
initialValue: initialValue,
style: kCodeStyle.copyWith(
color: clrScheme.onSurface,
),
obscureText: obscureText.value,
decoration: InputDecoration(
hintStyle: kCodeStyle.copyWith(
color: clrScheme.outline.withOpacity(
kHintOpacity,
),
),
hintText: hintText,
suffixIcon: IconButton(
padding: kP4,
icon: Icon(
obscureText.value ? Icons.visibility : Icons.visibility_off,
color: clrScheme.onSurface,
size: 14,
),
onPressed: () {
obscureText.value = !obscureText.value;
},
),
contentPadding: const EdgeInsets.only(bottom: 12),
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: clrScheme.primary.withOpacity(
kHintOpacity,
),
),
),
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: clrScheme.surfaceVariant,
),
),
),
onChanged: onChanged,
);
}
}

View File

@ -1,7 +1,7 @@
import 'package:apidash/consts.dart';
import 'package:apidash/utils/header_utils.dart';
import 'package:flutter/material.dart';
import 'package:flutter_typeahead/flutter_typeahead.dart';
import 'package:apidash/utils/utils.dart';
import 'package:apidash/consts.dart';
class HeaderField extends StatefulWidget {
const HeaderField({

View File

@ -0,0 +1,20 @@
import 'package:flutter/material.dart';
import 'package:apidash/consts.dart';
import 'field_raw.dart';
class JsonSearchField extends StatelessWidget {
const JsonSearchField({super.key, this.onChanged, this.controller});
final void Function(String)? onChanged;
final TextEditingController? controller;
@override
Widget build(BuildContext context) {
return RawTextField(
controller: controller,
onChanged: onChanged,
style: kCodeStyle,
hintText: 'Search..',
);
}
}

View File

@ -0,0 +1,84 @@
import 'package:flutter/material.dart';
import 'package:mention_tag_text_field/mention_tag_text_field.dart';
class MentionField extends StatelessWidget {
const MentionField({
super.key,
required this.focusNode,
required this.controller,
required this.initialMentions,
required this.mentionValue,
required this.onMentionValueChanged,
required this.isSuggestionsVisible,
this.onChanged,
this.onFieldSubmitted,
this.style,
this.decoration,
required this.mentionStart,
this.mentionBreak = "",
this.maxWords,
this.allowDecrement = false,
this.allowEmbedding = true,
this.showMentionStartSymbol = false,
});
final FocusNode focusNode;
final MentionTagTextEditingController controller;
final List<(String, Object?, Widget?)> initialMentions;
final String? mentionValue;
final void Function(String?) onMentionValueChanged;
final ValueNotifier<bool> isSuggestionsVisible;
final void Function(String)? onChanged;
final void Function(String)? onFieldSubmitted;
final TextStyle? style;
final InputDecoration? decoration;
final List<String> mentionStart;
final String mentionBreak;
final int? maxWords;
final bool allowDecrement;
final bool allowEmbedding;
final bool showMentionStartSymbol;
void onMention(String? value) {
onMentionValueChanged.call(value);
if (value != null) {
isSuggestionsVisible.value = true;
} else {
isSuggestionsVisible.value = false;
}
}
@override
Widget build(BuildContext context) {
return MentionTagTextFormField(
focusNode: focusNode,
onTap: () {
focusNode.requestFocus();
},
onTapOutside: (event) {
focusNode.unfocus();
isSuggestionsVisible.value = false;
},
controller: controller,
style: style,
initialMentions: initialMentions,
onMention: onMention,
onChanged: (value) {
onChanged?.call(controller.text);
},
onFieldSubmitted: (value) {
onFieldSubmitted?.call(controller.text);
isSuggestionsVisible.value = false;
},
decoration: decoration,
mentionTagDecoration: MentionTagDecoration(
mentionStart: mentionStart,
mentionBreak: mentionBreak,
maxWords: maxWords,
allowDecrement: allowDecrement,
allowEmbedding: allowEmbedding,
showMentionStartSymbol: showMentionStartSymbol,
),
);
}
}

View File

@ -0,0 +1,33 @@
import 'package:flutter/material.dart';
class RawTextField extends StatelessWidget {
const RawTextField({
super.key,
this.onChanged,
this.controller,
this.hintText,
this.style,
});
final void Function(String)? onChanged;
final TextEditingController? controller;
final String? hintText;
final TextStyle? style;
@override
Widget build(BuildContext context) {
return TextField(
controller: controller,
onChanged: onChanged,
style: style,
decoration: InputDecoration(
isDense: true,
border: InputBorder.none,
hintText: hintText,
),
onTapOutside: (PointerDownEvent event) {
FocusManager.instance.primaryFocus?.unfocus();
},
);
}
}

View File

@ -0,0 +1,40 @@
import 'package:flutter/material.dart';
import 'package:apidash/consts.dart';
class URLField extends StatelessWidget {
const URLField({
super.key,
required this.selectedId,
this.initialValue,
this.onChanged,
this.onFieldSubmitted,
});
final String selectedId;
final String? initialValue;
final void Function(String)? onChanged;
final void Function(String)? onFieldSubmitted;
@override
Widget build(BuildContext context) {
return TextFormField(
key: Key("url-$selectedId"),
initialValue: initialValue,
style: kCodeStyle,
decoration: InputDecoration(
hintText: kHintTextUrlCard,
hintStyle: kCodeStyle.copyWith(
color: Theme.of(context).colorScheme.outline.withOpacity(
kHintOpacity,
),
),
border: InputBorder.none,
),
onChanged: onChanged,
onFieldSubmitted: onFieldSubmitted,
onTapOutside: (PointerDownEvent event) {
FocusManager.instance.primaryFocus?.unfocus();
},
);
}
}

View File

@ -7,7 +7,7 @@ import 'package:url_launcher/url_launcher_string.dart';
import '../consts.dart';
import '../utils/ui_utils.dart';
import "snackbars.dart";
import 'textfields.dart';
import 'field_json_search.dart';
class JsonPreviewerColor {
const JsonPreviewerColor._();

View File

@ -2,7 +2,8 @@ import 'package:flutter/material.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:markdown/markdown.dart' as md;
import 'package:url_launcher/url_launcher.dart';
import 'buttons.dart';
import 'button_discord.dart';
import 'button_repo.dart';
class CustomMarkdown extends StatelessWidget {
const CustomMarkdown({

View File

@ -0,0 +1,60 @@
import 'package:flutter/material.dart';
import 'package:multi_split_view/multi_split_view.dart';
import 'package:apidash/consts.dart';
class DashboardSplitView extends StatefulWidget {
const DashboardSplitView({
super.key,
required this.sidebarWidget,
required this.mainWidget,
});
final Widget sidebarWidget;
final Widget mainWidget;
@override
DashboardSplitViewState createState() => DashboardSplitViewState();
}
class DashboardSplitViewState extends State<DashboardSplitView> {
final MultiSplitViewController _controller = MultiSplitViewController(
areas: [
Area(size: 250, minimalSize: 200),
Area(minimalWeight: 0.7),
],
);
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return MultiSplitViewTheme(
data: MultiSplitViewThemeData(
dividerThickness: 3,
dividerPainter: DividerPainters.background(
color: Theme.of(context).colorScheme.surfaceVariant,
highlightedColor: Theme.of(context).colorScheme.outline.withOpacity(
kHintOpacity,
),
animationEnabled: false,
),
),
child: MultiSplitView(
controller: _controller,
children: [
widget.sidebarWidget,
widget.mainWidget,
],
),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
}

View File

@ -1,119 +1,9 @@
import 'package:flutter/material.dart';
import 'package:multi_split_view/multi_split_view.dart';
import 'package:apidash/extensions/extensions.dart';
import 'package:apidash/consts.dart';
class DashboardSplitView extends StatefulWidget {
const DashboardSplitView({
super.key,
required this.sidebarWidget,
required this.mainWidget,
});
final Widget sidebarWidget;
final Widget mainWidget;
@override
DashboardSplitViewState createState() => DashboardSplitViewState();
}
class DashboardSplitViewState extends State<DashboardSplitView> {
final MultiSplitViewController _controller = MultiSplitViewController(
areas: [
Area(size: 250, minimalSize: 200),
Area(minimalWeight: 0.7),
],
);
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return MultiSplitViewTheme(
data: MultiSplitViewThemeData(
dividerThickness: 3,
dividerPainter: DividerPainters.background(
color: Theme.of(context).colorScheme.surfaceVariant,
highlightedColor: Theme.of(context).colorScheme.outline.withOpacity(
kHintOpacity,
),
animationEnabled: false,
),
),
child: MultiSplitView(
controller: _controller,
children: [
widget.sidebarWidget,
widget.mainWidget,
],
),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
}
class EqualSplitView extends StatefulWidget {
const EqualSplitView({
super.key,
required this.leftWidget,
required this.rightWidget,
});
final Widget leftWidget;
final Widget rightWidget;
@override
State<EqualSplitView> createState() => _EqualSplitViewState();
}
class _EqualSplitViewState extends State<EqualSplitView> {
final MultiSplitViewController _controller = MultiSplitViewController(
areas: [
Area(minimalSize: kMinRequestEditorDetailsCardPaneSize),
Area(minimalSize: kMinRequestEditorDetailsCardPaneSize),
],
);
@override
Widget build(BuildContext context) {
return MultiSplitViewTheme(
data: MultiSplitViewThemeData(
dividerThickness: 3,
dividerPainter: DividerPainters.background(
color: Theme.of(context).colorScheme.surfaceVariant,
highlightedColor: Theme.of(context).colorScheme.outline.withOpacity(
kHintOpacity,
),
animationEnabled: false,
),
),
child: MultiSplitView(
controller: _controller,
children: [
widget.leftWidget,
widget.rightWidget,
],
),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
}
class TwoDrawerScaffold extends StatelessWidget {
const TwoDrawerScaffold({
class DrawerSplitView extends StatelessWidget {
const DrawerSplitView({
super.key,
required this.scaffoldKey,
required this.mainContent,

View File

@ -0,0 +1,55 @@
import 'package:flutter/material.dart';
import 'package:multi_split_view/multi_split_view.dart';
import 'package:apidash/consts.dart';
class EqualSplitView extends StatefulWidget {
const EqualSplitView({
super.key,
required this.leftWidget,
required this.rightWidget,
});
final Widget leftWidget;
final Widget rightWidget;
@override
State<EqualSplitView> createState() => _EqualSplitViewState();
}
class _EqualSplitViewState extends State<EqualSplitView> {
final MultiSplitViewController _controller = MultiSplitViewController(
areas: [
Area(minimalSize: kMinRequestEditorDetailsCardPaneSize),
Area(minimalSize: kMinRequestEditorDetailsCardPaneSize),
],
);
@override
Widget build(BuildContext context) {
return MultiSplitViewTheme(
data: MultiSplitViewThemeData(
dividerThickness: 3,
dividerPainter: DividerPainters.background(
color: Theme.of(context).colorScheme.surfaceVariant,
highlightedColor: Theme.of(context).colorScheme.outline.withOpacity(
kHintOpacity,
),
animationEnabled: false,
),
),
child: MultiSplitView(
controller: _controller,
children: [
widget.leftWidget,
widget.rightWidget,
],
),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
}

View File

@ -0,0 +1,53 @@
import 'package:flutter/material.dart';
import 'package:mention_tag_text_field/mention_tag_text_field.dart';
import 'package:apidash/consts.dart';
class SuggestionsMenu extends StatelessWidget {
const SuggestionsMenu({
super.key,
required this.mentionController,
required this.suggestions,
required this.suggestionBuilder,
this.menuWidth = kSuggestionsMenuWidth,
this.menuMaxHeight = kSuggestionsMenuMaxHeight,
});
final MentionTagTextEditingController mentionController;
final List? suggestions;
final double menuWidth;
final double menuMaxHeight;
final Widget? Function(BuildContext, int) suggestionBuilder;
@override
Widget build(BuildContext context) {
return suggestions == null || suggestions!.isEmpty
? const SizedBox.shrink()
: ClipRRect(
borderRadius: kBorderRadius8,
child: Material(
type: MaterialType.card,
elevation: 8,
child: ConstrainedBox(
constraints: BoxConstraints(maxHeight: menuMaxHeight),
child: Ink(
width: menuWidth,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surface,
borderRadius: kBorderRadius8,
border: Border.all(
color: Theme.of(context).colorScheme.outlineVariant,
),
),
child: ListView.separated(
shrinkWrap: true,
itemCount: suggestions?.length ?? 0,
separatorBuilder: (context, index) =>
const Divider(height: 2),
itemBuilder: suggestionBuilder,
),
),
),
),
);
}
}

View File

@ -1,205 +0,0 @@
import 'package:flutter/material.dart';
import 'package:apidash/consts.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
class URLField extends StatelessWidget {
const URLField({
super.key,
required this.selectedId,
this.initialValue,
this.onChanged,
this.onFieldSubmitted,
});
final String selectedId;
final String? initialValue;
final void Function(String)? onChanged;
final void Function(String)? onFieldSubmitted;
@override
Widget build(BuildContext context) {
return TextFormField(
key: Key("url-$selectedId"),
initialValue: initialValue,
style: kCodeStyle,
decoration: InputDecoration(
hintText: kHintTextUrlCard,
hintStyle: kCodeStyle.copyWith(
color: Theme.of(context).colorScheme.outline.withOpacity(
kHintOpacity,
),
),
border: InputBorder.none,
),
onChanged: onChanged,
onFieldSubmitted: onFieldSubmitted,
onTapOutside: (PointerDownEvent event) {
FocusManager.instance.primaryFocus?.unfocus();
},
);
}
}
class CellField extends StatelessWidget {
const CellField({
super.key,
required this.keyId,
this.initialValue,
this.hintText,
this.onChanged,
this.colorScheme,
});
final String keyId;
final String? initialValue;
final String? hintText;
final void Function(String)? onChanged;
final ColorScheme? colorScheme;
@override
Widget build(BuildContext context) {
var clrScheme = colorScheme ?? Theme.of(context).colorScheme;
return TextFormField(
key: Key(keyId),
initialValue: initialValue,
style: kCodeStyle.copyWith(
color: clrScheme.onSurface,
),
decoration: InputDecoration(
hintStyle: kCodeStyle.copyWith(
color: clrScheme.outline.withOpacity(
kHintOpacity,
),
),
hintText: hintText,
contentPadding: const EdgeInsets.only(bottom: 12),
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: clrScheme.primary.withOpacity(
kHintOpacity,
),
),
),
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: clrScheme.surfaceVariant,
),
),
),
onChanged: onChanged,
);
}
}
class ObscurableCellField extends HookWidget {
const ObscurableCellField({
super.key,
required this.keyId,
this.initialValue,
this.hintText,
this.onChanged,
this.colorScheme,
});
final String keyId;
final String? initialValue;
final String? hintText;
final void Function(String)? onChanged;
final ColorScheme? colorScheme;
@override
Widget build(BuildContext context) {
final obscureText = useState(true);
var clrScheme = colorScheme ?? Theme.of(context).colorScheme;
return TextFormField(
key: Key(keyId),
initialValue: initialValue,
style: kCodeStyle.copyWith(
color: clrScheme.onSurface,
),
obscureText: obscureText.value,
decoration: InputDecoration(
hintStyle: kCodeStyle.copyWith(
color: clrScheme.outline.withOpacity(
kHintOpacity,
),
),
hintText: hintText,
suffixIcon: IconButton(
padding: kP4,
icon: Icon(
obscureText.value ? Icons.visibility : Icons.visibility_off,
color: clrScheme.onSurface,
size: 14,
),
onPressed: () {
obscureText.value = !obscureText.value;
},
),
contentPadding: const EdgeInsets.only(bottom: 12),
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: clrScheme.primary.withOpacity(
kHintOpacity,
),
),
),
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: clrScheme.surfaceVariant,
),
),
),
onChanged: onChanged,
);
}
}
class JsonSearchField extends StatelessWidget {
const JsonSearchField({super.key, this.onChanged, this.controller});
final void Function(String)? onChanged;
final TextEditingController? controller;
@override
Widget build(BuildContext context) {
return RawTextField(
controller: controller,
onChanged: onChanged,
style: kCodeStyle,
hintText: 'Search..',
);
}
}
class RawTextField extends StatelessWidget {
const RawTextField({
super.key,
this.onChanged,
this.controller,
this.hintText,
this.style,
});
final void Function(String)? onChanged;
final TextEditingController? controller;
final String? hintText;
final TextStyle? style;
@override
Widget build(BuildContext context) {
return TextField(
controller: controller,
onChanged: onChanged,
style: style,
decoration: InputDecoration(
isDense: true,
border: InputBorder.none,
hintText: hintText,
),
onTapOutside: (PointerDownEvent event) {
FocusManager.instance.primaryFocus?.unfocus();
},
);
}
}

View File

@ -1,14 +1,31 @@
export 'buttons.dart';
export 'cards.dart';
export 'button_clear_response.dart';
export 'button_copy.dart';
export 'button_discord.dart';
export 'button_repo.dart';
export 'button_save_download.dart';
export 'button_send.dart';
export 'card_request_details.dart';
export 'card_sidebar_environment.dart';
export 'card_sidebar_request.dart';
export 'checkbox.dart';
export 'code_previewer.dart';
export 'codegen_previewer.dart';
export 'dropdowns.dart';
export 'dialogs.dart';
export 'dropdown_codegen.dart';
export 'dropdown_content_type.dart';
export 'dropdown_formdata.dart';
export 'dropdown_http_method.dart';
export 'editor_json.dart';
export 'editor.dart';
export 'environment_field.dart';
export 'environment_field_base.dart';
export 'error_message.dart';
export 'headerfield.dart';
export 'field_cell_obscurable.dart';
export 'field_cell.dart';
export 'field_header.dart';
export 'field_json_search.dart';
export 'field_mention.dart';
export 'field_raw.dart';
export 'field_url.dart';
export 'intro_message.dart';
export 'json_previewer.dart';
export 'markdown.dart';
@ -19,10 +36,12 @@ export 'previewer.dart';
export 'request_widgets.dart';
export 'response_widgets.dart';
export 'snackbars.dart';
export 'splitviews.dart';
export 'splitview_drawer.dart';
export 'splitview_dashboard.dart';
export 'splitview_equal.dart';
export 'suggestions_menu.dart';
export 'tables.dart';
export 'tabs.dart';
export 'textfields.dart';
export 'texts.dart';
export 'uint8_audio_player.dart';
export 'window_caption.dart';

View File

@ -821,10 +821,10 @@ packages:
dependency: "direct main"
description:
name: mention_tag_text_field
sha256: b0e831f5fc8ca942a835916fd084e6f5054226715affc740c613cd320004b8a7
sha256: "9768a0a6fe5771cb8eb98f94b26b4c595ca2487b0eb28b9d5624f8d71a2ac17a"
url: "https://pub.dev"
source: hosted
version: "0.0.4"
version: "0.0.5"
meta:
dependency: transitive
description:

View File

@ -63,7 +63,7 @@ dependencies:
hooks_riverpod: ^2.5.1
flutter_hooks: ^0.20.5
flutter_portal: ^1.1.4
mention_tag_text_field: ^0.0.4
mention_tag_text_field: ^0.0.5
dependency_overrides:
web: ^0.5.0

View File

@ -12,8 +12,7 @@ import 'package:apidash/screens/home_page/home_page.dart';
import 'package:apidash/screens/intro_page.dart';
import 'package:apidash/screens/settings_page.dart';
import 'package:apidash/services/hive_services.dart';
import 'package:apidash/widgets/response_widgets.dart';
import 'package:apidash/widgets/textfields.dart';
import 'package:apidash/widgets/widgets.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
@ -395,7 +394,7 @@ void main() {
await tester.pump();
// Tap on the "Send" button
Finder sendButton = find.byType(SendButton);
Finder sendButton = find.byType(SendRequestButton);
await tester.tap(sendButton);
await tester.pump();
@ -439,7 +438,7 @@ void main() {
await tester.pump();
// Tap on the "Send" button
Finder sendButton = find.byType(SendButton);
Finder sendButton = find.byType(SendRequestButton);
await tester.tap(sendButton);
await tester.pump();
@ -487,7 +486,7 @@ void main() {
await tester.pump();
// Tap on the "Send" button
Finder sendButton = find.byType(SendButton);
Finder sendButton = find.byType(SendRequestButton);
await tester.tap(sendButton);
await tester.pump();
@ -544,7 +543,7 @@ void main() {
await tester.pump();
// Tap on the "Send" button
Finder sendButton = find.byType(SendButton);
Finder sendButton = find.byType(SendRequestButton);
await tester.tap(sendButton);
await tester.pump();

View File

@ -1,8 +1,8 @@
import 'dart:typed_data';
import 'package:apidash/screens/common_widgets/sidebar_save_button.dart';
import 'package:apidash/widgets/widgets.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:apidash/widgets/buttons.dart';
import 'package:apidash/screens/common/sidebar_widgets.dart';
import 'package:apidash/consts.dart';
import '../test_consts.dart';
@ -36,7 +36,7 @@ void main() {
title: 'Send Request button',
theme: kThemeDataLight,
home: Scaffold(
body: SendRequestButton(
body: SendButton(
isWorking: false,
onTap: () {
changedValue = 'Send';
@ -63,7 +63,7 @@ void main() {
title: 'Send Request button',
theme: kThemeDataLight,
home: Scaffold(
body: SendRequestButton(
body: SendButton(
isWorking: true,
onTap: () {},
),

View File

@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:apidash/widgets/cards.dart';
import 'package:apidash/widgets/widgets.dart';
import 'package:apidash/consts.dart';
import '../test_consts.dart';

View File

@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:apidash/widgets/dropdowns.dart';
import 'package:apidash/widgets/widgets.dart';
import 'package:apidash/consts.dart';
import '../test_consts.dart';

View File

@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:apidash/widgets/headerfield.dart';
import 'package:apidash/widgets/field_header.dart';
void main() {
testWidgets('Testing Header Field', (tester) async {

View File

@ -1,7 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:apidash/widgets/splitviews.dart';
import 'package:multi_split_view/multi_split_view.dart';
import 'package:apidash/widgets/widgets.dart';
void main() {
testWidgets('Testing for Dashboard Splitview', (tester) async {

View File

@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:apidash/widgets/textfields.dart';
import 'package:apidash/widgets/widgets.dart';
import 'package:apidash/consts.dart';
import '../test_consts.dart';