Better handling of response of various content-types

This commit is contained in:
Ankit Mahato
2023-03-16 21:03:57 +05:30
parent ccef9c0162
commit a14284e20c
4 changed files with 128 additions and 106 deletions

View File

@ -83,6 +83,7 @@ const kTypeText = 'text';
const kSubTypeJson = 'json';
const kSubTypePdf = 'pdf';
const kSubTypeXml = 'xml'; // also text
const kSubTypeOctetStream = 'octet-stream';
// text
const kSubTypeCss = 'css';
@ -97,7 +98,7 @@ const kSubTypeSvg = 'svg+xml';
const kSubTypeDefaultViewOptions = 'all';
enum ResponseBodyView { preview, code, raw }
enum ResponseBodyView { preview, code, raw, none }
const Map<ResponseBodyView, IconData> kResponseBodyViewIcons = {
ResponseBodyView.preview: Icons.visibility_rounded,
@ -105,11 +106,11 @@ const Map<ResponseBodyView, IconData> kResponseBodyViewIcons = {
ResponseBodyView.raw: Icons.text_snippet_rounded
};
const kDefaultBodyViewOptions = [ResponseBodyView.raw];
const kNoBodyViewOptions = [ResponseBodyView.none];
const kRawBodyViewOptions = [ResponseBodyView.raw];
const kCodeRawBodyViewOptions = [ResponseBodyView.code, ResponseBodyView.raw];
const kPreviewRawBodyViewOptions = [
const kPreviewBodyViewOptions = [
ResponseBodyView.preview,
ResponseBodyView.raw
];
const kPreviewCodeRawBodyViewOptions = [
ResponseBodyView.preview,
@ -120,22 +121,22 @@ const kPreviewCodeRawBodyViewOptions = [
const Map<String, Map<String, List<ResponseBodyView>>>
kResponseBodyViewOptions = {
kTypeApplication: {
kSubTypeDefaultViewOptions: kDefaultBodyViewOptions,
kSubTypeDefaultViewOptions: kNoBodyViewOptions,
kSubTypeJson: kCodeRawBodyViewOptions,
kSubTypePdf: kPreviewRawBodyViewOptions,
kSubTypePdf: kPreviewBodyViewOptions,
kSubTypeXml: kCodeRawBodyViewOptions,
},
kTypeImage: {
kSubTypeDefaultViewOptions: kPreviewRawBodyViewOptions,
kSubTypeDefaultViewOptions: kPreviewBodyViewOptions,
},
kTypeAudio: {
kSubTypeDefaultViewOptions: kPreviewRawBodyViewOptions,
kSubTypeDefaultViewOptions: kPreviewBodyViewOptions,
},
kTypeVideo: {
kSubTypeDefaultViewOptions: kPreviewRawBodyViewOptions,
kSubTypeDefaultViewOptions: kPreviewBodyViewOptions,
},
kTypeText: {
kSubTypeDefaultViewOptions: kDefaultBodyViewOptions,
kSubTypeDefaultViewOptions: kRawBodyViewOptions,
kSubTypeCss: kCodeRawBodyViewOptions,
kSubTypeHtml: kCodeRawBodyViewOptions,
kSubTypeJavascript: kCodeRawBodyViewOptions,

View File

@ -1,8 +1,8 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:http_parser/http_parser.dart';
import 'package:apidash/providers/providers.dart';
import 'package:apidash/models/models.dart';
import 'package:apidash/widgets/widgets.dart';
import 'package:apidash/utils/utils.dart';
import 'package:apidash/consts.dart';
@ -27,6 +27,7 @@ class _ResponseBodyState extends ConsumerState<ResponseBody> {
final idIdx = collection.indexWhere((m) => m.id == activeId);
final responseModel = collection[idIdx].responseModel;
var mediaType = responseModel?.mediaType;
var body = responseModel?.body;
if (responseModel == null) {
return const ErrorMessage(
message: 'Error: No Response Data Found. $kRaiseIssue');
@ -36,112 +37,129 @@ class _ResponseBodyState extends ConsumerState<ResponseBody> {
message:
'Unknown Response content type - ${responseModel.contentType}. $kRaiseIssue');
}
if (body == null) {
return const ErrorMessage(
message: 'Response body is empty. $kRaiseIssue');
}
var responseBodyView = getResponseBodyViewOptions(mediaType);
print(responseBodyView);
var options = responseBodyView.$0;
var highlightLanguage = responseBodyView.$1;
if (options == kNoBodyViewOptions) {
return ErrorMessage(
message:
"Viewing response data of Content-Type\n'${mediaType.mimeType}' $kMimeTypeRaiseIssue");
}
return BodySuccess(
mediaType: mediaType,
responseModel: responseModel,
options: options,
bytes: responseModel.bodyBytes!,
body: body,
highlightLanguage: highlightLanguage,
);
}
}
class BodySuccess extends StatefulWidget {
const BodySuccess(
{super.key, required this.mediaType, required this.responseModel});
{super.key,
required this.mediaType,
required this.body,
required this.options,
required this.bytes,
this.highlightLanguage});
final MediaType mediaType;
final ResponseModel responseModel;
final List<ResponseBodyView> options;
final String body;
final Uint8List bytes;
final String? highlightLanguage;
@override
State<BodySuccess> createState() => _BodySuccessState();
}
class _BodySuccessState extends State<BodySuccess> {
int segmentIdx = 0;
@override
Widget build(BuildContext context) {
String? body = widget.responseModel.body;
if (body == null) {
return Padding(
padding: kP5,
child: Text(
'(empty)',
style: kCodeStyle,
),
);
}
var bytes = widget.responseModel.bodyBytes!;
var responseBodyView = getResponseBodyViewOptions(widget.mediaType);
print(responseBodyView);
var currentSeg = widget.options[segmentIdx];
final textContainerdecoration = BoxDecoration(
color: Theme.of(context).brightness == Brightness.dark
? Color.alphaBlend(
Theme.of(context).colorScheme.surface.withOpacity(0.8),
Colors.black)
: Color.alphaBlend(
Theme.of(context).colorScheme.surface.withOpacity(0.2),
Colors.white),
border: Border.all(color: Theme.of(context).colorScheme.surfaceVariant),
borderRadius: kBorderRadius8,
);
return Padding(
padding: kPh20v5,
child: SingleChildScrollView(
child: Column(
children: [
SizedBox(
height: kHeaderHeight,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
responseBodyView.$0 == kDefaultBodyViewOptions
? const SizedBox()
: ResponseBodyViewSelector(options: responseBodyView.$0),
CopyButton(toCopy: body),
],
),
padding: kP10,
child: Column(
children: [
SizedBox(
height: kHeaderHeight,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
(widget.options == kRawBodyViewOptions)
? const SizedBox()
: SegmentedButton<ResponseBodyView>(
selectedIcon: Icon(kResponseBodyViewIcons[currentSeg]),
segments: widget.options
.map<ButtonSegment<ResponseBodyView>>(
(e) => ButtonSegment<ResponseBodyView>(
value: e,
label: Text(capitalizeFirstLetter(e.name)),
icon: Icon(kResponseBodyViewIcons[e]),
),
)
.toList(),
selected: {currentSeg},
onSelectionChanged: (newSelection) {
setState(() {
segmentIdx =
widget.options.indexOf(newSelection.first);
});
},
),
kCodeRawBodyViewOptions.contains(currentSeg)
? CopyButton(toCopy: widget.body)
: const SizedBox(),
],
),
if (responseBodyView.$0.contains(ResponseBodyView.preview))
Previewer(
bytes: bytes,
type: widget.mediaType.type,
subtype: widget.mediaType.subtype,
),
if (responseBodyView.$0.contains(ResponseBodyView.code))
CodeHighlight(
input: body,
language: responseBodyView.$1,
textStyle: kCodeStyle,
),
if (responseBodyView.$0.contains(ResponseBodyView.raw))
SelectableText(body),
],
),
),
kVSpacer10,
Expanded(
child: currentSeg == ResponseBodyView.preview
? Previewer(
bytes: widget.bytes,
type: widget.mediaType.type,
subtype: widget.mediaType.subtype,
)
: (currentSeg == ResponseBodyView.code
? //SizedBox()
CodeHighlight(
input: widget.body,
language: widget.highlightLanguage,
textStyle: kCodeStyle,
)
: Container(
padding: kP8,
decoration: textContainerdecoration,
child: SingleChildScrollView(
child: SelectableText(
widget.body,
style: kCodeStyle,
),
),
)),
),
],
),
);
}
}
class ResponseBodyViewSelector extends StatefulWidget {
const ResponseBodyViewSelector({super.key, required this.options});
final List<ResponseBodyView> options;
@override
State<ResponseBodyViewSelector> createState() =>
_ResponseBodyViewSelectorState();
}
class _ResponseBodyViewSelectorState extends State<ResponseBodyViewSelector> {
late ResponseBodyView value;
@override
void initState() {
super.initState();
value = widget.options[0];
}
@override
Widget build(BuildContext context) {
return SegmentedButton<ResponseBodyView>(
segments: widget.options
.map<ButtonSegment<ResponseBodyView>>(
(e) => ButtonSegment<ResponseBodyView>(
value: e,
label: Text(capitalizeFirstLetter(e.name)),
icon: Icon(kResponseBodyViewIcons[e]),
),
)
.toList(),
selected: {value},
onSelectionChanged: (newSelection) {
setState(() {
value = newSelection.first;
});
},
);
}
}

View File

@ -122,6 +122,6 @@ String formatHeaderCase(String text) {
return (kResponseBodyViewOptions[type]![kSubTypeDefaultViewOptions]!, subtype);
}
else {
return (kDefaultBodyViewOptions, null);
return (kNoBodyViewOptions, null);
}
}

View File

@ -1,5 +1,6 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'error_message.dart';
import 'package:apidash/consts.dart';
class Previewer extends StatefulWidget {
@ -20,24 +21,26 @@ class _PreviewerState extends State<Previewer> {
@override
Widget build(BuildContext context) {
if (widget.type == kTypeApplication && widget.subtype == kSubTypePdf) {
return const SelectableText("PDF viewing $kMimeTypeRaiseIssue");
return const ErrorMessage(message: "PDF viewing $kMimeTypeRaiseIssue");
}
if (widget.type == kTypeImage) {
return Image.memory(
widget.bytes,
errorBuilder: (context, _, stackTrace) {
return SelectableText(
"${widget.type}/${widget.subtype} mimetype preview $kMimeTypeRaiseIssue");
return ErrorMessage(
message:
"${widget.type}/${widget.subtype} mimetype preview $kMimeTypeRaiseIssue");
},
);
}
if (widget.type == kTypeAudio) {
return const SelectableText("Audio playing $kMimeTypeRaiseIssue");
return const ErrorMessage(message: "Audio playing $kMimeTypeRaiseIssue");
}
if (widget.type == kTypeVideo) {
return const SelectableText("Video playing $kMimeTypeRaiseIssue");
return const ErrorMessage(message: "Video playing $kMimeTypeRaiseIssue");
}
return SelectableText(
"${widget.type}/${widget.subtype} mimetype preview $kMimeTypeRaiseIssue");
return ErrorMessage(
message:
"${widget.type}/${widget.subtype} mimetype preview $kMimeTypeRaiseIssue");
}
}