mirror of
https://github.com/foss42/apidash.git
synced 2025-05-21 08:16:29 +08:00
fix: review changes
This commit is contained in:
@ -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,
|
||||
);
|
||||
|
@ -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));
|
@ -1,4 +1,4 @@
|
||||
export 'collection_providers.dart';
|
||||
export 'environment_provider.dart';
|
||||
export 'environment_providers.dart';
|
||||
export 'settings_providers.dart';
|
||||
export 'ui_providers.dart';
|
||||
|
@ -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));
|
||||
|
@ -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),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -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')),
|
||||
],
|
||||
);
|
||||
});
|
||||
}
|
@ -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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
11
lib/screens/common_widgets/common_widgets.dart
Normal file
11
lib/screens/common_widgets/common_widgets.dart
Normal 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';
|
62
lib/screens/common_widgets/editor_title.dart
Normal file
62
lib/screens/common_widgets/editor_title.dart
Normal 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(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
84
lib/screens/common_widgets/editor_title_actions.dart
Normal file
84
lib/screens/common_widgets/editor_title_actions.dart
Normal 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,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -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(
|
38
lib/screens/common_widgets/envfield_url.dart
Normal file
38
lib/screens/common_widgets/envfield_url.dart
Normal 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,
|
||||
);
|
||||
}
|
||||
}
|
37
lib/screens/common_widgets/environment_dropdown.dart
Normal file
37
lib/screens/common_widgets/environment_dropdown.dart
Normal 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;
|
||||
},
|
||||
));
|
||||
}
|
||||
}
|
49
lib/screens/common_widgets/environment_field.dart
Normal file
49
lib/screens/common_widgets/environment_field.dart
Normal 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,
|
||||
);
|
||||
}
|
||||
}
|
34
lib/screens/common_widgets/envvar_indicator.dart
Normal file
34
lib/screens/common_widgets/envvar_indicator.dart
Normal 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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
75
lib/screens/common_widgets/envvar_popover.dart
Normal file
75
lib/screens/common_widgets/envvar_popover.dart
Normal 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),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
84
lib/screens/common_widgets/envvar_span.dart
Normal file
84
lib/screens/common_widgets/envvar_span.dart
Normal 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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
45
lib/screens/common_widgets/sidebar_filter.dart
Normal file
45
lib/screens/common_widgets/sidebar_filter.dart
Normal 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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
43
lib/screens/common_widgets/sidebar_header.dart
Normal file
43
lib/screens/common_widgets/sidebar_header.dart
Normal 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(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
44
lib/screens/common_widgets/sidebar_save_button.dart
Normal file
44
lib/screens/common_widgets/sidebar_save_button.dart
Normal 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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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({
|
||||
|
@ -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({
|
||||
|
@ -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});
|
||||
|
@ -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});
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
@ -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,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
@ -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);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
@ -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)
|
||||
));
|
||||
}
|
||||
|
||||
|
@ -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';
|
||||
|
23
lib/widgets/button_clear_response.dart
Normal file
23
lib/widgets/button_clear_response.dart
Normal 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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
43
lib/widgets/button_copy.dart
Normal file
43
lib/widgets/button_copy.dart
Normal 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)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
30
lib/widgets/button_discord.dart
Normal file
30
lib/widgets/button_discord.dart
Normal 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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
43
lib/widgets/button_repo.dart
Normal file
43
lib/widgets/button_repo.dart
Normal 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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
67
lib/widgets/button_save_download.dart
Normal file
67
lib/widgets/button_save_download.dart
Normal 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)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
41
lib/widgets/button_send.dart
Normal file
41
lib/widgets/button_send.dart
Normal 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,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
22
lib/widgets/card_request_details.dart
Normal file
22
lib/widgets/card_request_details.dart
Normal 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,
|
||||
);
|
||||
}
|
||||
}
|
127
lib/widgets/card_sidebar_environment.dart
Normal file
127
lib/widgets/card_sidebar_environment.dart
Normal 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,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -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
41
lib/widgets/dialogs.dart
Normal 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')),
|
||||
],
|
||||
);
|
||||
});
|
||||
}
|
51
lib/widgets/dropdown_codegen.dart
Normal file
51
lib/widgets/dropdown_codegen.dart
Normal 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(),
|
||||
);
|
||||
}
|
||||
}
|
48
lib/widgets/dropdown_content_type.dart
Normal file
48
lib/widgets/dropdown_content_type.dart
Normal 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(),
|
||||
);
|
||||
}
|
||||
}
|
47
lib/widgets/dropdown_formdata.dart
Normal file
47
lib/widgets/dropdown_formdata.dart
Normal 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(),
|
||||
);
|
||||
}
|
||||
}
|
49
lib/widgets/dropdown_http_method.dart
Normal file
49
lib/widgets/dropdown_http_method.dart
Normal 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(),
|
||||
);
|
||||
}
|
||||
}
|
@ -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(),
|
||||
);
|
||||
}
|
||||
}
|
@ -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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
104
lib/widgets/environment_field_base.dart
Normal file
104
lib/widgets/environment_field_base.dart
Normal 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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
53
lib/widgets/field_cell.dart
Normal file
53
lib/widgets/field_cell.dart
Normal 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,
|
||||
);
|
||||
}
|
||||
}
|
67
lib/widgets/field_cell_obscurable.dart
Normal file
67
lib/widgets/field_cell_obscurable.dart
Normal 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,
|
||||
);
|
||||
}
|
||||
}
|
@ -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({
|
20
lib/widgets/field_json_search.dart
Normal file
20
lib/widgets/field_json_search.dart
Normal 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..',
|
||||
);
|
||||
}
|
||||
}
|
84
lib/widgets/field_mention.dart
Normal file
84
lib/widgets/field_mention.dart
Normal 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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
33
lib/widgets/field_raw.dart
Normal file
33
lib/widgets/field_raw.dart
Normal 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();
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
40
lib/widgets/field_url.dart
Normal file
40
lib/widgets/field_url.dart
Normal 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();
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
@ -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._();
|
||||
|
@ -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({
|
||||
|
60
lib/widgets/splitview_dashboard.dart
Normal file
60
lib/widgets/splitview_dashboard.dart
Normal 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();
|
||||
}
|
||||
}
|
@ -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,
|
55
lib/widgets/splitview_equal.dart
Normal file
55
lib/widgets/splitview_equal.dart
Normal 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();
|
||||
}
|
||||
}
|
53
lib/widgets/suggestions_menu.dart
Normal file
53
lib/widgets/suggestions_menu.dart
Normal 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,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -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();
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
@ -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';
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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: () {},
|
||||
),
|
||||
|
@ -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';
|
||||
|
||||
|
@ -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';
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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';
|
||||
|
||||
|
Reference in New Issue
Block a user