mirror of
https://github.com/foss42/apidash.git
synced 2025-10-17 11:44:03 +08:00
Refactor Intro Screen, Collection Pane & URL Card
This commit is contained in:
@ -179,6 +179,8 @@ const kDarkCodeTheme = {
|
||||
'variable': TextStyle(color: Color(0xffaddb67)),
|
||||
};
|
||||
|
||||
enum RequestItemMenuOption { delete, duplicate }
|
||||
|
||||
enum HTTPVerb { get, head, post, put, patch, delete }
|
||||
|
||||
enum ContentType { json, text }
|
||||
@ -311,8 +313,6 @@ const Map<String, String> kCodeHighlighterMap = {
|
||||
kSubTypeTextYml: "yaml",
|
||||
};
|
||||
|
||||
const sendingIndicator = AssetImage("assets/sending.gif");
|
||||
|
||||
// HTTP response status codes
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Status
|
||||
const kResponseCodeReasons = {
|
||||
@ -403,3 +403,9 @@ const kUnexpectedRaiseIssue =
|
||||
|
||||
const kRaiseIssue =
|
||||
"\nPlease raise an issue in API Dash GitHub repo so that we can resolve it.";
|
||||
|
||||
const kHintTextUrlCard = "Enter API endpoint like api.foss42.com/country/codes";
|
||||
const kLabelSend = "Send";
|
||||
const kLabelSending = "Sending..";
|
||||
const kLabelBusy = "Busy";
|
||||
const kLabelCopy = "Copy";
|
||||
|
@ -1,8 +1,8 @@
|
||||
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/models/models.dart';
|
||||
import 'package:apidash/utils/utils.dart';
|
||||
import 'package:apidash/consts.dart';
|
||||
|
||||
class CollectionPane extends ConsumerStatefulWidget {
|
||||
@ -152,8 +152,6 @@ class _RequestListState extends ConsumerState<RequestList> {
|
||||
}
|
||||
}
|
||||
|
||||
enum RequestItemMenuOption { delete, duplicate }
|
||||
|
||||
class RequestItem extends ConsumerStatefulWidget {
|
||||
const RequestItem({
|
||||
required this.id,
|
||||
@ -176,107 +174,27 @@ class _RequestItemState extends ConsumerState<RequestItem> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final Color color = Theme.of(context).colorScheme.surface;
|
||||
final Color surfaceTint = Theme.of(context).colorScheme.primary;
|
||||
final activeRequest = ref.watch(activeIdStateProvider);
|
||||
bool isActiveId = activeRequest == widget.id;
|
||||
return Material(
|
||||
borderRadius: kBorderRadius12,
|
||||
elevation: isActiveId ? 1 : 0,
|
||||
surfaceTintColor: isActiveId ? surfaceTint : null,
|
||||
color: color,
|
||||
type: MaterialType.card,
|
||||
child: InkWell(
|
||||
borderRadius: kBorderRadius12,
|
||||
onTap: () {
|
||||
ref.read(activeIdStateProvider.notifier).update((state) => widget.id);
|
||||
},
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(
|
||||
left: 10,
|
||||
right: isActiveId ? 0 : 20,
|
||||
top: 5,
|
||||
bottom: 5,
|
||||
),
|
||||
child: SizedBox(
|
||||
height: 20,
|
||||
child: Row(
|
||||
children: [
|
||||
MethodBox(method: widget.requestModel.method),
|
||||
kHSpacer5,
|
||||
Expanded(
|
||||
child: Text(
|
||||
getRequestTitleFromUrl(widget.requestModel.url),
|
||||
softWrap: false,
|
||||
overflow: TextOverflow.fade,
|
||||
),
|
||||
),
|
||||
Visibility(
|
||||
visible: isActiveId,
|
||||
child: PopupMenuButton<RequestItemMenuOption>(
|
||||
padding: EdgeInsets.zero,
|
||||
splashRadius: 14,
|
||||
iconSize: 14,
|
||||
onSelected: (RequestItemMenuOption item) {
|
||||
if (item == RequestItemMenuOption.delete) {
|
||||
ref
|
||||
.read(activeIdStateProvider.notifier)
|
||||
.update((state) => null);
|
||||
ref
|
||||
.read(collectionStateNotifierProvider.notifier)
|
||||
.remove(widget.id);
|
||||
}
|
||||
if (item == RequestItemMenuOption.duplicate) {
|
||||
ref
|
||||
.read(collectionStateNotifierProvider.notifier)
|
||||
.duplicate(widget.id);
|
||||
}
|
||||
},
|
||||
itemBuilder: (BuildContext context) =>
|
||||
<PopupMenuEntry<RequestItemMenuOption>>[
|
||||
const PopupMenuItem<RequestItemMenuOption>(
|
||||
value: RequestItemMenuOption.delete,
|
||||
child: Text('Delete'),
|
||||
),
|
||||
const PopupMenuItem<RequestItemMenuOption>(
|
||||
value: RequestItemMenuOption.duplicate,
|
||||
child: Text('Duplicate'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class MethodBox extends StatelessWidget {
|
||||
const MethodBox({super.key, required this.method});
|
||||
final HTTPVerb method;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
String text = method.name.toUpperCase();
|
||||
if (method == HTTPVerb.delete) {
|
||||
text = "DEL";
|
||||
}
|
||||
return SizedBox(
|
||||
width: 28,
|
||||
child: Text(
|
||||
text,
|
||||
style: TextStyle(
|
||||
fontSize: 8,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: getHTTPMethodColor(
|
||||
method,
|
||||
brightness: Theme.of(context).brightness,
|
||||
),
|
||||
),
|
||||
),
|
||||
final activeRequestId = ref.watch(activeIdStateProvider);
|
||||
|
||||
return SidebarRequestCard(
|
||||
id: widget.id,
|
||||
activeRequestId: activeRequestId,
|
||||
url: widget.requestModel.url,
|
||||
method: widget.requestModel.method,
|
||||
onTap: () {
|
||||
ref.read(activeIdStateProvider.notifier).update((state) => widget.id);
|
||||
},
|
||||
onMenuSelected: (RequestItemMenuOption item) {
|
||||
if (item == RequestItemMenuOption.delete) {
|
||||
ref.read(activeIdStateProvider.notifier).update((state) => null);
|
||||
ref.read(collectionStateNotifierProvider.notifier).remove(widget.id);
|
||||
}
|
||||
if (item == RequestItemMenuOption.duplicate) {
|
||||
ref
|
||||
.read(collectionStateNotifierProvider.notifier)
|
||||
.duplicate(widget.id);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:apidash/providers/providers.dart';
|
||||
import 'package:apidash/widgets/widgets.dart';
|
||||
import 'package:apidash/consts.dart';
|
||||
import 'details_card/details_card.dart';
|
||||
import 'url_card.dart';
|
||||
@ -49,233 +49,16 @@ class RequestEditorPaneHome extends ConsumerWidget {
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final isDarkMode = ref.watch(darkModeProvider);
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 40,
|
||||
horizontal: 60,
|
||||
),
|
||||
child: ListView(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
"Welcome to API Dash ⚡️",
|
||||
style: Theme.of(context).textTheme.headlineLarge,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
kVSpacer20,
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
kIntro,
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
kVSpacer10,
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text.rich(
|
||||
TextSpan(
|
||||
children: [
|
||||
TextSpan(
|
||||
text: "Please support this project by giving us a ",
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
WidgetSpan(
|
||||
alignment: PlaceholderAlignment.middle,
|
||||
child: FilledButton.icon(
|
||||
onPressed: () {
|
||||
launchUrl(Uri.parse(kGitUrl));
|
||||
},
|
||||
icon: const Icon(Icons.star),
|
||||
label: const Text(
|
||||
'Star on GitHub',
|
||||
style: kTextStyleButton,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
kVSpacer20,
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
"Getting Started",
|
||||
style: Theme.of(context).textTheme.headlineSmall,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
kVSpacer20,
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text.rich(
|
||||
TextSpan(
|
||||
children: [
|
||||
TextSpan(
|
||||
text: "Click on the ",
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
WidgetSpan(
|
||||
alignment: PlaceholderAlignment.middle,
|
||||
child: ElevatedButton(
|
||||
onPressed: () {
|
||||
String newId = ref
|
||||
.read(collectionStateNotifierProvider.notifier)
|
||||
.add();
|
||||
ref
|
||||
.read(activeIdStateProvider.notifier)
|
||||
.update((state) => newId);
|
||||
},
|
||||
child: const Text(
|
||||
'+ New',
|
||||
style: kTextStyleButton,
|
||||
),
|
||||
),
|
||||
),
|
||||
TextSpan(
|
||||
text: " button to start drafting a new API request.",
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
kVSpacer10,
|
||||
Row(
|
||||
children: [
|
||||
Text.rich(
|
||||
TextSpan(
|
||||
children: [
|
||||
const TextSpan(
|
||||
text: "Choose your theme now: ",
|
||||
),
|
||||
WidgetSpan(
|
||||
alignment: PlaceholderAlignment.middle,
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: () async {
|
||||
await ref.read(darkModeProvider.notifier).toggle();
|
||||
},
|
||||
icon: isDarkMode
|
||||
? const Icon(Icons.dark_mode)
|
||||
: const Icon(Icons.light_mode),
|
||||
label: Text(
|
||||
isDarkMode ? "Dark" : "Light",
|
||||
style: kTextStyleButton,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
],
|
||||
),
|
||||
kVSpacer20,
|
||||
kVSpacer10,
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
"Support Channel",
|
||||
style: Theme.of(context).textTheme.headlineSmall,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
kVSpacer20,
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text.rich(
|
||||
TextSpan(
|
||||
children: [
|
||||
TextSpan(
|
||||
text: "Join our ",
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
WidgetSpan(
|
||||
alignment: PlaceholderAlignment.middle,
|
||||
child: FilledButton.icon(
|
||||
onPressed: () {
|
||||
launchUrl(Uri.parse(kDiscordUrl));
|
||||
},
|
||||
icon: const Icon(Icons.discord),
|
||||
label: const Text(
|
||||
'Discord Server',
|
||||
style: kTextStyleButton,
|
||||
),
|
||||
),
|
||||
),
|
||||
TextSpan(
|
||||
text: " and drop a message in the #foss channel.",
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
kVSpacer20,
|
||||
kVSpacer10,
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
"Report Bug / Request New Feature",
|
||||
style: Theme.of(context).textTheme.headlineSmall,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
kVSpacer20,
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text.rich(
|
||||
TextSpan(
|
||||
children: [
|
||||
TextSpan(
|
||||
text: "Just raise an issue in our ",
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
WidgetSpan(
|
||||
alignment: PlaceholderAlignment.middle,
|
||||
child: FilledButton.icon(
|
||||
onPressed: () {
|
||||
launchUrl(Uri.parse(kGitUrl));
|
||||
},
|
||||
icon: const Icon(Icons.code_rounded),
|
||||
label: const Text(
|
||||
'Github Repo',
|
||||
style: kTextStyleButton,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
return IntroMessage(
|
||||
isDarkMode: isDarkMode,
|
||||
onNew: () {
|
||||
String newId = ref.read(collectionStateNotifierProvider.notifier).add();
|
||||
ref.read(activeIdStateProvider.notifier).update((state) => newId);
|
||||
},
|
||||
onModeToggle: () async {
|
||||
await ref.read(darkModeProvider.notifier).toggle();
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:apidash/providers/providers.dart';
|
||||
import 'package:apidash/utils/utils.dart';
|
||||
import 'package:apidash/widgets/widgets.dart';
|
||||
import 'package:apidash/consts.dart';
|
||||
|
||||
class EditorPaneRequestURLCard extends StatefulWidget {
|
||||
@ -43,7 +43,7 @@ class _EditorPaneRequestURLCardState extends State<EditorPaneRequestURLCard> {
|
||||
kHSpacer20,
|
||||
SizedBox(
|
||||
height: 36,
|
||||
child: SendRequestButton(),
|
||||
child: SendButton(),
|
||||
),
|
||||
],
|
||||
),
|
||||
@ -71,42 +71,16 @@ class _DropdownButtonHTTPMethodState
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final surfaceColor = Theme.of(context).colorScheme.surface;
|
||||
final activeId = ref.watch(activeIdStateProvider);
|
||||
final method =
|
||||
ref.watch(activeRequestModelProvider.select((value) => value?.method));
|
||||
return DropdownButton<HTTPVerb>(
|
||||
focusColor: surfaceColor,
|
||||
value: method,
|
||||
icon: const Icon(Icons.unfold_more_rounded),
|
||||
elevation: 4,
|
||||
underline: Container(
|
||||
height: 0,
|
||||
),
|
||||
borderRadius: kBorderRadius12,
|
||||
return DropdownButtonHttpMethod(
|
||||
method: method,
|
||||
onChanged: (HTTPVerb? value) {
|
||||
ref
|
||||
.read(collectionStateNotifierProvider.notifier)
|
||||
.update(activeId!, method: value);
|
||||
},
|
||||
items: HTTPVerb.values.map<DropdownMenuItem<HTTPVerb>>((HTTPVerb value) {
|
||||
return DropdownMenuItem<HTTPVerb>(
|
||||
value: value,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(left: 16),
|
||||
child: Text(
|
||||
value.name.toUpperCase(),
|
||||
style: kCodeStyle.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: getHTTPMethodColor(
|
||||
value,
|
||||
brightness: Theme.of(context).brightness,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -129,22 +103,12 @@ class _URLTextFieldState extends ConsumerState<URLTextField> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final activeId = ref.watch(activeIdStateProvider);
|
||||
return TextFormField(
|
||||
key: Key("url-${activeId!}"),
|
||||
return URLField(
|
||||
activeId: activeId!,
|
||||
initialValue: ref
|
||||
.read(collectionStateNotifierProvider.notifier)
|
||||
.getRequestModel(activeId)
|
||||
.url,
|
||||
style: kCodeStyle,
|
||||
decoration: InputDecoration(
|
||||
hintText: "Enter API endpoint like api.foss42.com/country/codes",
|
||||
hintStyle: kCodeStyle.copyWith(
|
||||
color: Theme.of(context).colorScheme.outline.withOpacity(
|
||||
kHintOpacity,
|
||||
),
|
||||
),
|
||||
border: InputBorder.none,
|
||||
),
|
||||
onChanged: (value) {
|
||||
ref
|
||||
.read(collectionStateNotifierProvider.notifier)
|
||||
@ -154,16 +118,16 @@ class _URLTextFieldState extends ConsumerState<URLTextField> {
|
||||
}
|
||||
}
|
||||
|
||||
class SendRequestButton extends ConsumerStatefulWidget {
|
||||
const SendRequestButton({
|
||||
class SendButton extends ConsumerStatefulWidget {
|
||||
const SendButton({
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
ConsumerState<SendRequestButton> createState() => _SendRequestButtonState();
|
||||
ConsumerState<SendButton> createState() => _SendButtonState();
|
||||
}
|
||||
|
||||
class _SendRequestButtonState extends ConsumerState<SendRequestButton> {
|
||||
class _SendButtonState extends ConsumerState<SendButton> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
@ -173,31 +137,14 @@ class _SendRequestButtonState extends ConsumerState<SendRequestButton> {
|
||||
Widget build(BuildContext context) {
|
||||
final activeId = ref.watch(activeIdStateProvider);
|
||||
final sentRequestId = ref.watch(sentRequestIdStateProvider);
|
||||
bool disable = sentRequestId != null;
|
||||
return FilledButton(
|
||||
onPressed: disable
|
||||
? null
|
||||
: () {
|
||||
ref
|
||||
.read(collectionStateNotifierProvider.notifier)
|
||||
.sendRequest(activeId!);
|
||||
},
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
disable
|
||||
? (activeId == sentRequestId ? "Sending.." : "Busy")
|
||||
: "Send",
|
||||
style: kTextStyleButton,
|
||||
),
|
||||
if (!disable) kHSpacer10,
|
||||
if (!disable)
|
||||
const Icon(
|
||||
size: 16,
|
||||
Icons.send,
|
||||
),
|
||||
],
|
||||
),
|
||||
return SendRequestButton(
|
||||
activeId: activeId,
|
||||
sentRequestId: sentRequestId,
|
||||
onTap: () {
|
||||
ref
|
||||
.read(collectionStateNotifierProvider.notifier)
|
||||
.sendRequest(activeId!);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:multi_split_view/multi_split_view.dart';
|
||||
import 'package:apidash/consts.dart';
|
||||
import 'package:apidash/widgets/widgets.dart';
|
||||
import 'editor_pane/editor_pane.dart';
|
||||
import 'collection_pane.dart';
|
||||
|
||||
@ -12,53 +11,11 @@ class HomePage extends StatefulWidget {
|
||||
}
|
||||
|
||||
class HomePageState extends State<HomePage> {
|
||||
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 Scaffold(
|
||||
body: Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: 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: const [
|
||||
CollectionPane(),
|
||||
RequestEditorPane(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
return const DashboardSplitView(
|
||||
sidebarWidget: CollectionPane(),
|
||||
mainWidget: RequestEditorPane(),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:apidash/consts.dart';
|
||||
|
||||
class CopyButton extends StatefulWidget {
|
||||
const CopyButton({super.key, required this.toCopy});
|
||||
@ -22,7 +23,56 @@ class _CopyButtonState extends State<CopyButton> {
|
||||
Icons.content_copy,
|
||||
size: 20,
|
||||
),
|
||||
Text("Copy")
|
||||
Text(kLabelCopy)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SendRequestButton extends StatefulWidget {
|
||||
const SendRequestButton({
|
||||
super.key,
|
||||
required this.activeId,
|
||||
required this.sentRequestId,
|
||||
required this.onTap,
|
||||
});
|
||||
|
||||
final String? activeId;
|
||||
final String? sentRequestId;
|
||||
final void Function() onTap;
|
||||
|
||||
@override
|
||||
State<SendRequestButton> createState() => _SendRequestButtonState();
|
||||
}
|
||||
|
||||
class _SendRequestButtonState extends State<SendRequestButton> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
bool disable = widget.sentRequestId != null;
|
||||
return FilledButton(
|
||||
onPressed: disable ? null : widget.onTap,
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
disable
|
||||
? (widget.activeId == widget.sentRequestId
|
||||
? kLabelSending
|
||||
: kLabelBusy)
|
||||
: kLabelSend,
|
||||
style: kTextStyleButton,
|
||||
),
|
||||
if (!disable) kHSpacer10,
|
||||
if (!disable)
|
||||
const Icon(
|
||||
size: 16,
|
||||
Icons.send,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
77
lib/widgets/cards.dart
Normal file
77
lib/widgets/cards.dart
Normal file
@ -0,0 +1,77 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:apidash/consts.dart';
|
||||
import 'package:apidash/utils/utils.dart';
|
||||
import 'menus.dart' show RequestCardMenu;
|
||||
import 'texts.dart' show MethodBox;
|
||||
|
||||
class SidebarRequestCard extends StatefulWidget {
|
||||
const SidebarRequestCard({
|
||||
super.key,
|
||||
required this.id,
|
||||
required this.activeRequestId,
|
||||
required this.url,
|
||||
required this.method,
|
||||
this.onTap,
|
||||
this.onMenuSelected,
|
||||
});
|
||||
|
||||
final String id;
|
||||
final String? activeRequestId;
|
||||
final String url;
|
||||
final HTTPVerb method;
|
||||
final void Function()? onTap;
|
||||
final Function(RequestItemMenuOption)? onMenuSelected;
|
||||
|
||||
@override
|
||||
State<SidebarRequestCard> createState() => _SidebarRequestCardState();
|
||||
}
|
||||
|
||||
class _SidebarRequestCardState extends State<SidebarRequestCard> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final Color color = Theme.of(context).colorScheme.surface;
|
||||
final Color surfaceTint = Theme.of(context).colorScheme.primary;
|
||||
bool isActiveId = widget.activeRequestId == widget.id;
|
||||
return Material(
|
||||
borderRadius: kBorderRadius12,
|
||||
elevation: isActiveId ? 1 : 0,
|
||||
surfaceTintColor: isActiveId ? surfaceTint : null,
|
||||
color: color,
|
||||
type: MaterialType.card,
|
||||
child: InkWell(
|
||||
borderRadius: kBorderRadius12,
|
||||
onTap: widget.onTap,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(
|
||||
left: 10,
|
||||
right: isActiveId ? 0 : 20,
|
||||
top: 5,
|
||||
bottom: 5,
|
||||
),
|
||||
child: SizedBox(
|
||||
height: 20,
|
||||
child: Row(
|
||||
children: [
|
||||
MethodBox(method: widget.method),
|
||||
kHSpacer5,
|
||||
Expanded(
|
||||
child: Text(
|
||||
getRequestTitleFromUrl(widget.url),
|
||||
softWrap: false,
|
||||
overflow: TextOverflow.fade,
|
||||
),
|
||||
),
|
||||
Visibility(
|
||||
visible: isActiveId,
|
||||
child: RequestCardMenu(
|
||||
onSelected: widget.onMenuSelected,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
59
lib/widgets/dropdowns.dart
Normal file
59
lib/widgets/dropdowns.dart
Normal file
@ -0,0 +1,59 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:apidash/utils/utils.dart';
|
||||
import 'package:apidash/consts.dart';
|
||||
|
||||
class DropdownButtonHttpMethod extends StatefulWidget {
|
||||
const DropdownButtonHttpMethod({
|
||||
super.key,
|
||||
this.method,
|
||||
this.onChanged,
|
||||
});
|
||||
|
||||
final HTTPVerb? method;
|
||||
final void Function(HTTPVerb? value)? onChanged;
|
||||
|
||||
@override
|
||||
State<DropdownButtonHttpMethod> createState() =>
|
||||
_DropdownButtonHttpMethodState();
|
||||
}
|
||||
|
||||
class _DropdownButtonHttpMethodState extends State<DropdownButtonHttpMethod> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final surfaceColor = Theme.of(context).colorScheme.surface;
|
||||
return DropdownButton<HTTPVerb>(
|
||||
focusColor: surfaceColor,
|
||||
value: widget.method,
|
||||
icon: const Icon(Icons.unfold_more_rounded),
|
||||
elevation: 4,
|
||||
underline: Container(
|
||||
height: 0,
|
||||
),
|
||||
borderRadius: kBorderRadius12,
|
||||
onChanged: widget.onChanged,
|
||||
items: HTTPVerb.values.map<DropdownMenuItem<HTTPVerb>>((HTTPVerb value) {
|
||||
return DropdownMenuItem<HTTPVerb>(
|
||||
value: value,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(left: 16),
|
||||
child: Text(
|
||||
value.name.toUpperCase(),
|
||||
style: kCodeStyle.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: getHTTPMethodColor(
|
||||
value,
|
||||
brightness: Theme.of(context).brightness,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
);
|
||||
}
|
||||
}
|
244
lib/widgets/intro_message.dart
Normal file
244
lib/widgets/intro_message.dart
Normal file
@ -0,0 +1,244 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:apidash/consts.dart';
|
||||
|
||||
class IntroMessage extends StatefulWidget {
|
||||
const IntroMessage({
|
||||
super.key,
|
||||
required this.isDarkMode,
|
||||
this.onNew,
|
||||
this.onModeToggle,
|
||||
});
|
||||
|
||||
final bool isDarkMode;
|
||||
final void Function()? onNew;
|
||||
final void Function()? onModeToggle;
|
||||
|
||||
@override
|
||||
State<IntroMessage> createState() => _IntroMessageState();
|
||||
}
|
||||
|
||||
class _IntroMessageState extends State<IntroMessage> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 40,
|
||||
horizontal: 60,
|
||||
),
|
||||
child: ListView(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
"Welcome to API Dash ⚡️",
|
||||
style: Theme.of(context).textTheme.headlineLarge,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
kVSpacer20,
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
kIntro,
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
kVSpacer10,
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text.rich(
|
||||
TextSpan(
|
||||
children: [
|
||||
TextSpan(
|
||||
text: "Please support this project by giving us a ",
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
WidgetSpan(
|
||||
alignment: PlaceholderAlignment.middle,
|
||||
child: FilledButton.icon(
|
||||
onPressed: () {
|
||||
launchUrl(Uri.parse(kGitUrl));
|
||||
},
|
||||
icon: const Icon(Icons.star),
|
||||
label: const Text(
|
||||
'Star on GitHub',
|
||||
style: kTextStyleButton,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
kVSpacer20,
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
"Getting Started",
|
||||
style: Theme.of(context).textTheme.headlineSmall,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
kVSpacer20,
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text.rich(
|
||||
TextSpan(
|
||||
children: [
|
||||
TextSpan(
|
||||
text: "Click on the ",
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
WidgetSpan(
|
||||
alignment: PlaceholderAlignment.middle,
|
||||
child: ElevatedButton(
|
||||
onPressed: widget.onNew,
|
||||
child: const Text(
|
||||
'+ New',
|
||||
style: kTextStyleButton,
|
||||
),
|
||||
),
|
||||
),
|
||||
TextSpan(
|
||||
text: " button to start drafting a new API request.",
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
kVSpacer10,
|
||||
Row(
|
||||
children: [
|
||||
Text.rich(
|
||||
TextSpan(
|
||||
children: [
|
||||
const TextSpan(
|
||||
text: "Choose your theme now: ",
|
||||
),
|
||||
WidgetSpan(
|
||||
alignment: PlaceholderAlignment.middle,
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: widget.onModeToggle,
|
||||
icon: widget.isDarkMode
|
||||
? const Icon(Icons.dark_mode)
|
||||
: const Icon(Icons.light_mode),
|
||||
label: Text(
|
||||
widget.isDarkMode ? "Dark" : "Light",
|
||||
style: kTextStyleButton,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
],
|
||||
),
|
||||
kVSpacer20,
|
||||
kVSpacer10,
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
"Support Channel",
|
||||
style: Theme.of(context).textTheme.headlineSmall,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
kVSpacer20,
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text.rich(
|
||||
TextSpan(
|
||||
children: [
|
||||
TextSpan(
|
||||
text: "Join our ",
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
WidgetSpan(
|
||||
alignment: PlaceholderAlignment.middle,
|
||||
child: FilledButton.icon(
|
||||
onPressed: () {
|
||||
launchUrl(Uri.parse(kDiscordUrl));
|
||||
},
|
||||
icon: const Icon(Icons.discord),
|
||||
label: const Text(
|
||||
'Discord Server',
|
||||
style: kTextStyleButton,
|
||||
),
|
||||
),
|
||||
),
|
||||
TextSpan(
|
||||
text: " and drop a message in the #foss channel.",
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
kVSpacer20,
|
||||
kVSpacer10,
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
"Report Bug / Request New Feature",
|
||||
style: Theme.of(context).textTheme.headlineSmall,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
kVSpacer20,
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text.rich(
|
||||
TextSpan(
|
||||
children: [
|
||||
TextSpan(
|
||||
text: "Just raise an issue in our ",
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
WidgetSpan(
|
||||
alignment: PlaceholderAlignment.middle,
|
||||
child: FilledButton.icon(
|
||||
onPressed: () {
|
||||
launchUrl(Uri.parse(kGitUrl));
|
||||
},
|
||||
icon: const Icon(Icons.code_rounded),
|
||||
label: const Text(
|
||||
'Github Repo',
|
||||
style: kTextStyleButton,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
37
lib/widgets/menus.dart
Normal file
37
lib/widgets/menus.dart
Normal file
@ -0,0 +1,37 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:apidash/consts.dart';
|
||||
|
||||
class RequestCardMenu extends StatefulWidget {
|
||||
const RequestCardMenu({
|
||||
super.key,
|
||||
this.onSelected,
|
||||
});
|
||||
|
||||
final Function(RequestItemMenuOption)? onSelected;
|
||||
|
||||
@override
|
||||
State<RequestCardMenu> createState() => _RequestCardMenuState();
|
||||
}
|
||||
|
||||
class _RequestCardMenuState extends State<RequestCardMenu> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return PopupMenuButton<RequestItemMenuOption>(
|
||||
padding: EdgeInsets.zero,
|
||||
splashRadius: 14,
|
||||
iconSize: 14,
|
||||
onSelected: widget.onSelected,
|
||||
itemBuilder: (BuildContext context) =>
|
||||
<PopupMenuEntry<RequestItemMenuOption>>[
|
||||
const PopupMenuItem<RequestItemMenuOption>(
|
||||
value: RequestItemMenuOption.delete,
|
||||
child: Text('Delete'),
|
||||
),
|
||||
const PopupMenuItem<RequestItemMenuOption>(
|
||||
value: RequestItemMenuOption.duplicate,
|
||||
child: Text('Duplicate'),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
69
lib/widgets/splitviews.dart
Normal file
69
lib/widgets/splitviews.dart
Normal file
@ -0,0 +1,69 @@
|
||||
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({
|
||||
Key? key,
|
||||
required this.sidebarWidget,
|
||||
required this.mainWidget,
|
||||
}) : super(key: key);
|
||||
|
||||
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 Scaffold(
|
||||
body: Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: 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();
|
||||
}
|
||||
}
|
44
lib/widgets/textfields.dart
Normal file
44
lib/widgets/textfields.dart
Normal file
@ -0,0 +1,44 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:apidash/consts.dart';
|
||||
|
||||
class URLField extends StatefulWidget {
|
||||
const URLField({
|
||||
super.key,
|
||||
required this.activeId,
|
||||
this.initialValue,
|
||||
this.onChanged,
|
||||
});
|
||||
|
||||
final String activeId;
|
||||
final String? initialValue;
|
||||
final void Function(String)? onChanged;
|
||||
|
||||
@override
|
||||
State<URLField> createState() => _URLFieldState();
|
||||
}
|
||||
|
||||
class _URLFieldState extends State<URLField> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return TextFormField(
|
||||
key: Key("url-${widget.activeId}"),
|
||||
initialValue: widget.initialValue,
|
||||
style: kCodeStyle,
|
||||
decoration: InputDecoration(
|
||||
hintText: kHintTextUrlCard,
|
||||
hintStyle: kCodeStyle.copyWith(
|
||||
color: Theme.of(context).colorScheme.outline.withOpacity(
|
||||
kHintOpacity,
|
||||
),
|
||||
),
|
||||
border: InputBorder.none,
|
||||
),
|
||||
onChanged: widget.onChanged,
|
||||
);
|
||||
}
|
||||
}
|
30
lib/widgets/texts.dart
Normal file
30
lib/widgets/texts.dart
Normal file
@ -0,0 +1,30 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:apidash/utils/utils.dart';
|
||||
import 'package:apidash/consts.dart';
|
||||
|
||||
class MethodBox extends StatelessWidget {
|
||||
const MethodBox({super.key, required this.method});
|
||||
final HTTPVerb method;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
String text = method.name.toUpperCase();
|
||||
if (method == HTTPVerb.delete) {
|
||||
text = "DEL";
|
||||
}
|
||||
return SizedBox(
|
||||
width: 28,
|
||||
child: Text(
|
||||
text,
|
||||
style: TextStyle(
|
||||
fontSize: 8,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: getHTTPMethodColor(
|
||||
method,
|
||||
brightness: Theme.of(context).brightness,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -6,3 +6,10 @@ export 'code_previewer.dart';
|
||||
export 'codegen_previewer.dart';
|
||||
export 'error_message.dart';
|
||||
export 'sending_widget.dart';
|
||||
export 'dropdowns.dart';
|
||||
export 'splitviews.dart';
|
||||
export 'texts.dart';
|
||||
export 'textfields.dart';
|
||||
export 'menus.dart';
|
||||
export 'cards.dart';
|
||||
export 'intro_message.dart';
|
||||
|
Reference in New Issue
Block a user