mirror of
https://github.com/foss42/apidash.git
synced 2025-05-23 01:06:46 +08:00
fix: review changes
This commit is contained in:
23
lib/widgets/button_clear_response.dart
Normal file
23
lib/widgets/button_clear_response.dart
Normal file
@ -0,0 +1,23 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:apidash/consts.dart';
|
||||
|
||||
class ClearResponseButton extends StatelessWidget {
|
||||
const ClearResponseButton({
|
||||
super.key,
|
||||
this.onPressed,
|
||||
});
|
||||
|
||||
final VoidCallback? onPressed;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return IconButton(
|
||||
tooltip: kTooltipClearResponse,
|
||||
onPressed: onPressed,
|
||||
icon: const Icon(
|
||||
Icons.delete,
|
||||
size: 16,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
43
lib/widgets/button_copy.dart
Normal file
43
lib/widgets/button_copy.dart
Normal file
@ -0,0 +1,43 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:apidash/consts.dart';
|
||||
import "snackbars.dart";
|
||||
|
||||
class CopyButton extends StatelessWidget {
|
||||
const CopyButton({
|
||||
super.key,
|
||||
required this.toCopy,
|
||||
this.showLabel = true,
|
||||
});
|
||||
|
||||
final String toCopy;
|
||||
final bool showLabel;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var sm = ScaffoldMessenger.of(context);
|
||||
return Tooltip(
|
||||
message: showLabel ? '' : kLabelCopy,
|
||||
child: SizedBox(
|
||||
width: showLabel ? null : kTextButtonMinWidth,
|
||||
child: TextButton(
|
||||
onPressed: () async {
|
||||
await Clipboard.setData(ClipboardData(text: toCopy));
|
||||
sm.hideCurrentSnackBar();
|
||||
sm.showSnackBar(getSnackBar("Copied"));
|
||||
},
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(
|
||||
Icons.content_copy,
|
||||
size: 20,
|
||||
),
|
||||
if (showLabel) const Text(kLabelCopy)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
30
lib/widgets/button_discord.dart
Normal file
30
lib/widgets/button_discord.dart
Normal file
@ -0,0 +1,30 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:apidash/consts.dart';
|
||||
|
||||
class DiscordButton extends StatelessWidget {
|
||||
const DiscordButton({
|
||||
super.key,
|
||||
this.text,
|
||||
});
|
||||
|
||||
final String? text;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var label = text ?? 'Discord Server';
|
||||
return FilledButton.icon(
|
||||
onPressed: () {
|
||||
launchUrl(Uri.parse(kDiscordUrl));
|
||||
},
|
||||
icon: const Icon(
|
||||
Icons.discord,
|
||||
size: 20.0,
|
||||
),
|
||||
label: Text(
|
||||
label,
|
||||
style: kTextStyleButton,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
43
lib/widgets/button_repo.dart
Normal file
43
lib/widgets/button_repo.dart
Normal file
@ -0,0 +1,43 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:apidash/consts.dart';
|
||||
|
||||
class RepoButton extends StatelessWidget {
|
||||
const RepoButton({
|
||||
super.key,
|
||||
this.text,
|
||||
this.icon,
|
||||
});
|
||||
|
||||
final String? text;
|
||||
final IconData? icon;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var label = text ?? "GitHub";
|
||||
if (icon == null) {
|
||||
return FilledButton(
|
||||
onPressed: () {
|
||||
launchUrl(Uri.parse(kGitUrl));
|
||||
},
|
||||
child: Text(
|
||||
label,
|
||||
style: kTextStyleButton,
|
||||
),
|
||||
);
|
||||
}
|
||||
return FilledButton.icon(
|
||||
onPressed: () {
|
||||
launchUrl(Uri.parse(kGitUrl));
|
||||
},
|
||||
icon: Icon(
|
||||
icon,
|
||||
size: 20.0,
|
||||
),
|
||||
label: Text(
|
||||
label,
|
||||
style: kTextStyleButton,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
67
lib/widgets/button_save_download.dart
Normal file
67
lib/widgets/button_save_download.dart
Normal file
@ -0,0 +1,67 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:apidash/utils/utils.dart';
|
||||
import 'package:apidash/consts.dart';
|
||||
import "snackbars.dart";
|
||||
|
||||
class SaveInDownloadsButton extends StatelessWidget {
|
||||
const SaveInDownloadsButton({
|
||||
super.key,
|
||||
this.content,
|
||||
this.mimeType,
|
||||
this.ext,
|
||||
this.name,
|
||||
this.showLabel = true,
|
||||
});
|
||||
|
||||
final Uint8List? content;
|
||||
final String? mimeType;
|
||||
final String? ext;
|
||||
final String? name;
|
||||
final bool showLabel;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var sm = ScaffoldMessenger.of(context);
|
||||
return Tooltip(
|
||||
message: showLabel ? '' : kLabelDownload,
|
||||
child: SizedBox(
|
||||
width: showLabel ? null : kTextButtonMinWidth,
|
||||
child: TextButton(
|
||||
onPressed: (content != null)
|
||||
? () async {
|
||||
var message = "";
|
||||
var path = await getFileDownloadpath(
|
||||
name,
|
||||
ext ?? getFileExtension(mimeType),
|
||||
);
|
||||
if (path != null) {
|
||||
try {
|
||||
await saveFile(path, content!);
|
||||
var sp = getShortPath(path);
|
||||
message = 'Saved to $sp';
|
||||
} catch (e) {
|
||||
message = "An error occurred while saving file.";
|
||||
}
|
||||
} else {
|
||||
message = "Unable to determine the download path.";
|
||||
}
|
||||
sm.hideCurrentSnackBar();
|
||||
sm.showSnackBar(getSnackBar(message, small: false));
|
||||
}
|
||||
: null,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(
|
||||
Icons.download,
|
||||
size: 20,
|
||||
),
|
||||
if (showLabel) const Text(kLabelDownload)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
41
lib/widgets/button_send.dart
Normal file
41
lib/widgets/button_send.dart
Normal file
@ -0,0 +1,41 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:apidash/consts.dart';
|
||||
|
||||
class SendButton extends StatelessWidget {
|
||||
const SendButton({
|
||||
super.key,
|
||||
required this.isWorking,
|
||||
required this.onTap,
|
||||
});
|
||||
|
||||
final bool isWorking;
|
||||
final void Function() onTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FilledButton(
|
||||
onPressed: isWorking ? null : onTap,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: isWorking
|
||||
? const [
|
||||
Text(
|
||||
kLabelSending,
|
||||
style: kTextStyleButton,
|
||||
),
|
||||
]
|
||||
: const [
|
||||
Text(
|
||||
kLabelSend,
|
||||
style: kTextStyleButton,
|
||||
),
|
||||
kHSpacer10,
|
||||
Icon(
|
||||
size: 16,
|
||||
Icons.send,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,234 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:apidash/utils/utils.dart';
|
||||
import 'package:apidash/consts.dart';
|
||||
import "snackbars.dart";
|
||||
|
||||
class CopyButton extends StatelessWidget {
|
||||
const CopyButton({
|
||||
super.key,
|
||||
required this.toCopy,
|
||||
this.showLabel = true,
|
||||
});
|
||||
|
||||
final String toCopy;
|
||||
final bool showLabel;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var sm = ScaffoldMessenger.of(context);
|
||||
return Tooltip(
|
||||
message: showLabel ? '' : kLabelCopy,
|
||||
child: SizedBox(
|
||||
width: showLabel ? null : kTextButtonMinWidth,
|
||||
child: TextButton(
|
||||
onPressed: () async {
|
||||
await Clipboard.setData(ClipboardData(text: toCopy));
|
||||
sm.hideCurrentSnackBar();
|
||||
sm.showSnackBar(getSnackBar("Copied"));
|
||||
},
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(
|
||||
Icons.content_copy,
|
||||
size: 20,
|
||||
),
|
||||
if (showLabel) const Text(kLabelCopy)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SendRequestButton extends StatelessWidget {
|
||||
const SendRequestButton({
|
||||
super.key,
|
||||
required this.isWorking,
|
||||
required this.onTap,
|
||||
});
|
||||
|
||||
final bool isWorking;
|
||||
final void Function() onTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FilledButton(
|
||||
onPressed: isWorking ? null : onTap,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: isWorking
|
||||
? const [
|
||||
Text(
|
||||
kLabelSending,
|
||||
style: kTextStyleButton,
|
||||
),
|
||||
]
|
||||
: const [
|
||||
Text(
|
||||
kLabelSend,
|
||||
style: kTextStyleButton,
|
||||
),
|
||||
kHSpacer10,
|
||||
Icon(
|
||||
size: 16,
|
||||
Icons.send,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SaveInDownloadsButton extends StatelessWidget {
|
||||
const SaveInDownloadsButton({
|
||||
super.key,
|
||||
this.content,
|
||||
this.mimeType,
|
||||
this.ext,
|
||||
this.name,
|
||||
this.showLabel = true,
|
||||
});
|
||||
|
||||
final Uint8List? content;
|
||||
final String? mimeType;
|
||||
final String? ext;
|
||||
final String? name;
|
||||
final bool showLabel;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var sm = ScaffoldMessenger.of(context);
|
||||
return Tooltip(
|
||||
message: showLabel ? '' : kLabelDownload,
|
||||
child: SizedBox(
|
||||
width: showLabel ? null : kTextButtonMinWidth,
|
||||
child: TextButton(
|
||||
onPressed: (content != null)
|
||||
? () async {
|
||||
var message = "";
|
||||
var path = await getFileDownloadpath(
|
||||
name,
|
||||
ext ?? getFileExtension(mimeType),
|
||||
);
|
||||
if (path != null) {
|
||||
try {
|
||||
await saveFile(path, content!);
|
||||
var sp = getShortPath(path);
|
||||
message = 'Saved to $sp';
|
||||
} catch (e) {
|
||||
message = "An error occurred while saving file.";
|
||||
}
|
||||
} else {
|
||||
message = "Unable to determine the download path.";
|
||||
}
|
||||
sm.hideCurrentSnackBar();
|
||||
sm.showSnackBar(getSnackBar(message, small: false));
|
||||
}
|
||||
: null,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(
|
||||
Icons.download,
|
||||
size: 20,
|
||||
),
|
||||
if (showLabel) const Text(kLabelDownload)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class RepoButton extends StatelessWidget {
|
||||
const RepoButton({
|
||||
super.key,
|
||||
this.text,
|
||||
this.icon,
|
||||
});
|
||||
|
||||
final String? text;
|
||||
final IconData? icon;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var label = text ?? "GitHub";
|
||||
if (icon == null) {
|
||||
return FilledButton(
|
||||
onPressed: () {
|
||||
launchUrl(Uri.parse(kGitUrl));
|
||||
},
|
||||
child: Text(
|
||||
label,
|
||||
style: kTextStyleButton,
|
||||
),
|
||||
);
|
||||
}
|
||||
return FilledButton.icon(
|
||||
onPressed: () {
|
||||
launchUrl(Uri.parse(kGitUrl));
|
||||
},
|
||||
icon: Icon(
|
||||
icon,
|
||||
size: 20.0,
|
||||
),
|
||||
label: Text(
|
||||
label,
|
||||
style: kTextStyleButton,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class DiscordButton extends StatelessWidget {
|
||||
const DiscordButton({
|
||||
super.key,
|
||||
this.text,
|
||||
});
|
||||
|
||||
final String? text;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var label = text ?? 'Discord Server';
|
||||
return FilledButton.icon(
|
||||
onPressed: () {
|
||||
launchUrl(Uri.parse(kDiscordUrl));
|
||||
},
|
||||
icon: const Icon(
|
||||
Icons.discord,
|
||||
size: 20.0,
|
||||
),
|
||||
label: Text(
|
||||
label,
|
||||
style: kTextStyleButton,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ClearResponseButton extends StatelessWidget {
|
||||
const ClearResponseButton({
|
||||
super.key,
|
||||
this.onPressed,
|
||||
});
|
||||
|
||||
final VoidCallback? onPressed;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return IconButton(
|
||||
tooltip: kTooltipClearResponse,
|
||||
onPressed: onPressed,
|
||||
icon: const Icon(
|
||||
Icons.delete,
|
||||
size: 16,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
22
lib/widgets/card_request_details.dart
Normal file
22
lib/widgets/card_request_details.dart
Normal file
@ -0,0 +1,22 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:apidash/consts.dart';
|
||||
|
||||
class RequestDetailsCard extends StatelessWidget {
|
||||
const RequestDetailsCard({super.key, this.child});
|
||||
|
||||
final Widget? child;
|
||||
@override
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Card(
|
||||
shape: RoundedRectangleBorder(
|
||||
side: BorderSide(
|
||||
color: Theme.of(context).colorScheme.surfaceVariant,
|
||||
),
|
||||
borderRadius: kBorderRadius12,
|
||||
),
|
||||
elevation: 0,
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
}
|
127
lib/widgets/card_sidebar_environment.dart
Normal file
127
lib/widgets/card_sidebar_environment.dart
Normal file
@ -0,0 +1,127 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:apidash/consts.dart';
|
||||
import 'package:apidash/utils/utils.dart';
|
||||
import 'menus.dart' show ItemCardMenu;
|
||||
|
||||
class SidebarEnvironmentCard extends StatelessWidget {
|
||||
const SidebarEnvironmentCard({
|
||||
super.key,
|
||||
required this.id,
|
||||
this.isGlobal = false,
|
||||
this.isActive = false,
|
||||
this.name,
|
||||
this.selectedId,
|
||||
this.editRequestId,
|
||||
this.setActive,
|
||||
this.onTap,
|
||||
this.onDoubleTap,
|
||||
this.onSecondaryTap,
|
||||
this.onChangedNameEditor,
|
||||
this.focusNode,
|
||||
this.onTapOutsideNameEditor,
|
||||
this.onMenuSelected,
|
||||
});
|
||||
|
||||
final String id;
|
||||
final bool isGlobal;
|
||||
final bool isActive;
|
||||
final String? name;
|
||||
final String? selectedId;
|
||||
final String? editRequestId;
|
||||
final void Function(bool?)? setActive;
|
||||
final void Function()? onTap;
|
||||
final void Function()? onDoubleTap;
|
||||
final void Function()? onSecondaryTap;
|
||||
final Function(String)? onChangedNameEditor;
|
||||
final FocusNode? focusNode;
|
||||
final Function()? onTapOutsideNameEditor;
|
||||
final Function(ItemMenuOption)? onMenuSelected;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
final Color color =
|
||||
isGlobal ? colorScheme.secondaryContainer : colorScheme.surface;
|
||||
final Color colorVariant = colorScheme.surfaceVariant.withOpacity(0.5);
|
||||
final Color surfaceTint = colorScheme.primary;
|
||||
bool isSelected = selectedId == id;
|
||||
bool inEditMode = editRequestId == id;
|
||||
String nm = getEnvironmentTitle(name);
|
||||
return Tooltip(
|
||||
message: nm,
|
||||
triggerMode: TooltipTriggerMode.manual,
|
||||
waitDuration: const Duration(seconds: 1),
|
||||
child: Card(
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: kBorderRadius8,
|
||||
),
|
||||
elevation: isSelected ? 1 : 0,
|
||||
surfaceTintColor: isSelected ? surfaceTint : null,
|
||||
color: isSelected && !isGlobal
|
||||
? colorScheme.brightness == Brightness.dark
|
||||
? colorVariant
|
||||
: color
|
||||
: color,
|
||||
margin: EdgeInsets.zero,
|
||||
child: InkWell(
|
||||
borderRadius: kBorderRadius8,
|
||||
hoverColor: colorVariant,
|
||||
focusColor: colorVariant.withOpacity(0.5),
|
||||
onTap: inEditMode ? null : onTap,
|
||||
onSecondaryTap: onSecondaryTap,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(
|
||||
left: 6,
|
||||
right: isSelected ? 6 : 10,
|
||||
top: 5,
|
||||
bottom: 5,
|
||||
),
|
||||
child: SizedBox(
|
||||
height: 20,
|
||||
child: Row(
|
||||
children: [
|
||||
kHSpacer4,
|
||||
Expanded(
|
||||
child: inEditMode
|
||||
? TextFormField(
|
||||
key: ValueKey("$id-name"),
|
||||
initialValue: name,
|
||||
focusNode: focusNode,
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
onTapOutside: (_) {
|
||||
onTapOutsideNameEditor?.call();
|
||||
},
|
||||
onFieldSubmitted: (value) {
|
||||
onTapOutsideNameEditor?.call();
|
||||
},
|
||||
onChanged: onChangedNameEditor,
|
||||
decoration: const InputDecoration(
|
||||
isCollapsed: true,
|
||||
contentPadding: EdgeInsets.zero,
|
||||
border: InputBorder.none,
|
||||
),
|
||||
)
|
||||
: Text(
|
||||
nm,
|
||||
softWrap: false,
|
||||
overflow: TextOverflow.fade,
|
||||
),
|
||||
),
|
||||
Visibility(
|
||||
visible: isSelected && !inEditMode && !isGlobal,
|
||||
child: SizedBox(
|
||||
width: 28,
|
||||
child: ItemCardMenu(
|
||||
onSelected: onMenuSelected,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -132,146 +132,3 @@ class SidebarRequestCard extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class RequestDetailsCard extends StatelessWidget {
|
||||
const RequestDetailsCard({super.key, this.child});
|
||||
|
||||
final Widget? child;
|
||||
@override
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Card(
|
||||
shape: RoundedRectangleBorder(
|
||||
side: BorderSide(
|
||||
color: Theme.of(context).colorScheme.surfaceVariant,
|
||||
),
|
||||
borderRadius: kBorderRadius12,
|
||||
),
|
||||
elevation: 0,
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SidebarEnvironmentCard extends StatelessWidget {
|
||||
const SidebarEnvironmentCard({
|
||||
super.key,
|
||||
required this.id,
|
||||
this.isGlobal = false,
|
||||
this.isActive = false,
|
||||
this.name,
|
||||
this.selectedId,
|
||||
this.editRequestId,
|
||||
this.setActive,
|
||||
this.onTap,
|
||||
this.onDoubleTap,
|
||||
this.onSecondaryTap,
|
||||
this.onChangedNameEditor,
|
||||
this.focusNode,
|
||||
this.onTapOutsideNameEditor,
|
||||
this.onMenuSelected,
|
||||
});
|
||||
|
||||
final String id;
|
||||
final bool isGlobal;
|
||||
final bool isActive;
|
||||
final String? name;
|
||||
final String? selectedId;
|
||||
final String? editRequestId;
|
||||
final void Function(bool?)? setActive;
|
||||
final void Function()? onTap;
|
||||
final void Function()? onDoubleTap;
|
||||
final void Function()? onSecondaryTap;
|
||||
final Function(String)? onChangedNameEditor;
|
||||
final FocusNode? focusNode;
|
||||
final Function()? onTapOutsideNameEditor;
|
||||
final Function(ItemMenuOption)? onMenuSelected;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
final Color color =
|
||||
isGlobal ? colorScheme.secondaryContainer : colorScheme.surface;
|
||||
final Color colorVariant = colorScheme.surfaceVariant.withOpacity(0.5);
|
||||
final Color surfaceTint = colorScheme.primary;
|
||||
bool isSelected = selectedId == id;
|
||||
bool inEditMode = editRequestId == id;
|
||||
String nm = getEnvironmentTitle(name);
|
||||
return Tooltip(
|
||||
message: nm,
|
||||
triggerMode: TooltipTriggerMode.manual,
|
||||
waitDuration: const Duration(seconds: 1),
|
||||
child: Card(
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: kBorderRadius8,
|
||||
),
|
||||
elevation: isSelected ? 1 : 0,
|
||||
surfaceTintColor: isSelected ? surfaceTint : null,
|
||||
color: isSelected && !isGlobal
|
||||
? colorScheme.brightness == Brightness.dark
|
||||
? colorVariant
|
||||
: color
|
||||
: color,
|
||||
margin: EdgeInsets.zero,
|
||||
child: InkWell(
|
||||
borderRadius: kBorderRadius8,
|
||||
hoverColor: colorVariant,
|
||||
focusColor: colorVariant.withOpacity(0.5),
|
||||
onTap: inEditMode ? null : onTap,
|
||||
onSecondaryTap: onSecondaryTap,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(
|
||||
left: 6,
|
||||
right: isSelected ? 6 : 10,
|
||||
top: 5,
|
||||
bottom: 5,
|
||||
),
|
||||
child: SizedBox(
|
||||
height: 20,
|
||||
child: Row(
|
||||
children: [
|
||||
kHSpacer4,
|
||||
Expanded(
|
||||
child: inEditMode
|
||||
? TextFormField(
|
||||
key: ValueKey("$id-name"),
|
||||
initialValue: name,
|
||||
focusNode: focusNode,
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
onTapOutside: (_) {
|
||||
onTapOutsideNameEditor?.call();
|
||||
},
|
||||
onFieldSubmitted: (value) {
|
||||
onTapOutsideNameEditor?.call();
|
||||
},
|
||||
onChanged: onChangedNameEditor,
|
||||
decoration: const InputDecoration(
|
||||
isCollapsed: true,
|
||||
contentPadding: EdgeInsets.zero,
|
||||
border: InputBorder.none,
|
||||
),
|
||||
)
|
||||
: Text(
|
||||
nm,
|
||||
softWrap: false,
|
||||
overflow: TextOverflow.fade,
|
||||
),
|
||||
),
|
||||
Visibility(
|
||||
visible: isSelected && !inEditMode && !isGlobal,
|
||||
child: SizedBox(
|
||||
width: 28,
|
||||
child: ItemCardMenu(
|
||||
onSelected: onMenuSelected,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
41
lib/widgets/dialogs.dart
Normal file
41
lib/widgets/dialogs.dart
Normal file
@ -0,0 +1,41 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
showRenameDialog(
|
||||
BuildContext context,
|
||||
String dialogTitle,
|
||||
String? name,
|
||||
Function(String) onRename,
|
||||
) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
final controller = TextEditingController(text: name ?? "");
|
||||
controller.selection =
|
||||
TextSelection(baseOffset: 0, extentOffset: controller.text.length);
|
||||
return AlertDialog(
|
||||
title: Text(dialogTitle),
|
||||
content: TextField(
|
||||
autofocus: true,
|
||||
controller: controller,
|
||||
decoration: const InputDecoration(hintText: "Enter new name"),
|
||||
),
|
||||
actions: <Widget>[
|
||||
OutlinedButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: const Text('CANCEL')),
|
||||
FilledButton(
|
||||
onPressed: () {
|
||||
final val = controller.text.trim();
|
||||
onRename(val);
|
||||
Navigator.pop(context);
|
||||
Future.delayed(const Duration(milliseconds: 100), () {
|
||||
controller.dispose();
|
||||
});
|
||||
},
|
||||
child: const Text('OK')),
|
||||
],
|
||||
);
|
||||
});
|
||||
}
|
51
lib/widgets/dropdown_codegen.dart
Normal file
51
lib/widgets/dropdown_codegen.dart
Normal file
@ -0,0 +1,51 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:apidash/consts.dart';
|
||||
|
||||
class DropdownButtonCodegenLanguage extends StatelessWidget {
|
||||
const DropdownButtonCodegenLanguage({
|
||||
super.key,
|
||||
this.codegenLanguage,
|
||||
this.onChanged,
|
||||
});
|
||||
|
||||
final CodegenLanguage? codegenLanguage;
|
||||
final void Function(CodegenLanguage?)? onChanged;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final surfaceColor = Theme.of(context).colorScheme.surface;
|
||||
return DropdownButton<CodegenLanguage>(
|
||||
isExpanded: true,
|
||||
focusColor: surfaceColor,
|
||||
value: codegenLanguage,
|
||||
icon: const Icon(
|
||||
Icons.unfold_more_rounded,
|
||||
size: 16,
|
||||
),
|
||||
elevation: 4,
|
||||
style: kCodeStyle.copyWith(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
underline: Container(
|
||||
height: 0,
|
||||
),
|
||||
onChanged: onChanged,
|
||||
borderRadius: kBorderRadius12,
|
||||
items: CodegenLanguage.values
|
||||
.map<DropdownMenuItem<CodegenLanguage>>((CodegenLanguage value) {
|
||||
return DropdownMenuItem<CodegenLanguage>(
|
||||
value: value,
|
||||
child: Padding(
|
||||
padding: kPs8,
|
||||
child: Text(
|
||||
value.label,
|
||||
style: kTextStyleButton,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 1,
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
);
|
||||
}
|
||||
}
|
48
lib/widgets/dropdown_content_type.dart
Normal file
48
lib/widgets/dropdown_content_type.dart
Normal file
@ -0,0 +1,48 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:apidash/consts.dart';
|
||||
|
||||
class DropdownButtonContentType extends StatelessWidget {
|
||||
const DropdownButtonContentType({
|
||||
super.key,
|
||||
this.contentType,
|
||||
this.onChanged,
|
||||
});
|
||||
|
||||
final ContentType? contentType;
|
||||
final void Function(ContentType?)? onChanged;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final surfaceColor = Theme.of(context).colorScheme.surface;
|
||||
return DropdownButton<ContentType>(
|
||||
focusColor: surfaceColor,
|
||||
value: contentType,
|
||||
icon: const Icon(
|
||||
Icons.unfold_more_rounded,
|
||||
size: 16,
|
||||
),
|
||||
elevation: 4,
|
||||
style: kCodeStyle.copyWith(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
underline: Container(
|
||||
height: 0,
|
||||
),
|
||||
onChanged: onChanged,
|
||||
borderRadius: kBorderRadius12,
|
||||
items: ContentType.values
|
||||
.map<DropdownMenuItem<ContentType>>((ContentType value) {
|
||||
return DropdownMenuItem<ContentType>(
|
||||
value: value,
|
||||
child: Padding(
|
||||
padding: kPs8,
|
||||
child: Text(
|
||||
value.name,
|
||||
style: kTextStyleButton,
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
);
|
||||
}
|
||||
}
|
47
lib/widgets/dropdown_formdata.dart
Normal file
47
lib/widgets/dropdown_formdata.dart
Normal file
@ -0,0 +1,47 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:apidash/consts.dart';
|
||||
|
||||
class DropdownButtonFormData extends StatelessWidget {
|
||||
const DropdownButtonFormData({
|
||||
super.key,
|
||||
this.formDataType,
|
||||
this.onChanged,
|
||||
});
|
||||
|
||||
final FormDataType? formDataType;
|
||||
final void Function(FormDataType?)? onChanged;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final surfaceColor = Theme.of(context).colorScheme.surface;
|
||||
return DropdownButton<FormDataType>(
|
||||
dropdownColor: surfaceColor,
|
||||
focusColor: surfaceColor,
|
||||
value: formDataType,
|
||||
icon: const Icon(
|
||||
Icons.unfold_more_rounded,
|
||||
size: 16,
|
||||
),
|
||||
elevation: 4,
|
||||
style: kCodeStyle.copyWith(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
underline: const IgnorePointer(),
|
||||
onChanged: onChanged,
|
||||
borderRadius: kBorderRadius12,
|
||||
items: FormDataType.values
|
||||
.map<DropdownMenuItem<FormDataType>>((FormDataType value) {
|
||||
return DropdownMenuItem<FormDataType>(
|
||||
value: value,
|
||||
child: Padding(
|
||||
padding: kPs8,
|
||||
child: Text(
|
||||
value.name,
|
||||
style: kTextStyleButton,
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
);
|
||||
}
|
||||
}
|
49
lib/widgets/dropdown_http_method.dart
Normal file
49
lib/widgets/dropdown_http_method.dart
Normal file
@ -0,0 +1,49 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:apidash/utils/utils.dart';
|
||||
import 'package:apidash/consts.dart';
|
||||
import 'package:apidash/extensions/extensions.dart';
|
||||
|
||||
class DropdownButtonHttpMethod extends StatelessWidget {
|
||||
const DropdownButtonHttpMethod({
|
||||
super.key,
|
||||
this.method,
|
||||
this.onChanged,
|
||||
});
|
||||
|
||||
final HTTPVerb? method;
|
||||
final void Function(HTTPVerb? value)? onChanged;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final surfaceColor = Theme.of(context).colorScheme.surface;
|
||||
return DropdownButton<HTTPVerb>(
|
||||
focusColor: surfaceColor,
|
||||
value: method,
|
||||
icon: const Icon(Icons.unfold_more_rounded),
|
||||
elevation: 4,
|
||||
underline: Container(
|
||||
height: 0,
|
||||
),
|
||||
borderRadius: kBorderRadius12,
|
||||
onChanged: onChanged,
|
||||
items: HTTPVerb.values.map<DropdownMenuItem<HTTPVerb>>((HTTPVerb value) {
|
||||
return DropdownMenuItem<HTTPVerb>(
|
||||
value: value,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(left: context.isMediumWindow ? 8 : 16),
|
||||
child: Text(
|
||||
value.name.toUpperCase(),
|
||||
style: kCodeStyle.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: getHTTPMethodColor(
|
||||
value,
|
||||
brightness: Theme.of(context).brightness,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,189 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:apidash/utils/utils.dart';
|
||||
import 'package:apidash/consts.dart';
|
||||
import 'package:apidash/extensions/extensions.dart';
|
||||
|
||||
class DropdownButtonHttpMethod extends StatelessWidget {
|
||||
const DropdownButtonHttpMethod({
|
||||
super.key,
|
||||
this.method,
|
||||
this.onChanged,
|
||||
});
|
||||
|
||||
final HTTPVerb? method;
|
||||
final void Function(HTTPVerb? value)? onChanged;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final surfaceColor = Theme.of(context).colorScheme.surface;
|
||||
return DropdownButton<HTTPVerb>(
|
||||
focusColor: surfaceColor,
|
||||
value: method,
|
||||
icon: const Icon(Icons.unfold_more_rounded),
|
||||
elevation: 4,
|
||||
underline: Container(
|
||||
height: 0,
|
||||
),
|
||||
borderRadius: kBorderRadius12,
|
||||
onChanged: onChanged,
|
||||
items: HTTPVerb.values.map<DropdownMenuItem<HTTPVerb>>((HTTPVerb value) {
|
||||
return DropdownMenuItem<HTTPVerb>(
|
||||
value: value,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(left: context.isMediumWindow ? 8 : 16),
|
||||
child: Text(
|
||||
value.name.toUpperCase(),
|
||||
style: kCodeStyle.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: getHTTPMethodColor(
|
||||
value,
|
||||
brightness: Theme.of(context).brightness,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class DropdownButtonContentType extends StatelessWidget {
|
||||
const DropdownButtonContentType({
|
||||
super.key,
|
||||
this.contentType,
|
||||
this.onChanged,
|
||||
});
|
||||
|
||||
final ContentType? contentType;
|
||||
final void Function(ContentType?)? onChanged;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final surfaceColor = Theme.of(context).colorScheme.surface;
|
||||
return DropdownButton<ContentType>(
|
||||
focusColor: surfaceColor,
|
||||
value: contentType,
|
||||
icon: const Icon(
|
||||
Icons.unfold_more_rounded,
|
||||
size: 16,
|
||||
),
|
||||
elevation: 4,
|
||||
style: kCodeStyle.copyWith(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
underline: Container(
|
||||
height: 0,
|
||||
),
|
||||
onChanged: onChanged,
|
||||
borderRadius: kBorderRadius12,
|
||||
items: ContentType.values
|
||||
.map<DropdownMenuItem<ContentType>>((ContentType value) {
|
||||
return DropdownMenuItem<ContentType>(
|
||||
value: value,
|
||||
child: Padding(
|
||||
padding: kPs8,
|
||||
child: Text(
|
||||
value.name,
|
||||
style: kTextStyleButton,
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class DropdownButtonFormData extends StatelessWidget {
|
||||
const DropdownButtonFormData({
|
||||
super.key,
|
||||
this.formDataType,
|
||||
this.onChanged,
|
||||
});
|
||||
|
||||
final FormDataType? formDataType;
|
||||
final void Function(FormDataType?)? onChanged;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final surfaceColor = Theme.of(context).colorScheme.surface;
|
||||
return DropdownButton<FormDataType>(
|
||||
dropdownColor: surfaceColor,
|
||||
focusColor: surfaceColor,
|
||||
value: formDataType,
|
||||
icon: const Icon(
|
||||
Icons.unfold_more_rounded,
|
||||
size: 16,
|
||||
),
|
||||
elevation: 4,
|
||||
style: kCodeStyle.copyWith(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
underline: const IgnorePointer(),
|
||||
onChanged: onChanged,
|
||||
borderRadius: kBorderRadius12,
|
||||
items: FormDataType.values
|
||||
.map<DropdownMenuItem<FormDataType>>((FormDataType value) {
|
||||
return DropdownMenuItem<FormDataType>(
|
||||
value: value,
|
||||
child: Padding(
|
||||
padding: kPs8,
|
||||
child: Text(
|
||||
value.name,
|
||||
style: kTextStyleButton,
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class DropdownButtonCodegenLanguage extends StatelessWidget {
|
||||
const DropdownButtonCodegenLanguage({
|
||||
super.key,
|
||||
this.codegenLanguage,
|
||||
this.onChanged,
|
||||
});
|
||||
|
||||
final CodegenLanguage? codegenLanguage;
|
||||
final void Function(CodegenLanguage?)? onChanged;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final surfaceColor = Theme.of(context).colorScheme.surface;
|
||||
return DropdownButton<CodegenLanguage>(
|
||||
isExpanded: true,
|
||||
focusColor: surfaceColor,
|
||||
value: codegenLanguage,
|
||||
icon: const Icon(
|
||||
Icons.unfold_more_rounded,
|
||||
size: 16,
|
||||
),
|
||||
elevation: 4,
|
||||
style: kCodeStyle.copyWith(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
underline: Container(
|
||||
height: 0,
|
||||
),
|
||||
onChanged: onChanged,
|
||||
borderRadius: kBorderRadius12,
|
||||
items: CodegenLanguage.values
|
||||
.map<DropdownMenuItem<CodegenLanguage>>((CodegenLanguage value) {
|
||||
return DropdownMenuItem<CodegenLanguage>(
|
||||
value: value,
|
||||
child: Padding(
|
||||
padding: kPs8,
|
||||
child: Text(
|
||||
value.label,
|
||||
style: kTextStyleButton,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 1,
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,261 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:flutter_portal/flutter_portal.dart';
|
||||
import 'package:mention_tag_text_field/mention_tag_text_field.dart';
|
||||
import 'package:apidash/models/models.dart';
|
||||
import 'package:apidash/consts.dart';
|
||||
import '../screens/common/environment_autocomplete.dart';
|
||||
|
||||
class EnvironmentAutocompleteFieldBase extends StatefulHookWidget {
|
||||
const EnvironmentAutocompleteFieldBase({
|
||||
super.key,
|
||||
this.initialValue,
|
||||
this.onChanged,
|
||||
this.onFieldSubmitted,
|
||||
this.style,
|
||||
this.decoration,
|
||||
this.initialMentions,
|
||||
this.suggestions,
|
||||
this.mentionValue,
|
||||
required this.onMentionValueChanged,
|
||||
});
|
||||
|
||||
final String? initialValue;
|
||||
final void Function(String)? onChanged;
|
||||
final void Function(String)? onFieldSubmitted;
|
||||
final TextStyle? style;
|
||||
final InputDecoration? decoration;
|
||||
final List<(String, Object?, Widget?)>? initialMentions;
|
||||
final List<EnvironmentVariableSuggestion>? suggestions;
|
||||
final String? mentionValue;
|
||||
final void Function(String?) onMentionValueChanged;
|
||||
|
||||
@override
|
||||
State<EnvironmentAutocompleteFieldBase> createState() =>
|
||||
_EnvironmentAutocompleteFieldBaseState();
|
||||
}
|
||||
|
||||
class _EnvironmentAutocompleteFieldBaseState
|
||||
extends State<EnvironmentAutocompleteFieldBase> {
|
||||
final MentionTagTextEditingController controller =
|
||||
MentionTagTextEditingController();
|
||||
|
||||
final FocusNode focusNode = FocusNode();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
controller.text = widget.initialValue ?? "";
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isSuggestionsVisible = useState(false);
|
||||
|
||||
return PortalTarget(
|
||||
visible: isSuggestionsVisible.value && focusNode.hasFocus,
|
||||
portalFollower: EnvironmentSuggestionsMenu(
|
||||
mentionController: controller,
|
||||
suggestions: widget.suggestions,
|
||||
onSelect: (suggestion) {
|
||||
controller.addMention(
|
||||
label: '{${suggestion.variable.key}}}',
|
||||
data: suggestion,
|
||||
stylingWidget: EnvironmentVariableSpan(suggestion: suggestion));
|
||||
widget.onChanged?.call(controller.text);
|
||||
widget.onMentionValueChanged.call(null);
|
||||
isSuggestionsVisible.value = false;
|
||||
var mentionsCharacters =
|
||||
controller.mentions.fold<int>(0, (previousValue, element) {
|
||||
return previousValue + element.variable.key.length + 4 as int;
|
||||
});
|
||||
controller.selection = TextSelection.collapsed(
|
||||
offset: controller.text.length +
|
||||
controller.mentions.length -
|
||||
mentionsCharacters,
|
||||
);
|
||||
},
|
||||
),
|
||||
anchor: const Aligned(
|
||||
follower: Alignment.topLeft,
|
||||
target: Alignment.bottomLeft,
|
||||
backup: Aligned(
|
||||
follower: Alignment.bottomLeft,
|
||||
target: Alignment.topLeft,
|
||||
),
|
||||
),
|
||||
child: EnvironmentMentionField(
|
||||
focusNode: focusNode,
|
||||
controller: controller,
|
||||
initialMentions: widget.initialMentions ?? [],
|
||||
mentionValue: widget.mentionValue,
|
||||
onMentionValueChanged: widget.onMentionValueChanged,
|
||||
isSuggestionsVisible: isSuggestionsVisible,
|
||||
onChanged: widget.onChanged,
|
||||
onFieldSubmitted: widget.onFieldSubmitted,
|
||||
style: widget.style,
|
||||
decoration: widget.decoration,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class EnvironmentMentionField extends StatelessWidget {
|
||||
const EnvironmentMentionField({
|
||||
super.key,
|
||||
required this.focusNode,
|
||||
required this.controller,
|
||||
required this.initialMentions,
|
||||
required this.mentionValue,
|
||||
required this.onMentionValueChanged,
|
||||
required this.isSuggestionsVisible,
|
||||
this.onChanged,
|
||||
this.onFieldSubmitted,
|
||||
this.style,
|
||||
this.decoration,
|
||||
});
|
||||
|
||||
final FocusNode focusNode;
|
||||
final MentionTagTextEditingController controller;
|
||||
final List<(String, Object?, Widget?)> initialMentions;
|
||||
final String? mentionValue;
|
||||
final void Function(String?) onMentionValueChanged;
|
||||
final ValueNotifier<bool> isSuggestionsVisible;
|
||||
final void Function(String)? onChanged;
|
||||
final void Function(String)? onFieldSubmitted;
|
||||
final TextStyle? style;
|
||||
final InputDecoration? decoration;
|
||||
|
||||
void onMention(String? value) {
|
||||
onMentionValueChanged.call(value);
|
||||
if (value != null) {
|
||||
isSuggestionsVisible.value = true;
|
||||
} else {
|
||||
isSuggestionsVisible.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MentionTagTextFormField(
|
||||
focusNode: focusNode,
|
||||
onTap: () {
|
||||
focusNode.requestFocus();
|
||||
},
|
||||
onTapOutside: (event) {
|
||||
focusNode.unfocus();
|
||||
isSuggestionsVisible.value = false;
|
||||
},
|
||||
controller: controller,
|
||||
style: style,
|
||||
initialMentions: initialMentions,
|
||||
onMention: onMention,
|
||||
onChanged: (value) {
|
||||
onChanged?.call(controller.text);
|
||||
},
|
||||
onFieldSubmitted: (value) {
|
||||
onFieldSubmitted?.call(controller.text);
|
||||
isSuggestionsVisible.value = false;
|
||||
},
|
||||
decoration: decoration,
|
||||
mentionTagDecoration: const MentionTagDecoration(
|
||||
mentionStart: ['{'],
|
||||
mentionBreak:
|
||||
" ", // This is a workaround for the exception but adds a space after the mention
|
||||
maxWords: 1,
|
||||
allowDecrement: false,
|
||||
allowEmbedding: true,
|
||||
showMentionStartSymbol: false,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class EnvironmentSuggestionsMenu extends StatelessWidget {
|
||||
const EnvironmentSuggestionsMenu({
|
||||
super.key,
|
||||
required this.mentionController,
|
||||
required this.suggestions,
|
||||
this.onSelect,
|
||||
});
|
||||
|
||||
final MentionTagTextEditingController mentionController;
|
||||
final List<EnvironmentVariableSuggestion>? suggestions;
|
||||
final Function(EnvironmentVariableSuggestion)? onSelect;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return suggestions == null || suggestions!.isEmpty
|
||||
? const SizedBox.shrink()
|
||||
: ClipRRect(
|
||||
borderRadius: kBorderRadius8,
|
||||
child: Material(
|
||||
type: MaterialType.card,
|
||||
elevation: 8,
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxHeight: 200),
|
||||
child: Ink(
|
||||
width: 300,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
borderRadius: kBorderRadius8,
|
||||
border: Border.all(
|
||||
color: Theme.of(context).colorScheme.outlineVariant,
|
||||
),
|
||||
),
|
||||
child: ListView.separated(
|
||||
shrinkWrap: true,
|
||||
itemCount: suggestions?.length ?? 0,
|
||||
separatorBuilder: (context, index) => const Divider(
|
||||
height: 2,
|
||||
),
|
||||
itemBuilder: (context, index) {
|
||||
final suggestion = suggestions![index];
|
||||
return ListTile(
|
||||
dense: true,
|
||||
leading: EnvironmentIndicator(suggestion: suggestion),
|
||||
title: Text(suggestion.variable.key),
|
||||
subtitle: Text(suggestion.variable.value),
|
||||
onTap: () {
|
||||
onSelect?.call(suggestions![index]);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class EnvironmentIndicator extends StatelessWidget {
|
||||
const EnvironmentIndicator({super.key, required this.suggestion});
|
||||
|
||||
final EnvironmentVariableSuggestion suggestion;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isUnknown = suggestion.isUnknown;
|
||||
final isGlobal = suggestion.environmentId == kGlobalEnvironmentId;
|
||||
return Container(
|
||||
padding: kP4,
|
||||
decoration: BoxDecoration(
|
||||
color: isUnknown
|
||||
? Theme.of(context).colorScheme.errorContainer
|
||||
: isGlobal
|
||||
? Theme.of(context).colorScheme.secondaryContainer
|
||||
: Theme.of(context).colorScheme.primaryContainer,
|
||||
borderRadius: kBorderRadius4,
|
||||
),
|
||||
child: Icon(
|
||||
isUnknown
|
||||
? Icons.block
|
||||
: isGlobal
|
||||
? Icons.public
|
||||
: Icons.computer,
|
||||
size: 16,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
104
lib/widgets/environment_field_base.dart
Normal file
104
lib/widgets/environment_field_base.dart
Normal file
@ -0,0 +1,104 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:flutter_portal/flutter_portal.dart';
|
||||
import 'package:mention_tag_text_field/mention_tag_text_field.dart';
|
||||
import 'package:apidash/models/models.dart';
|
||||
import 'package:apidash/widgets/field_mention.dart';
|
||||
import '../screens/common_widgets/common_widgets.dart';
|
||||
import 'suggestions_menu.dart';
|
||||
|
||||
class EnvironmentFieldBase extends StatefulHookWidget {
|
||||
const EnvironmentFieldBase({
|
||||
super.key,
|
||||
this.initialValue,
|
||||
this.onChanged,
|
||||
this.onFieldSubmitted,
|
||||
this.style,
|
||||
this.decoration,
|
||||
this.initialMentions,
|
||||
this.suggestions,
|
||||
this.mentionValue,
|
||||
required this.onMentionValueChanged,
|
||||
});
|
||||
|
||||
final String? initialValue;
|
||||
final void Function(String)? onChanged;
|
||||
final void Function(String)? onFieldSubmitted;
|
||||
final TextStyle? style;
|
||||
final InputDecoration? decoration;
|
||||
final List<(String, Object?, Widget?)>? initialMentions;
|
||||
final List<EnvironmentVariableSuggestion>? suggestions;
|
||||
final String? mentionValue;
|
||||
final void Function(String?) onMentionValueChanged;
|
||||
|
||||
@override
|
||||
State<EnvironmentFieldBase> createState() =>
|
||||
_EnvironmentAutocompleteFieldBaseState();
|
||||
}
|
||||
|
||||
class _EnvironmentAutocompleteFieldBaseState
|
||||
extends State<EnvironmentFieldBase> {
|
||||
final MentionTagTextEditingController controller =
|
||||
MentionTagTextEditingController();
|
||||
|
||||
final FocusNode focusNode = FocusNode();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
controller.text = widget.initialValue ?? "";
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isSuggestionsVisible = useState(false);
|
||||
|
||||
return PortalTarget(
|
||||
visible: isSuggestionsVisible.value && focusNode.hasFocus,
|
||||
portalFollower: SuggestionsMenu(
|
||||
mentionController: controller,
|
||||
suggestions: widget.suggestions,
|
||||
suggestionBuilder: (context, index) {
|
||||
final suggestion = widget.suggestions![index];
|
||||
return ListTile(
|
||||
dense: true,
|
||||
leading: EnvVarIndicator(suggestion: suggestion),
|
||||
title: Text(suggestion.variable.key),
|
||||
subtitle: Text(suggestion.variable.value),
|
||||
onTap: () {
|
||||
controller.addMention(
|
||||
label: '{${suggestion.variable.key}}}',
|
||||
data: suggestion,
|
||||
stylingWidget: EnvVarSpan(suggestion: suggestion));
|
||||
widget.onChanged?.call(controller.text);
|
||||
widget.onMentionValueChanged.call(null);
|
||||
isSuggestionsVisible.value = false;
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
anchor: const Aligned(
|
||||
follower: Alignment.topLeft,
|
||||
target: Alignment.bottomLeft,
|
||||
backup: Aligned(
|
||||
follower: Alignment.bottomLeft,
|
||||
target: Alignment.topLeft,
|
||||
),
|
||||
),
|
||||
child: MentionField(
|
||||
focusNode: focusNode,
|
||||
controller: controller,
|
||||
initialMentions: widget.initialMentions ?? [],
|
||||
mentionValue: widget.mentionValue,
|
||||
onMentionValueChanged: widget.onMentionValueChanged,
|
||||
isSuggestionsVisible: isSuggestionsVisible,
|
||||
onChanged: widget.onChanged,
|
||||
onFieldSubmitted: widget.onFieldSubmitted,
|
||||
style: widget.style,
|
||||
decoration: widget.decoration,
|
||||
mentionStart: const ['{'],
|
||||
maxWords: 1,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
53
lib/widgets/field_cell.dart
Normal file
53
lib/widgets/field_cell.dart
Normal file
@ -0,0 +1,53 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:apidash/consts.dart';
|
||||
|
||||
class CellField extends StatelessWidget {
|
||||
const CellField({
|
||||
super.key,
|
||||
required this.keyId,
|
||||
this.initialValue,
|
||||
this.hintText,
|
||||
this.onChanged,
|
||||
this.colorScheme,
|
||||
});
|
||||
|
||||
final String keyId;
|
||||
final String? initialValue;
|
||||
final String? hintText;
|
||||
final void Function(String)? onChanged;
|
||||
final ColorScheme? colorScheme;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var clrScheme = colorScheme ?? Theme.of(context).colorScheme;
|
||||
return TextFormField(
|
||||
key: Key(keyId),
|
||||
initialValue: initialValue,
|
||||
style: kCodeStyle.copyWith(
|
||||
color: clrScheme.onSurface,
|
||||
),
|
||||
decoration: InputDecoration(
|
||||
hintStyle: kCodeStyle.copyWith(
|
||||
color: clrScheme.outline.withOpacity(
|
||||
kHintOpacity,
|
||||
),
|
||||
),
|
||||
hintText: hintText,
|
||||
contentPadding: const EdgeInsets.only(bottom: 12),
|
||||
focusedBorder: UnderlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: clrScheme.primary.withOpacity(
|
||||
kHintOpacity,
|
||||
),
|
||||
),
|
||||
),
|
||||
enabledBorder: UnderlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: clrScheme.surfaceVariant,
|
||||
),
|
||||
),
|
||||
),
|
||||
onChanged: onChanged,
|
||||
);
|
||||
}
|
||||
}
|
67
lib/widgets/field_cell_obscurable.dart
Normal file
67
lib/widgets/field_cell_obscurable.dart
Normal file
@ -0,0 +1,67 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:apidash/consts.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
|
||||
class ObscurableCellField extends HookWidget {
|
||||
const ObscurableCellField({
|
||||
super.key,
|
||||
required this.keyId,
|
||||
this.initialValue,
|
||||
this.hintText,
|
||||
this.onChanged,
|
||||
this.colorScheme,
|
||||
});
|
||||
|
||||
final String keyId;
|
||||
final String? initialValue;
|
||||
final String? hintText;
|
||||
final void Function(String)? onChanged;
|
||||
final ColorScheme? colorScheme;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final obscureText = useState(true);
|
||||
var clrScheme = colorScheme ?? Theme.of(context).colorScheme;
|
||||
return TextFormField(
|
||||
key: Key(keyId),
|
||||
initialValue: initialValue,
|
||||
style: kCodeStyle.copyWith(
|
||||
color: clrScheme.onSurface,
|
||||
),
|
||||
obscureText: obscureText.value,
|
||||
decoration: InputDecoration(
|
||||
hintStyle: kCodeStyle.copyWith(
|
||||
color: clrScheme.outline.withOpacity(
|
||||
kHintOpacity,
|
||||
),
|
||||
),
|
||||
hintText: hintText,
|
||||
suffixIcon: IconButton(
|
||||
padding: kP4,
|
||||
icon: Icon(
|
||||
obscureText.value ? Icons.visibility : Icons.visibility_off,
|
||||
color: clrScheme.onSurface,
|
||||
size: 14,
|
||||
),
|
||||
onPressed: () {
|
||||
obscureText.value = !obscureText.value;
|
||||
},
|
||||
),
|
||||
contentPadding: const EdgeInsets.only(bottom: 12),
|
||||
focusedBorder: UnderlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: clrScheme.primary.withOpacity(
|
||||
kHintOpacity,
|
||||
),
|
||||
),
|
||||
),
|
||||
enabledBorder: UnderlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: clrScheme.surfaceVariant,
|
||||
),
|
||||
),
|
||||
),
|
||||
onChanged: onChanged,
|
||||
);
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
import 'package:apidash/consts.dart';
|
||||
import 'package:apidash/utils/header_utils.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_typeahead/flutter_typeahead.dart';
|
||||
import 'package:apidash/utils/utils.dart';
|
||||
import 'package:apidash/consts.dart';
|
||||
|
||||
class HeaderField extends StatefulWidget {
|
||||
const HeaderField({
|
20
lib/widgets/field_json_search.dart
Normal file
20
lib/widgets/field_json_search.dart
Normal file
@ -0,0 +1,20 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:apidash/consts.dart';
|
||||
import 'field_raw.dart';
|
||||
|
||||
class JsonSearchField extends StatelessWidget {
|
||||
const JsonSearchField({super.key, this.onChanged, this.controller});
|
||||
|
||||
final void Function(String)? onChanged;
|
||||
final TextEditingController? controller;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return RawTextField(
|
||||
controller: controller,
|
||||
onChanged: onChanged,
|
||||
style: kCodeStyle,
|
||||
hintText: 'Search..',
|
||||
);
|
||||
}
|
||||
}
|
84
lib/widgets/field_mention.dart
Normal file
84
lib/widgets/field_mention.dart
Normal file
@ -0,0 +1,84 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:mention_tag_text_field/mention_tag_text_field.dart';
|
||||
|
||||
class MentionField extends StatelessWidget {
|
||||
const MentionField({
|
||||
super.key,
|
||||
required this.focusNode,
|
||||
required this.controller,
|
||||
required this.initialMentions,
|
||||
required this.mentionValue,
|
||||
required this.onMentionValueChanged,
|
||||
required this.isSuggestionsVisible,
|
||||
this.onChanged,
|
||||
this.onFieldSubmitted,
|
||||
this.style,
|
||||
this.decoration,
|
||||
required this.mentionStart,
|
||||
this.mentionBreak = "",
|
||||
this.maxWords,
|
||||
this.allowDecrement = false,
|
||||
this.allowEmbedding = true,
|
||||
this.showMentionStartSymbol = false,
|
||||
});
|
||||
|
||||
final FocusNode focusNode;
|
||||
final MentionTagTextEditingController controller;
|
||||
final List<(String, Object?, Widget?)> initialMentions;
|
||||
final String? mentionValue;
|
||||
final void Function(String?) onMentionValueChanged;
|
||||
final ValueNotifier<bool> isSuggestionsVisible;
|
||||
final void Function(String)? onChanged;
|
||||
final void Function(String)? onFieldSubmitted;
|
||||
final TextStyle? style;
|
||||
final InputDecoration? decoration;
|
||||
final List<String> mentionStart;
|
||||
final String mentionBreak;
|
||||
final int? maxWords;
|
||||
final bool allowDecrement;
|
||||
final bool allowEmbedding;
|
||||
final bool showMentionStartSymbol;
|
||||
|
||||
void onMention(String? value) {
|
||||
onMentionValueChanged.call(value);
|
||||
if (value != null) {
|
||||
isSuggestionsVisible.value = true;
|
||||
} else {
|
||||
isSuggestionsVisible.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MentionTagTextFormField(
|
||||
focusNode: focusNode,
|
||||
onTap: () {
|
||||
focusNode.requestFocus();
|
||||
},
|
||||
onTapOutside: (event) {
|
||||
focusNode.unfocus();
|
||||
isSuggestionsVisible.value = false;
|
||||
},
|
||||
controller: controller,
|
||||
style: style,
|
||||
initialMentions: initialMentions,
|
||||
onMention: onMention,
|
||||
onChanged: (value) {
|
||||
onChanged?.call(controller.text);
|
||||
},
|
||||
onFieldSubmitted: (value) {
|
||||
onFieldSubmitted?.call(controller.text);
|
||||
isSuggestionsVisible.value = false;
|
||||
},
|
||||
decoration: decoration,
|
||||
mentionTagDecoration: MentionTagDecoration(
|
||||
mentionStart: mentionStart,
|
||||
mentionBreak: mentionBreak,
|
||||
maxWords: maxWords,
|
||||
allowDecrement: allowDecrement,
|
||||
allowEmbedding: allowEmbedding,
|
||||
showMentionStartSymbol: showMentionStartSymbol,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
33
lib/widgets/field_raw.dart
Normal file
33
lib/widgets/field_raw.dart
Normal file
@ -0,0 +1,33 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class RawTextField extends StatelessWidget {
|
||||
const RawTextField({
|
||||
super.key,
|
||||
this.onChanged,
|
||||
this.controller,
|
||||
this.hintText,
|
||||
this.style,
|
||||
});
|
||||
|
||||
final void Function(String)? onChanged;
|
||||
final TextEditingController? controller;
|
||||
final String? hintText;
|
||||
final TextStyle? style;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return TextField(
|
||||
controller: controller,
|
||||
onChanged: onChanged,
|
||||
style: style,
|
||||
decoration: InputDecoration(
|
||||
isDense: true,
|
||||
border: InputBorder.none,
|
||||
hintText: hintText,
|
||||
),
|
||||
onTapOutside: (PointerDownEvent event) {
|
||||
FocusManager.instance.primaryFocus?.unfocus();
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
40
lib/widgets/field_url.dart
Normal file
40
lib/widgets/field_url.dart
Normal file
@ -0,0 +1,40 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:apidash/consts.dart';
|
||||
|
||||
class URLField extends StatelessWidget {
|
||||
const URLField({
|
||||
super.key,
|
||||
required this.selectedId,
|
||||
this.initialValue,
|
||||
this.onChanged,
|
||||
this.onFieldSubmitted,
|
||||
});
|
||||
|
||||
final String selectedId;
|
||||
final String? initialValue;
|
||||
final void Function(String)? onChanged;
|
||||
final void Function(String)? onFieldSubmitted;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return TextFormField(
|
||||
key: Key("url-$selectedId"),
|
||||
initialValue: initialValue,
|
||||
style: kCodeStyle,
|
||||
decoration: InputDecoration(
|
||||
hintText: kHintTextUrlCard,
|
||||
hintStyle: kCodeStyle.copyWith(
|
||||
color: Theme.of(context).colorScheme.outline.withOpacity(
|
||||
kHintOpacity,
|
||||
),
|
||||
),
|
||||
border: InputBorder.none,
|
||||
),
|
||||
onChanged: onChanged,
|
||||
onFieldSubmitted: onFieldSubmitted,
|
||||
onTapOutside: (PointerDownEvent event) {
|
||||
FocusManager.instance.primaryFocus?.unfocus();
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
@ -7,7 +7,7 @@ import 'package:url_launcher/url_launcher_string.dart';
|
||||
import '../consts.dart';
|
||||
import '../utils/ui_utils.dart';
|
||||
import "snackbars.dart";
|
||||
import 'textfields.dart';
|
||||
import 'field_json_search.dart';
|
||||
|
||||
class JsonPreviewerColor {
|
||||
const JsonPreviewerColor._();
|
||||
|
@ -2,7 +2,8 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_markdown/flutter_markdown.dart';
|
||||
import 'package:markdown/markdown.dart' as md;
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'buttons.dart';
|
||||
import 'button_discord.dart';
|
||||
import 'button_repo.dart';
|
||||
|
||||
class CustomMarkdown extends StatelessWidget {
|
||||
const CustomMarkdown({
|
||||
|
60
lib/widgets/splitview_dashboard.dart
Normal file
60
lib/widgets/splitview_dashboard.dart
Normal file
@ -0,0 +1,60 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:multi_split_view/multi_split_view.dart';
|
||||
import 'package:apidash/consts.dart';
|
||||
|
||||
class DashboardSplitView extends StatefulWidget {
|
||||
const DashboardSplitView({
|
||||
super.key,
|
||||
required this.sidebarWidget,
|
||||
required this.mainWidget,
|
||||
});
|
||||
|
||||
final Widget sidebarWidget;
|
||||
final Widget mainWidget;
|
||||
|
||||
@override
|
||||
DashboardSplitViewState createState() => DashboardSplitViewState();
|
||||
}
|
||||
|
||||
class DashboardSplitViewState extends State<DashboardSplitView> {
|
||||
final MultiSplitViewController _controller = MultiSplitViewController(
|
||||
areas: [
|
||||
Area(size: 250, minimalSize: 200),
|
||||
Area(minimalWeight: 0.7),
|
||||
],
|
||||
);
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MultiSplitViewTheme(
|
||||
data: MultiSplitViewThemeData(
|
||||
dividerThickness: 3,
|
||||
dividerPainter: DividerPainters.background(
|
||||
color: Theme.of(context).colorScheme.surfaceVariant,
|
||||
highlightedColor: Theme.of(context).colorScheme.outline.withOpacity(
|
||||
kHintOpacity,
|
||||
),
|
||||
animationEnabled: false,
|
||||
),
|
||||
),
|
||||
child: MultiSplitView(
|
||||
controller: _controller,
|
||||
children: [
|
||||
widget.sidebarWidget,
|
||||
widget.mainWidget,
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
@ -1,119 +1,9 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:multi_split_view/multi_split_view.dart';
|
||||
import 'package:apidash/extensions/extensions.dart';
|
||||
import 'package:apidash/consts.dart';
|
||||
|
||||
class DashboardSplitView extends StatefulWidget {
|
||||
const DashboardSplitView({
|
||||
super.key,
|
||||
required this.sidebarWidget,
|
||||
required this.mainWidget,
|
||||
});
|
||||
|
||||
final Widget sidebarWidget;
|
||||
final Widget mainWidget;
|
||||
|
||||
@override
|
||||
DashboardSplitViewState createState() => DashboardSplitViewState();
|
||||
}
|
||||
|
||||
class DashboardSplitViewState extends State<DashboardSplitView> {
|
||||
final MultiSplitViewController _controller = MultiSplitViewController(
|
||||
areas: [
|
||||
Area(size: 250, minimalSize: 200),
|
||||
Area(minimalWeight: 0.7),
|
||||
],
|
||||
);
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MultiSplitViewTheme(
|
||||
data: MultiSplitViewThemeData(
|
||||
dividerThickness: 3,
|
||||
dividerPainter: DividerPainters.background(
|
||||
color: Theme.of(context).colorScheme.surfaceVariant,
|
||||
highlightedColor: Theme.of(context).colorScheme.outline.withOpacity(
|
||||
kHintOpacity,
|
||||
),
|
||||
animationEnabled: false,
|
||||
),
|
||||
),
|
||||
child: MultiSplitView(
|
||||
controller: _controller,
|
||||
children: [
|
||||
widget.sidebarWidget,
|
||||
widget.mainWidget,
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
class EqualSplitView extends StatefulWidget {
|
||||
const EqualSplitView({
|
||||
super.key,
|
||||
required this.leftWidget,
|
||||
required this.rightWidget,
|
||||
});
|
||||
|
||||
final Widget leftWidget;
|
||||
final Widget rightWidget;
|
||||
|
||||
@override
|
||||
State<EqualSplitView> createState() => _EqualSplitViewState();
|
||||
}
|
||||
|
||||
class _EqualSplitViewState extends State<EqualSplitView> {
|
||||
final MultiSplitViewController _controller = MultiSplitViewController(
|
||||
areas: [
|
||||
Area(minimalSize: kMinRequestEditorDetailsCardPaneSize),
|
||||
Area(minimalSize: kMinRequestEditorDetailsCardPaneSize),
|
||||
],
|
||||
);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MultiSplitViewTheme(
|
||||
data: MultiSplitViewThemeData(
|
||||
dividerThickness: 3,
|
||||
dividerPainter: DividerPainters.background(
|
||||
color: Theme.of(context).colorScheme.surfaceVariant,
|
||||
highlightedColor: Theme.of(context).colorScheme.outline.withOpacity(
|
||||
kHintOpacity,
|
||||
),
|
||||
animationEnabled: false,
|
||||
),
|
||||
),
|
||||
child: MultiSplitView(
|
||||
controller: _controller,
|
||||
children: [
|
||||
widget.leftWidget,
|
||||
widget.rightWidget,
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
class TwoDrawerScaffold extends StatelessWidget {
|
||||
const TwoDrawerScaffold({
|
||||
class DrawerSplitView extends StatelessWidget {
|
||||
const DrawerSplitView({
|
||||
super.key,
|
||||
required this.scaffoldKey,
|
||||
required this.mainContent,
|
55
lib/widgets/splitview_equal.dart
Normal file
55
lib/widgets/splitview_equal.dart
Normal file
@ -0,0 +1,55 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:multi_split_view/multi_split_view.dart';
|
||||
import 'package:apidash/consts.dart';
|
||||
|
||||
class EqualSplitView extends StatefulWidget {
|
||||
const EqualSplitView({
|
||||
super.key,
|
||||
required this.leftWidget,
|
||||
required this.rightWidget,
|
||||
});
|
||||
|
||||
final Widget leftWidget;
|
||||
final Widget rightWidget;
|
||||
|
||||
@override
|
||||
State<EqualSplitView> createState() => _EqualSplitViewState();
|
||||
}
|
||||
|
||||
class _EqualSplitViewState extends State<EqualSplitView> {
|
||||
final MultiSplitViewController _controller = MultiSplitViewController(
|
||||
areas: [
|
||||
Area(minimalSize: kMinRequestEditorDetailsCardPaneSize),
|
||||
Area(minimalSize: kMinRequestEditorDetailsCardPaneSize),
|
||||
],
|
||||
);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MultiSplitViewTheme(
|
||||
data: MultiSplitViewThemeData(
|
||||
dividerThickness: 3,
|
||||
dividerPainter: DividerPainters.background(
|
||||
color: Theme.of(context).colorScheme.surfaceVariant,
|
||||
highlightedColor: Theme.of(context).colorScheme.outline.withOpacity(
|
||||
kHintOpacity,
|
||||
),
|
||||
animationEnabled: false,
|
||||
),
|
||||
),
|
||||
child: MultiSplitView(
|
||||
controller: _controller,
|
||||
children: [
|
||||
widget.leftWidget,
|
||||
widget.rightWidget,
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
53
lib/widgets/suggestions_menu.dart
Normal file
53
lib/widgets/suggestions_menu.dart
Normal file
@ -0,0 +1,53 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:mention_tag_text_field/mention_tag_text_field.dart';
|
||||
import 'package:apidash/consts.dart';
|
||||
|
||||
class SuggestionsMenu extends StatelessWidget {
|
||||
const SuggestionsMenu({
|
||||
super.key,
|
||||
required this.mentionController,
|
||||
required this.suggestions,
|
||||
required this.suggestionBuilder,
|
||||
this.menuWidth = kSuggestionsMenuWidth,
|
||||
this.menuMaxHeight = kSuggestionsMenuMaxHeight,
|
||||
});
|
||||
|
||||
final MentionTagTextEditingController mentionController;
|
||||
final List? suggestions;
|
||||
final double menuWidth;
|
||||
final double menuMaxHeight;
|
||||
final Widget? Function(BuildContext, int) suggestionBuilder;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return suggestions == null || suggestions!.isEmpty
|
||||
? const SizedBox.shrink()
|
||||
: ClipRRect(
|
||||
borderRadius: kBorderRadius8,
|
||||
child: Material(
|
||||
type: MaterialType.card,
|
||||
elevation: 8,
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(maxHeight: menuMaxHeight),
|
||||
child: Ink(
|
||||
width: menuWidth,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
borderRadius: kBorderRadius8,
|
||||
border: Border.all(
|
||||
color: Theme.of(context).colorScheme.outlineVariant,
|
||||
),
|
||||
),
|
||||
child: ListView.separated(
|
||||
shrinkWrap: true,
|
||||
itemCount: suggestions?.length ?? 0,
|
||||
separatorBuilder: (context, index) =>
|
||||
const Divider(height: 2),
|
||||
itemBuilder: suggestionBuilder,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,205 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:apidash/consts.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
|
||||
class URLField extends StatelessWidget {
|
||||
const URLField({
|
||||
super.key,
|
||||
required this.selectedId,
|
||||
this.initialValue,
|
||||
this.onChanged,
|
||||
this.onFieldSubmitted,
|
||||
});
|
||||
|
||||
final String selectedId;
|
||||
final String? initialValue;
|
||||
final void Function(String)? onChanged;
|
||||
final void Function(String)? onFieldSubmitted;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return TextFormField(
|
||||
key: Key("url-$selectedId"),
|
||||
initialValue: initialValue,
|
||||
style: kCodeStyle,
|
||||
decoration: InputDecoration(
|
||||
hintText: kHintTextUrlCard,
|
||||
hintStyle: kCodeStyle.copyWith(
|
||||
color: Theme.of(context).colorScheme.outline.withOpacity(
|
||||
kHintOpacity,
|
||||
),
|
||||
),
|
||||
border: InputBorder.none,
|
||||
),
|
||||
onChanged: onChanged,
|
||||
onFieldSubmitted: onFieldSubmitted,
|
||||
onTapOutside: (PointerDownEvent event) {
|
||||
FocusManager.instance.primaryFocus?.unfocus();
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CellField extends StatelessWidget {
|
||||
const CellField({
|
||||
super.key,
|
||||
required this.keyId,
|
||||
this.initialValue,
|
||||
this.hintText,
|
||||
this.onChanged,
|
||||
this.colorScheme,
|
||||
});
|
||||
|
||||
final String keyId;
|
||||
final String? initialValue;
|
||||
final String? hintText;
|
||||
final void Function(String)? onChanged;
|
||||
final ColorScheme? colorScheme;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var clrScheme = colorScheme ?? Theme.of(context).colorScheme;
|
||||
return TextFormField(
|
||||
key: Key(keyId),
|
||||
initialValue: initialValue,
|
||||
style: kCodeStyle.copyWith(
|
||||
color: clrScheme.onSurface,
|
||||
),
|
||||
decoration: InputDecoration(
|
||||
hintStyle: kCodeStyle.copyWith(
|
||||
color: clrScheme.outline.withOpacity(
|
||||
kHintOpacity,
|
||||
),
|
||||
),
|
||||
hintText: hintText,
|
||||
contentPadding: const EdgeInsets.only(bottom: 12),
|
||||
focusedBorder: UnderlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: clrScheme.primary.withOpacity(
|
||||
kHintOpacity,
|
||||
),
|
||||
),
|
||||
),
|
||||
enabledBorder: UnderlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: clrScheme.surfaceVariant,
|
||||
),
|
||||
),
|
||||
),
|
||||
onChanged: onChanged,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ObscurableCellField extends HookWidget {
|
||||
const ObscurableCellField({
|
||||
super.key,
|
||||
required this.keyId,
|
||||
this.initialValue,
|
||||
this.hintText,
|
||||
this.onChanged,
|
||||
this.colorScheme,
|
||||
});
|
||||
|
||||
final String keyId;
|
||||
final String? initialValue;
|
||||
final String? hintText;
|
||||
final void Function(String)? onChanged;
|
||||
final ColorScheme? colorScheme;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final obscureText = useState(true);
|
||||
var clrScheme = colorScheme ?? Theme.of(context).colorScheme;
|
||||
return TextFormField(
|
||||
key: Key(keyId),
|
||||
initialValue: initialValue,
|
||||
style: kCodeStyle.copyWith(
|
||||
color: clrScheme.onSurface,
|
||||
),
|
||||
obscureText: obscureText.value,
|
||||
decoration: InputDecoration(
|
||||
hintStyle: kCodeStyle.copyWith(
|
||||
color: clrScheme.outline.withOpacity(
|
||||
kHintOpacity,
|
||||
),
|
||||
),
|
||||
hintText: hintText,
|
||||
suffixIcon: IconButton(
|
||||
padding: kP4,
|
||||
icon: Icon(
|
||||
obscureText.value ? Icons.visibility : Icons.visibility_off,
|
||||
color: clrScheme.onSurface,
|
||||
size: 14,
|
||||
),
|
||||
onPressed: () {
|
||||
obscureText.value = !obscureText.value;
|
||||
},
|
||||
),
|
||||
contentPadding: const EdgeInsets.only(bottom: 12),
|
||||
focusedBorder: UnderlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: clrScheme.primary.withOpacity(
|
||||
kHintOpacity,
|
||||
),
|
||||
),
|
||||
),
|
||||
enabledBorder: UnderlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: clrScheme.surfaceVariant,
|
||||
),
|
||||
),
|
||||
),
|
||||
onChanged: onChanged,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class JsonSearchField extends StatelessWidget {
|
||||
const JsonSearchField({super.key, this.onChanged, this.controller});
|
||||
|
||||
final void Function(String)? onChanged;
|
||||
final TextEditingController? controller;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return RawTextField(
|
||||
controller: controller,
|
||||
onChanged: onChanged,
|
||||
style: kCodeStyle,
|
||||
hintText: 'Search..',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class RawTextField extends StatelessWidget {
|
||||
const RawTextField({
|
||||
super.key,
|
||||
this.onChanged,
|
||||
this.controller,
|
||||
this.hintText,
|
||||
this.style,
|
||||
});
|
||||
|
||||
final void Function(String)? onChanged;
|
||||
final TextEditingController? controller;
|
||||
final String? hintText;
|
||||
final TextStyle? style;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return TextField(
|
||||
controller: controller,
|
||||
onChanged: onChanged,
|
||||
style: style,
|
||||
decoration: InputDecoration(
|
||||
isDense: true,
|
||||
border: InputBorder.none,
|
||||
hintText: hintText,
|
||||
),
|
||||
onTapOutside: (PointerDownEvent event) {
|
||||
FocusManager.instance.primaryFocus?.unfocus();
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
@ -1,14 +1,31 @@
|
||||
export 'buttons.dart';
|
||||
export 'cards.dart';
|
||||
export 'button_clear_response.dart';
|
||||
export 'button_copy.dart';
|
||||
export 'button_discord.dart';
|
||||
export 'button_repo.dart';
|
||||
export 'button_save_download.dart';
|
||||
export 'button_send.dart';
|
||||
export 'card_request_details.dart';
|
||||
export 'card_sidebar_environment.dart';
|
||||
export 'card_sidebar_request.dart';
|
||||
export 'checkbox.dart';
|
||||
export 'code_previewer.dart';
|
||||
export 'codegen_previewer.dart';
|
||||
export 'dropdowns.dart';
|
||||
export 'dialogs.dart';
|
||||
export 'dropdown_codegen.dart';
|
||||
export 'dropdown_content_type.dart';
|
||||
export 'dropdown_formdata.dart';
|
||||
export 'dropdown_http_method.dart';
|
||||
export 'editor_json.dart';
|
||||
export 'editor.dart';
|
||||
export 'environment_field.dart';
|
||||
export 'environment_field_base.dart';
|
||||
export 'error_message.dart';
|
||||
export 'headerfield.dart';
|
||||
export 'field_cell_obscurable.dart';
|
||||
export 'field_cell.dart';
|
||||
export 'field_header.dart';
|
||||
export 'field_json_search.dart';
|
||||
export 'field_mention.dart';
|
||||
export 'field_raw.dart';
|
||||
export 'field_url.dart';
|
||||
export 'intro_message.dart';
|
||||
export 'json_previewer.dart';
|
||||
export 'markdown.dart';
|
||||
@ -19,10 +36,12 @@ export 'previewer.dart';
|
||||
export 'request_widgets.dart';
|
||||
export 'response_widgets.dart';
|
||||
export 'snackbars.dart';
|
||||
export 'splitviews.dart';
|
||||
export 'splitview_drawer.dart';
|
||||
export 'splitview_dashboard.dart';
|
||||
export 'splitview_equal.dart';
|
||||
export 'suggestions_menu.dart';
|
||||
export 'tables.dart';
|
||||
export 'tabs.dart';
|
||||
export 'textfields.dart';
|
||||
export 'texts.dart';
|
||||
export 'uint8_audio_player.dart';
|
||||
export 'window_caption.dart';
|
||||
|
Reference in New Issue
Block a user