mirror of
https://github.com/foss42/apidash.git
synced 2025-05-21 16:26:37 +08:00
fix: review changes
This commit is contained in:
@ -157,6 +157,12 @@ const kTextButtonMinWidth = 44.0;
|
|||||||
|
|
||||||
const kRandMax = 100000;
|
const kRandMax = 100000;
|
||||||
|
|
||||||
|
const kSuggestionsMenuWidth = 300.0;
|
||||||
|
const kSuggestionsMenuMaxHeight = 200.0;
|
||||||
|
|
||||||
|
const kReqResTabWidth = 280.0;
|
||||||
|
const kReqResTabHeight = 32.0;
|
||||||
|
|
||||||
const kDataTableScrollbarTheme = ScrollbarThemeData(
|
const kDataTableScrollbarTheme = ScrollbarThemeData(
|
||||||
crossAxisMargin: -4,
|
crossAxisMargin: -4,
|
||||||
);
|
);
|
||||||
|
@ -15,6 +15,26 @@ final selectedEnvironmentModelProvider =
|
|||||||
return selectedId != null ? environments![selectedId] : null;
|
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,
|
final StateNotifierProvider<EnvironmentsStateNotifier,
|
||||||
Map<String, EnvironmentModel>?> environmentsStateNotifierProvider =
|
Map<String, EnvironmentModel>?> environmentsStateNotifierProvider =
|
||||||
StateNotifierProvider((ref) => EnvironmentsStateNotifier(ref, hiveHandler));
|
StateNotifierProvider((ref) => EnvironmentsStateNotifier(ref, hiveHandler));
|
@ -1,4 +1,4 @@
|
|||||||
export 'collection_providers.dart';
|
export 'collection_providers.dart';
|
||||||
export 'environment_provider.dart';
|
export 'environment_providers.dart';
|
||||||
export 'settings_providers.dart';
|
export 'settings_providers.dart';
|
||||||
export 'ui_providers.dart';
|
export 'ui_providers.dart';
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import '../models/models.dart';
|
import '../models/models.dart';
|
||||||
import 'environment_provider.dart';
|
|
||||||
import '../services/services.dart' show hiveHandler, HiveHandler;
|
import '../services/services.dart' show hiveHandler, HiveHandler;
|
||||||
import '../consts.dart';
|
import '../consts.dart';
|
||||||
|
|
||||||
@ -11,26 +10,6 @@ final codegenLanguageStateProvider = StateProvider<CodegenLanguage>((ref) =>
|
|||||||
final activeEnvironmentIdStateProvider = StateProvider<String?>((ref) =>
|
final activeEnvironmentIdStateProvider = StateProvider<String?>((ref) =>
|
||||||
ref.watch(settingsProvider.select((value) => value.activeEnvironmentId)));
|
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>
|
final StateNotifierProvider<ThemeStateNotifier, SettingsModel>
|
||||||
settingsProvider =
|
settingsProvider =
|
||||||
StateNotifierProvider((ref) => ThemeStateNotifier(hiveHandler));
|
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:flutter/material.dart';
|
||||||
import 'package:apidash/consts.dart';
|
import 'package:apidash/consts.dart';
|
||||||
import 'environment_autocomplete.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 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,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class EnvCellField extends StatelessWidget {
|
class EnvCellField extends StatelessWidget {
|
||||||
const EnvCellField({
|
const EnvCellField({
|
||||||
@ -56,7 +21,7 @@ class EnvCellField extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var clrScheme = colorScheme ?? Theme.of(context).colorScheme;
|
var clrScheme = colorScheme ?? Theme.of(context).colorScheme;
|
||||||
return EnvironmentAutocompleteField(
|
return EnvironmentField(
|
||||||
keyId: keyId,
|
keyId: keyId,
|
||||||
initialValue: initialValue,
|
initialValue: initialValue,
|
||||||
style: kCodeStyle.copyWith(
|
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:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:apidash/providers/providers.dart';
|
import 'package:apidash/providers/providers.dart';
|
||||||
import 'package:apidash/extensions/extensions.dart';
|
import 'package:apidash/extensions/extensions.dart';
|
||||||
|
import 'package:apidash/widgets/widgets.dart';
|
||||||
import 'package:apidash/consts.dart';
|
import 'package:apidash/consts.dart';
|
||||||
import '../common/main_editor_widgets.dart';
|
import '../common_widgets/common_widgets.dart';
|
||||||
import './editor_pane/variables_pane.dart';
|
import './editor_pane/variables_pane.dart';
|
||||||
|
|
||||||
class EnvironmentEditor extends ConsumerWidget {
|
class EnvironmentEditor extends ConsumerWidget {
|
||||||
@ -39,7 +40,7 @@ class EnvironmentEditor extends ConsumerWidget {
|
|||||||
const SizedBox(
|
const SizedBox(
|
||||||
width: 6,
|
width: 6,
|
||||||
),
|
),
|
||||||
TitleActionsArray(
|
EditorTitleActions(
|
||||||
onRenamePressed: () {
|
onRenamePressed: () {
|
||||||
showRenameDialog(context, "Rename Environment", name,
|
showRenameDialog(context, "Rename Environment", name,
|
||||||
(val) {
|
(val) {
|
||||||
|
@ -5,7 +5,7 @@ import 'package:apidash/extensions/extensions.dart';
|
|||||||
import 'package:apidash/providers/providers.dart';
|
import 'package:apidash/providers/providers.dart';
|
||||||
import 'package:apidash/utils/utils.dart';
|
import 'package:apidash/utils/utils.dart';
|
||||||
import 'package:apidash/consts.dart';
|
import 'package:apidash/consts.dart';
|
||||||
import '../common/main_editor_widgets.dart';
|
import '../common_widgets/common_widgets.dart';
|
||||||
import 'environments_pane.dart';
|
import 'environments_pane.dart';
|
||||||
import 'environment_editor.dart';
|
import 'environment_editor.dart';
|
||||||
|
|
||||||
@ -22,10 +22,10 @@ class EnvironmentPage extends ConsumerWidget {
|
|||||||
final name = getEnvironmentTitle(ref.watch(
|
final name = getEnvironmentTitle(ref.watch(
|
||||||
selectedEnvironmentModelProvider.select((value) => value?.name)));
|
selectedEnvironmentModelProvider.select((value) => value?.name)));
|
||||||
if (context.isMediumWindow) {
|
if (context.isMediumWindow) {
|
||||||
return TwoDrawerScaffold(
|
return DrawerSplitView(
|
||||||
scaffoldKey: scaffoldKey,
|
scaffoldKey: scaffoldKey,
|
||||||
mainContent: const EnvironmentEditor(),
|
mainContent: const EnvironmentEditor(),
|
||||||
title: ScaffoldTitle(
|
title: EditorTitle(
|
||||||
title: name,
|
title: name,
|
||||||
showMenu: id != kGlobalEnvironmentId,
|
showMenu: id != kGlobalEnvironmentId,
|
||||||
onSelected: (ItemMenuOption item) {
|
onSelected: (ItemMenuOption item) {
|
||||||
|
@ -6,7 +6,7 @@ import 'package:apidash/models/environment_model.dart';
|
|||||||
import 'package:apidash/providers/providers.dart';
|
import 'package:apidash/providers/providers.dart';
|
||||||
import 'package:apidash/widgets/widgets.dart';
|
import 'package:apidash/widgets/widgets.dart';
|
||||||
import 'package:apidash/consts.dart';
|
import 'package:apidash/consts.dart';
|
||||||
import '../common/sidebar_widgets.dart';
|
import '../common_widgets/common_widgets.dart';
|
||||||
|
|
||||||
class EnvironmentsPane extends ConsumerWidget {
|
class EnvironmentsPane extends ConsumerWidget {
|
||||||
const EnvironmentsPane({
|
const EnvironmentsPane({
|
||||||
|
@ -5,7 +5,7 @@ import 'package:apidash/extensions/extensions.dart';
|
|||||||
import 'package:apidash/widgets/widgets.dart';
|
import 'package:apidash/widgets/widgets.dart';
|
||||||
import 'package:apidash/models/models.dart';
|
import 'package:apidash/models/models.dart';
|
||||||
import 'package:apidash/consts.dart';
|
import 'package:apidash/consts.dart';
|
||||||
import 'package:apidash/screens/common/sidebar_widgets.dart';
|
import '../common_widgets/common_widgets.dart';
|
||||||
|
|
||||||
class CollectionPane extends ConsumerWidget {
|
class CollectionPane extends ConsumerWidget {
|
||||||
const CollectionPane({
|
const CollectionPane({
|
||||||
|
@ -6,7 +6,7 @@ import 'package:apidash/providers/providers.dart';
|
|||||||
import 'package:apidash/widgets/widgets.dart';
|
import 'package:apidash/widgets/widgets.dart';
|
||||||
import 'package:apidash/models/models.dart';
|
import 'package:apidash/models/models.dart';
|
||||||
import 'package:apidash/consts.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 {
|
class EditRequestHeaders extends ConsumerStatefulWidget {
|
||||||
const EditRequestHeaders({super.key});
|
const EditRequestHeaders({super.key});
|
||||||
|
@ -6,7 +6,7 @@ import 'package:apidash/providers/providers.dart';
|
|||||||
import 'package:apidash/widgets/widgets.dart';
|
import 'package:apidash/widgets/widgets.dart';
|
||||||
import 'package:apidash/models/models.dart';
|
import 'package:apidash/models/models.dart';
|
||||||
import 'package:apidash/consts.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 {
|
class EditRequestURLParams extends ConsumerStatefulWidget {
|
||||||
const EditRequestURLParams({super.key});
|
const EditRequestURLParams({super.key});
|
||||||
|
@ -2,10 +2,11 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:apidash/providers/providers.dart';
|
import 'package:apidash/providers/providers.dart';
|
||||||
import 'package:apidash/extensions/extensions.dart';
|
import 'package:apidash/extensions/extensions.dart';
|
||||||
|
import 'package:apidash/widgets/widgets.dart';
|
||||||
import 'package:apidash/consts.dart';
|
import 'package:apidash/consts.dart';
|
||||||
import 'details_card/details_card.dart';
|
import 'details_card/details_card.dart';
|
||||||
import 'details_card/request_pane/request_pane.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';
|
import 'url_card.dart';
|
||||||
|
|
||||||
class RequestEditor extends StatelessWidget {
|
class RequestEditor extends StatelessWidget {
|
||||||
@ -69,7 +70,7 @@ class RequestEditorTopBar extends ConsumerWidget {
|
|||||||
const SizedBox(
|
const SizedBox(
|
||||||
width: 6,
|
width: 6,
|
||||||
),
|
),
|
||||||
TitleActionsArray(
|
EditorTitleActions(
|
||||||
onRenamePressed: () {
|
onRenamePressed: () {
|
||||||
showRenameDialog(context, "Rename Request", name, (val) {
|
showRenameDialog(context, "Rename Request", name, (val) {
|
||||||
ref
|
ref
|
||||||
|
@ -4,7 +4,7 @@ import 'package:apidash/providers/providers.dart';
|
|||||||
import 'package:apidash/widgets/widgets.dart';
|
import 'package:apidash/widgets/widgets.dart';
|
||||||
import 'package:apidash/consts.dart';
|
import 'package:apidash/consts.dart';
|
||||||
import 'package:apidash/extensions/extensions.dart';
|
import 'package:apidash/extensions/extensions.dart';
|
||||||
import '../../common/environment_textfields.dart';
|
import '../../common_widgets/common_widgets.dart';
|
||||||
|
|
||||||
class EditorPaneRequestURLCard extends StatelessWidget {
|
class EditorPaneRequestURLCard extends StatelessWidget {
|
||||||
const EditorPaneRequestURLCard({super.key});
|
const EditorPaneRequestURLCard({super.key});
|
||||||
@ -45,7 +45,7 @@ class EditorPaneRequestURLCard extends StatelessWidget {
|
|||||||
kHSpacer20,
|
kHSpacer20,
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: 36,
|
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;
|
final Function()? onTap;
|
||||||
const SendButton({
|
const SendRequestButton({
|
||||||
super.key,
|
super.key,
|
||||||
this.onTap,
|
this.onTap,
|
||||||
});
|
});
|
||||||
@ -117,7 +117,7 @@ class SendButton extends ConsumerWidget {
|
|||||||
final isWorking = ref.watch(
|
final isWorking = ref.watch(
|
||||||
selectedRequestModelProvider.select((value) => value?.isWorking));
|
selectedRequestModelProvider.select((value) => value?.isWorking));
|
||||||
|
|
||||||
return SendRequestButton(
|
return SendButton(
|
||||||
isWorking: isWorking ?? false,
|
isWorking: isWorking ?? false,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
onTap?.call();
|
onTap?.call();
|
||||||
|
@ -42,16 +42,16 @@ class RequestResponseTabbar extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Center(
|
return Center(
|
||||||
child: Container(
|
child: Container(
|
||||||
width: 280,
|
width: kReqResTabWidth,
|
||||||
height: 32,
|
height: kReqResTabHeight,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.circular(50),
|
borderRadius: kBorderRadius20,
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: Theme.of(context).colorScheme.outlineVariant,
|
color: Theme.of(context).colorScheme.outlineVariant,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: ClipRRect(
|
child: ClipRRect(
|
||||||
borderRadius: BorderRadius.circular(50),
|
borderRadius: kBorderRadius20,
|
||||||
child: TabBar(
|
child: TabBar(
|
||||||
dividerColor: Colors.transparent,
|
dividerColor: Colors.transparent,
|
||||||
indicatorWeight: 0.0,
|
indicatorWeight: 0.0,
|
||||||
@ -63,18 +63,18 @@ class RequestResponseTabbar extends StatelessWidget {
|
|||||||
color: Theme.of(context).colorScheme.onPrimary,
|
color: Theme.of(context).colorScheme.onPrimary,
|
||||||
),
|
),
|
||||||
unselectedLabelStyle: kTextStyleTab,
|
unselectedLabelStyle: kTextStyleTab,
|
||||||
splashBorderRadius: BorderRadius.circular(50),
|
splashBorderRadius: kBorderRadius20,
|
||||||
indicator: BoxDecoration(
|
indicator: BoxDecoration(
|
||||||
borderRadius: BorderRadius.circular(50),
|
borderRadius: kBorderRadius20,
|
||||||
color: Theme.of(context).colorScheme.primary,
|
color: Theme.of(context).colorScheme.primary,
|
||||||
),
|
),
|
||||||
controller: controller,
|
controller: controller,
|
||||||
tabs: const <Widget>[
|
tabs: const <Widget>[
|
||||||
Tab(
|
Tab(
|
||||||
text: "Request",
|
text: kLabelRequest,
|
||||||
),
|
),
|
||||||
Tab(
|
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/url_card.dart';
|
||||||
import '../../home_page/editor_pane/details_card/code_pane.dart';
|
import '../../home_page/editor_pane/details_card/code_pane.dart';
|
||||||
import '../../home_page/editor_pane/editor_default.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 '../widgets/page_base.dart';
|
||||||
import 'request_response_tabs.dart';
|
import 'request_response_tabs.dart';
|
||||||
|
|
||||||
@ -35,9 +35,9 @@ class _RequestResponsePageState extends ConsumerState<RequestResponsePage>
|
|||||||
ref.watch(selectedRequestModelProvider.select((value) => value?.name)));
|
ref.watch(selectedRequestModelProvider.select((value) => value?.name)));
|
||||||
final TabController requestResponseTabController =
|
final TabController requestResponseTabController =
|
||||||
useTabController(initialLength: 2, vsync: this);
|
useTabController(initialLength: 2, vsync: this);
|
||||||
return TwoDrawerScaffold(
|
return DrawerSplitView(
|
||||||
scaffoldKey: widget.scaffoldKey,
|
scaffoldKey: widget.scaffoldKey,
|
||||||
title: ScaffoldTitle(
|
title: EditorTitle(
|
||||||
title: name,
|
title: name,
|
||||||
onSelected: (ItemMenuOption item) {
|
onSelected: (ItemMenuOption item) {
|
||||||
if (item == ItemMenuOption.edit) {
|
if (item == ItemMenuOption.edit) {
|
||||||
@ -125,12 +125,15 @@ class RequestResponsePageBottombar extends ConsumerWidget {
|
|||||||
},
|
},
|
||||||
icon: const Icon(Icons.code_rounded),
|
icon: const Icon(Icons.code_rounded),
|
||||||
),
|
),
|
||||||
SendButton(
|
SizedBox(
|
||||||
onTap: () {
|
height: 36,
|
||||||
if (requestResponseTabController.index != 1) {
|
child: SendRequestButton(
|
||||||
requestResponseTabController.animateTo(1);
|
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:collection/collection.dart';
|
||||||
import 'package:flutter/material.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) {
|
String getEnvironmentTitle(String? name) {
|
||||||
if (name == null || name.trim() == "") {
|
if (name == null || name.trim() == "") {
|
||||||
@ -126,7 +126,7 @@ List<(String, Object?, Widget?)> getMentions(
|
|||||||
mentions.add((
|
mentions.add((
|
||||||
'{{${variable?.key ?? variableName}}}',
|
'{{${variable?.key ?? variableName}}}',
|
||||||
suggestion,
|
suggestion,
|
||||||
EnvironmentVariableSpan(suggestion: suggestion)
|
EnvVarSpan(suggestion: suggestion)
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
export 'ui_utils.dart';
|
export 'ui_utils.dart';
|
||||||
export 'convert_utils.dart';
|
export 'convert_utils.dart';
|
||||||
|
export 'header_utils.dart';
|
||||||
export 'http_utils.dart';
|
export 'http_utils.dart';
|
||||||
export 'file_utils.dart';
|
export 'file_utils.dart';
|
||||||
export 'window_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/material.dart';
|
||||||
import 'package:flutter_typeahead/flutter_typeahead.dart';
|
import 'package:flutter_typeahead/flutter_typeahead.dart';
|
||||||
|
import 'package:apidash/utils/utils.dart';
|
||||||
|
import 'package:apidash/consts.dart';
|
||||||
|
|
||||||
class HeaderField extends StatefulWidget {
|
class HeaderField extends StatefulWidget {
|
||||||
const HeaderField({
|
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 '../consts.dart';
|
||||||
import '../utils/ui_utils.dart';
|
import '../utils/ui_utils.dart';
|
||||||
import "snackbars.dart";
|
import "snackbars.dart";
|
||||||
import 'textfields.dart';
|
import 'field_json_search.dart';
|
||||||
|
|
||||||
class JsonPreviewerColor {
|
class JsonPreviewerColor {
|
||||||
const JsonPreviewerColor._();
|
const JsonPreviewerColor._();
|
||||||
|
@ -2,7 +2,8 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_markdown/flutter_markdown.dart';
|
import 'package:flutter_markdown/flutter_markdown.dart';
|
||||||
import 'package:markdown/markdown.dart' as md;
|
import 'package:markdown/markdown.dart' as md;
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
import 'buttons.dart';
|
import 'button_discord.dart';
|
||||||
|
import 'button_repo.dart';
|
||||||
|
|
||||||
class CustomMarkdown extends StatelessWidget {
|
class CustomMarkdown extends StatelessWidget {
|
||||||
const CustomMarkdown({
|
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:flutter/material.dart';
|
||||||
import 'package:multi_split_view/multi_split_view.dart';
|
|
||||||
import 'package:apidash/extensions/extensions.dart';
|
import 'package:apidash/extensions/extensions.dart';
|
||||||
import 'package:apidash/consts.dart';
|
import 'package:apidash/consts.dart';
|
||||||
|
|
||||||
class DashboardSplitView extends StatefulWidget {
|
class DrawerSplitView extends StatelessWidget {
|
||||||
const DashboardSplitView({
|
const DrawerSplitView({
|
||||||
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({
|
|
||||||
super.key,
|
super.key,
|
||||||
required this.scaffoldKey,
|
required this.scaffoldKey,
|
||||||
required this.mainContent,
|
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 'button_clear_response.dart';
|
||||||
export 'cards.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 'checkbox.dart';
|
||||||
export 'code_previewer.dart';
|
export 'code_previewer.dart';
|
||||||
export 'codegen_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_json.dart';
|
||||||
export 'editor.dart';
|
export 'editor.dart';
|
||||||
export 'environment_field.dart';
|
export 'environment_field_base.dart';
|
||||||
export 'error_message.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 'intro_message.dart';
|
||||||
export 'json_previewer.dart';
|
export 'json_previewer.dart';
|
||||||
export 'markdown.dart';
|
export 'markdown.dart';
|
||||||
@ -19,10 +36,12 @@ export 'previewer.dart';
|
|||||||
export 'request_widgets.dart';
|
export 'request_widgets.dart';
|
||||||
export 'response_widgets.dart';
|
export 'response_widgets.dart';
|
||||||
export 'snackbars.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 'tables.dart';
|
||||||
export 'tabs.dart';
|
export 'tabs.dart';
|
||||||
export 'textfields.dart';
|
|
||||||
export 'texts.dart';
|
export 'texts.dart';
|
||||||
export 'uint8_audio_player.dart';
|
export 'uint8_audio_player.dart';
|
||||||
export 'window_caption.dart';
|
export 'window_caption.dart';
|
||||||
|
@ -821,10 +821,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: mention_tag_text_field
|
name: mention_tag_text_field
|
||||||
sha256: b0e831f5fc8ca942a835916fd084e6f5054226715affc740c613cd320004b8a7
|
sha256: "9768a0a6fe5771cb8eb98f94b26b4c595ca2487b0eb28b9d5624f8d71a2ac17a"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.0.4"
|
version: "0.0.5"
|
||||||
meta:
|
meta:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -63,7 +63,7 @@ dependencies:
|
|||||||
hooks_riverpod: ^2.5.1
|
hooks_riverpod: ^2.5.1
|
||||||
flutter_hooks: ^0.20.5
|
flutter_hooks: ^0.20.5
|
||||||
flutter_portal: ^1.1.4
|
flutter_portal: ^1.1.4
|
||||||
mention_tag_text_field: ^0.0.4
|
mention_tag_text_field: ^0.0.5
|
||||||
|
|
||||||
dependency_overrides:
|
dependency_overrides:
|
||||||
web: ^0.5.0
|
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/intro_page.dart';
|
||||||
import 'package:apidash/screens/settings_page.dart';
|
import 'package:apidash/screens/settings_page.dart';
|
||||||
import 'package:apidash/services/hive_services.dart';
|
import 'package:apidash/services/hive_services.dart';
|
||||||
import 'package:apidash/widgets/response_widgets.dart';
|
import 'package:apidash/widgets/widgets.dart';
|
||||||
import 'package:apidash/widgets/textfields.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
@ -395,7 +394,7 @@ void main() {
|
|||||||
await tester.pump();
|
await tester.pump();
|
||||||
|
|
||||||
// Tap on the "Send" button
|
// Tap on the "Send" button
|
||||||
Finder sendButton = find.byType(SendButton);
|
Finder sendButton = find.byType(SendRequestButton);
|
||||||
await tester.tap(sendButton);
|
await tester.tap(sendButton);
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
|
|
||||||
@ -439,7 +438,7 @@ void main() {
|
|||||||
await tester.pump();
|
await tester.pump();
|
||||||
|
|
||||||
// Tap on the "Send" button
|
// Tap on the "Send" button
|
||||||
Finder sendButton = find.byType(SendButton);
|
Finder sendButton = find.byType(SendRequestButton);
|
||||||
await tester.tap(sendButton);
|
await tester.tap(sendButton);
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
|
|
||||||
@ -487,7 +486,7 @@ void main() {
|
|||||||
await tester.pump();
|
await tester.pump();
|
||||||
|
|
||||||
// Tap on the "Send" button
|
// Tap on the "Send" button
|
||||||
Finder sendButton = find.byType(SendButton);
|
Finder sendButton = find.byType(SendRequestButton);
|
||||||
await tester.tap(sendButton);
|
await tester.tap(sendButton);
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
|
|
||||||
@ -544,7 +543,7 @@ void main() {
|
|||||||
await tester.pump();
|
await tester.pump();
|
||||||
|
|
||||||
// Tap on the "Send" button
|
// Tap on the "Send" button
|
||||||
Finder sendButton = find.byType(SendButton);
|
Finder sendButton = find.byType(SendRequestButton);
|
||||||
await tester.tap(sendButton);
|
await tester.tap(sendButton);
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
|
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import 'dart:typed_data';
|
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/material.dart';
|
||||||
import 'package:flutter_test/flutter_test.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 'package:apidash/consts.dart';
|
||||||
import '../test_consts.dart';
|
import '../test_consts.dart';
|
||||||
|
|
||||||
@ -36,7 +36,7 @@ void main() {
|
|||||||
title: 'Send Request button',
|
title: 'Send Request button',
|
||||||
theme: kThemeDataLight,
|
theme: kThemeDataLight,
|
||||||
home: Scaffold(
|
home: Scaffold(
|
||||||
body: SendRequestButton(
|
body: SendButton(
|
||||||
isWorking: false,
|
isWorking: false,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
changedValue = 'Send';
|
changedValue = 'Send';
|
||||||
@ -63,7 +63,7 @@ void main() {
|
|||||||
title: 'Send Request button',
|
title: 'Send Request button',
|
||||||
theme: kThemeDataLight,
|
theme: kThemeDataLight,
|
||||||
home: Scaffold(
|
home: Scaffold(
|
||||||
body: SendRequestButton(
|
body: SendButton(
|
||||||
isWorking: true,
|
isWorking: true,
|
||||||
onTap: () {},
|
onTap: () {},
|
||||||
),
|
),
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_test/flutter_test.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 'package:apidash/consts.dart';
|
||||||
import '../test_consts.dart';
|
import '../test_consts.dart';
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_test/flutter_test.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 'package:apidash/consts.dart';
|
||||||
import '../test_consts.dart';
|
import '../test_consts.dart';
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:apidash/widgets/headerfield.dart';
|
import 'package:apidash/widgets/field_header.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
testWidgets('Testing Header Field', (tester) async {
|
testWidgets('Testing Header Field', (tester) async {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_test/flutter_test.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:multi_split_view/multi_split_view.dart';
|
||||||
|
import 'package:apidash/widgets/widgets.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
testWidgets('Testing for Dashboard Splitview', (tester) async {
|
testWidgets('Testing for Dashboard Splitview', (tester) async {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_test/flutter_test.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 'package:apidash/consts.dart';
|
||||||
import '../test_consts.dart';
|
import '../test_consts.dart';
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user