mirror of
https://github.com/foss42/apidash.git
synced 2025-08-06 13:51:20 +08:00
response pane widgets
This commit is contained in:
@ -1,217 +1,74 @@
|
||||
import 'dart:convert';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_json_view/flutter_json_view.dart';
|
||||
import 'package:apidash/providers/providers.dart';
|
||||
import 'package:apidash/models/models.dart';
|
||||
import 'package:apidash/utils/utils.dart';
|
||||
import 'package:apidash/consts.dart';
|
||||
import 'response_tabs/response_tabs.dart';
|
||||
|
||||
final jsonViewTheme = JsonViewTheme(
|
||||
defaultTextStyle: codeStyle,
|
||||
viewType: JsonViewType.collapsible,
|
||||
backgroundColor: colorBg,
|
||||
stringStyle: const TextStyle(color: Colors.brown),
|
||||
closeIcon: const Icon(
|
||||
Icons.arrow_drop_up,
|
||||
size: 18,
|
||||
),
|
||||
openIcon: const Icon(
|
||||
Icons.arrow_drop_down,
|
||||
size: 18,
|
||||
),
|
||||
);
|
||||
|
||||
class ResponseViewer extends ConsumerStatefulWidget {
|
||||
const ResponseViewer({super.key});
|
||||
class ResponseDetails extends ConsumerStatefulWidget {
|
||||
const ResponseDetails({super.key});
|
||||
|
||||
@override
|
||||
ConsumerState<ResponseViewer> createState() => _ResponseViewerState();
|
||||
ConsumerState<ResponseDetails> createState() => _ResponseDetailsState();
|
||||
}
|
||||
|
||||
class _ResponseViewerState extends ConsumerState<ResponseViewer> {
|
||||
class _ResponseDetailsState extends ConsumerState<ResponseDetails> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final activeId = ref.watch(activeIdStateProvider);
|
||||
final sentRequestId = ref.watch(sentRequestIdStateProvider);
|
||||
final collection = ref.watch(collectionStateNotifierProvider);
|
||||
final collection = ref.read(collectionStateNotifierProvider);
|
||||
final idIdx = collection.indexWhere((m) => m.id == activeId);
|
||||
final responseStatus = collection[idIdx].responseStatus;
|
||||
final message = collection[idIdx].message;
|
||||
return Container();
|
||||
}
|
||||
}
|
||||
|
||||
class ResponseiViewer extends StatelessWidget {
|
||||
final int statusCode;
|
||||
final String? message;
|
||||
final ResponseModel responseModel;
|
||||
|
||||
const ResponseiViewer({
|
||||
super.key,
|
||||
required this.statusCode,
|
||||
required this.message,
|
||||
required this.responseModel,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var requestHeaders = responseModel.requestHeaders ?? {};
|
||||
var responseHeaders = responseModel.headers ?? {};
|
||||
var body = responseModel.body ?? '';
|
||||
return Padding(
|
||||
padding: p10,
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
children: const [
|
||||
Text(
|
||||
"Response",
|
||||
style: textStyleButton,
|
||||
),
|
||||
],
|
||||
final responseModel = collection[idIdx].responseModel;
|
||||
return Column(
|
||||
children: [
|
||||
Row(
|
||||
children: const [
|
||||
Text(
|
||||
"Response",
|
||||
style: textStyleButton,
|
||||
),
|
||||
const SizedBox(height: 5),
|
||||
Row(
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 50,
|
||||
child: Text(
|
||||
statusCode.toString(),
|
||||
style: codeStyle.copyWith(
|
||||
color: getResponseStatusCodeColor(statusCode),
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Text(
|
||||
message ?? "",
|
||||
style: codeStyle.copyWith(
|
||||
color: getResponseStatusCodeColor(statusCode),
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
width: 100,
|
||||
child: Text(
|
||||
humanizeDuration(responseModel.time),
|
||||
style: codeStyle.copyWith(
|
||||
color: getResponseStatusCodeColor(statusCode),
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 5),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
"Request Headers (${requestHeaders.length} items)",
|
||||
style: codeStyle,
|
||||
),
|
||||
),
|
||||
if (requestHeaders.isNotEmpty)
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
await Clipboard.setData(
|
||||
ClipboardData(text: json.encode(requestHeaders)));
|
||||
},
|
||||
child: Row(
|
||||
children: const [
|
||||
Icon(
|
||||
Icons.content_copy,
|
||||
size: 20,
|
||||
),
|
||||
Text("Copy")
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (requestHeaders.isNotEmpty) const SizedBox(height: 5),
|
||||
if (requestHeaders.isNotEmpty)
|
||||
JsonView.map(
|
||||
requestHeaders,
|
||||
theme: jsonViewTheme,
|
||||
),
|
||||
const SizedBox(height: 5),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
"Response Headers (${responseHeaders.length} items)",
|
||||
style: codeStyle,
|
||||
),
|
||||
),
|
||||
if (responseHeaders.isNotEmpty)
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
await Clipboard.setData(
|
||||
ClipboardData(text: json.encode(responseHeaders)));
|
||||
},
|
||||
child: Row(
|
||||
children: const [
|
||||
Icon(
|
||||
Icons.content_copy,
|
||||
size: 20,
|
||||
),
|
||||
Text("Copy")
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (responseHeaders.isNotEmpty) const SizedBox(height: 5),
|
||||
if (responseHeaders.isNotEmpty)
|
||||
JsonView.map(
|
||||
responseHeaders,
|
||||
theme: jsonViewTheme,
|
||||
),
|
||||
const SizedBox(height: 5),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
"Body ${body.isEmpty ? '(empty)' : ''}",
|
||||
style: codeStyle,
|
||||
),
|
||||
),
|
||||
if (body.isNotEmpty)
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
await Clipboard.setData(ClipboardData(text: body));
|
||||
},
|
||||
child: Row(
|
||||
children: const [
|
||||
Icon(
|
||||
Icons.content_copy,
|
||||
size: 20,
|
||||
),
|
||||
Text("Copy")
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 5),
|
||||
if (body.isNotEmpty &&
|
||||
responseModel.contentType!.startsWith(JSON_MIMETYPE))
|
||||
JsonView.string(
|
||||
body,
|
||||
theme: jsonViewTheme,
|
||||
),
|
||||
if (body.isNotEmpty &&
|
||||
responseModel.contentType!.startsWith("text/"))
|
||||
Text(body),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 5),
|
||||
Row(
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 50,
|
||||
child: Text(
|
||||
"$responseStatus",
|
||||
style: codeStyle.copyWith(
|
||||
color: getResponseStatusCodeColor(responseStatus),
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Text(
|
||||
message ?? "",
|
||||
style: codeStyle.copyWith(
|
||||
color: getResponseStatusCodeColor(responseStatus),
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
width: 100,
|
||||
child: Text(
|
||||
humanizeDuration(responseModel!.time),
|
||||
style: codeStyle.copyWith(
|
||||
color: getResponseStatusCodeColor(responseStatus),
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const Expanded(
|
||||
child: ResponseTabs(),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ class _ResponsePaneState extends ConsumerState<ResponsePane> {
|
||||
Widget build(BuildContext context) {
|
||||
final activeId = ref.watch(activeIdStateProvider);
|
||||
final sentRequestId = ref.watch(sentRequestIdStateProvider);
|
||||
final collection = ref.watch(collectionStateNotifierProvider);
|
||||
final collection = ref.read(collectionStateNotifierProvider);
|
||||
final idIdx = collection.indexWhere((m) => m.id == activeId);
|
||||
final responseStatus = collection[idIdx].responseStatus;
|
||||
final message = collection[idIdx].message;
|
||||
@ -33,9 +33,8 @@ class _ResponsePaneState extends ConsumerState<ResponsePane> {
|
||||
}
|
||||
if (responseStatus == -1) {
|
||||
return ErrorMessage(message: message);
|
||||
} else {
|
||||
return const ResponseViewer();
|
||||
}
|
||||
return const ResponseDetails();
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -0,0 +1,73 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_json_view/flutter_json_view.dart';
|
||||
import 'package:apidash/providers/providers.dart';
|
||||
import 'package:apidash/widgets/jsonview.dart';
|
||||
import 'package:apidash/consts.dart';
|
||||
|
||||
class ResponseBody extends ConsumerStatefulWidget {
|
||||
const ResponseBody({super.key});
|
||||
|
||||
@override
|
||||
ConsumerState<ResponseBody> createState() => _ResponseBodyState();
|
||||
}
|
||||
|
||||
class _ResponseBodyState extends ConsumerState<ResponseBody> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final activeId = ref.watch(activeIdStateProvider);
|
||||
final collection = ref.watch(collectionStateNotifierProvider);
|
||||
final idIdx = collection.indexWhere((m) => m.id == activeId);
|
||||
final responseModel = collection[idIdx].responseModel;
|
||||
var body = responseModel?.body ?? '';
|
||||
var contentType = responseModel?.contentType ?? "";
|
||||
return Padding(
|
||||
padding: p10,
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
const SizedBox(height: 5),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
"Body ${body.isEmpty ? '(empty)' : ''}",
|
||||
style: codeStyle,
|
||||
),
|
||||
),
|
||||
if (body.isNotEmpty)
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
await Clipboard.setData(ClipboardData(text: body));
|
||||
},
|
||||
child: Row(
|
||||
children: const [
|
||||
Icon(
|
||||
Icons.content_copy,
|
||||
size: 20,
|
||||
),
|
||||
Text("Copy")
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 5),
|
||||
if (body.isNotEmpty && contentType.startsWith(JSON_MIMETYPE))
|
||||
JsonView.string(
|
||||
body,
|
||||
theme: jsonViewTheme,
|
||||
),
|
||||
if (body.isNotEmpty && contentType.startsWith("text/")) Text(body),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,106 @@
|
||||
import 'dart:convert';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_json_view/flutter_json_view.dart';
|
||||
import 'package:apidash/providers/providers.dart';
|
||||
import 'package:apidash/widgets/jsonview.dart';
|
||||
import 'package:apidash/consts.dart';
|
||||
|
||||
class ResponseHeaders extends ConsumerStatefulWidget {
|
||||
const ResponseHeaders({super.key});
|
||||
|
||||
@override
|
||||
ConsumerState<ResponseHeaders> createState() => _ResponseHeadersState();
|
||||
}
|
||||
|
||||
class _ResponseHeadersState extends ConsumerState<ResponseHeaders> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final activeId = ref.watch(activeIdStateProvider);
|
||||
final collection = ref.watch(collectionStateNotifierProvider);
|
||||
final idIdx = collection.indexWhere((m) => m.id == activeId);
|
||||
final requestHeaders =
|
||||
collection[idIdx].responseModel?.requestHeaders ?? {};
|
||||
final responseHeaders = collection[idIdx].responseModel?.headers ?? {};
|
||||
return Container(
|
||||
decoration: tableContainerDecoration,
|
||||
margin: p5,
|
||||
child: ListView(
|
||||
children: [
|
||||
const SizedBox(height: 5),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
"Request Headers (${requestHeaders.length} items)",
|
||||
style: codeStyle,
|
||||
),
|
||||
),
|
||||
if (requestHeaders.isNotEmpty)
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
await Clipboard.setData(
|
||||
ClipboardData(text: json.encode(requestHeaders)));
|
||||
},
|
||||
child: Row(
|
||||
children: const [
|
||||
Icon(
|
||||
Icons.content_copy,
|
||||
size: 20,
|
||||
),
|
||||
Text("Copy")
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (requestHeaders.isNotEmpty) const SizedBox(height: 5),
|
||||
if (requestHeaders.isNotEmpty)
|
||||
JsonView.map(
|
||||
requestHeaders,
|
||||
theme: jsonViewTheme,
|
||||
),
|
||||
const SizedBox(height: 5),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
"Response Headers (${responseHeaders.length} items)",
|
||||
style: codeStyle,
|
||||
),
|
||||
),
|
||||
if (responseHeaders.isNotEmpty)
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
await Clipboard.setData(
|
||||
ClipboardData(text: json.encode(responseHeaders)));
|
||||
},
|
||||
child: Row(
|
||||
children: const [
|
||||
Icon(
|
||||
Icons.content_copy,
|
||||
size: 20,
|
||||
),
|
||||
Text("Copy")
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (responseHeaders.isNotEmpty) const SizedBox(height: 5),
|
||||
if (responseHeaders.isNotEmpty)
|
||||
JsonView.map(
|
||||
responseHeaders,
|
||||
theme: jsonViewTheme,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,84 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:apidash/providers/providers.dart';
|
||||
import 'package:apidash/consts.dart';
|
||||
import 'response_headers.dart';
|
||||
import 'response_body.dart';
|
||||
|
||||
class ResponseTabs extends ConsumerStatefulWidget {
|
||||
const ResponseTabs({super.key});
|
||||
|
||||
@override
|
||||
ConsumerState<ResponseTabs> createState() => _ResponseTabsState();
|
||||
}
|
||||
|
||||
class _ResponseTabsState extends ConsumerState<ResponseTabs>
|
||||
with TickerProviderStateMixin {
|
||||
late final TabController _controller;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controller = TabController(
|
||||
length: 2,
|
||||
animationDuration: tabAnimationDuration,
|
||||
vsync: this,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final activeId = ref.watch(activeIdStateProvider);
|
||||
return Column(
|
||||
children: [
|
||||
TabBar(
|
||||
key: Key(activeId!),
|
||||
controller: _controller,
|
||||
overlayColor: colorTransparent,
|
||||
onTap: (index) {},
|
||||
tabs: const [
|
||||
SizedBox(
|
||||
height: 45,
|
||||
child: Center(
|
||||
child: Text(
|
||||
'Body',
|
||||
textAlign: TextAlign.center,
|
||||
overflow: TextOverflow.fade,
|
||||
softWrap: false,
|
||||
style: textStyleButton,
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: 45,
|
||||
child: Center(
|
||||
child: Text(
|
||||
'Headers',
|
||||
textAlign: TextAlign.center,
|
||||
overflow: TextOverflow.fade,
|
||||
style: textStyleButton,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Expanded(
|
||||
child: TabBarView(
|
||||
controller: _controller,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
children: const [
|
||||
ResponseBody(),
|
||||
ResponseHeaders(),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
18
lib/widgets/jsonview.dart
Normal file
18
lib/widgets/jsonview.dart
Normal file
@ -0,0 +1,18 @@
|
||||
import 'package:flutter_json_view/flutter_json_view.dart';
|
||||
import 'package:apidash/consts.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
final jsonViewTheme = JsonViewTheme(
|
||||
defaultTextStyle: codeStyle,
|
||||
viewType: JsonViewType.collapsible,
|
||||
backgroundColor: colorBg,
|
||||
stringStyle: const TextStyle(color: Colors.brown),
|
||||
closeIcon: const Icon(
|
||||
Icons.arrow_drop_up,
|
||||
size: 18,
|
||||
),
|
||||
openIcon: const Icon(
|
||||
Icons.arrow_drop_down,
|
||||
size: 18,
|
||||
),
|
||||
);
|
Reference in New Issue
Block a user