diff --git a/lib/consts.dart b/lib/consts.dart index d5d37dfd..7b8c6784 100644 --- a/lib/consts.dart +++ b/lib/consts.dart @@ -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 kResponseBodyViewIcons = { ResponseBodyView.preview: Icons.visibility_rounded, @@ -105,11 +106,11 @@ const Map 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>> 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, diff --git a/lib/screens/home_page/editor_pane/details_card/response_pane/response_tabs/response_body.dart b/lib/screens/home_page/editor_pane/details_card/response_pane/response_tabs/response_body.dart index 59a1e6ad..91ff7ea7 100644 --- a/lib/screens/home_page/editor_pane/details_card/response_pane/response_tabs/response_body.dart +++ b/lib/screens/home_page/editor_pane/details_card/response_pane/response_tabs/response_body.dart @@ -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 { 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 { 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 options; + final String body; + final Uint8List bytes; + final String? highlightLanguage; @override State createState() => _BodySuccessState(); } class _BodySuccessState extends State { + 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( + selectedIcon: Icon(kResponseBodyViewIcons[currentSeg]), + segments: widget.options + .map>( + (e) => ButtonSegment( + 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 options; - @override - State createState() => - _ResponseBodyViewSelectorState(); -} - -class _ResponseBodyViewSelectorState extends State { - late ResponseBodyView value; - - @override - void initState() { - super.initState(); - value = widget.options[0]; - } - - @override - Widget build(BuildContext context) { - return SegmentedButton( - segments: widget.options - .map>( - (e) => ButtonSegment( - value: e, - label: Text(capitalizeFirstLetter(e.name)), - icon: Icon(kResponseBodyViewIcons[e]), - ), - ) - .toList(), - selected: {value}, - onSelectionChanged: (newSelection) { - setState(() { - value = newSelection.first; - }); - }, - ); - } -} diff --git a/lib/utils/utils.dart b/lib/utils/utils.dart index 5f37e9fd..cf320a58 100644 --- a/lib/utils/utils.dart +++ b/lib/utils/utils.dart @@ -122,6 +122,6 @@ String formatHeaderCase(String text) { return (kResponseBodyViewOptions[type]![kSubTypeDefaultViewOptions]!, subtype); } else { - return (kDefaultBodyViewOptions, null); + return (kNoBodyViewOptions, null); } } \ No newline at end of file diff --git a/lib/widgets/previewer.dart b/lib/widgets/previewer.dart index dc5be558..22f96fdb 100644 --- a/lib/widgets/previewer.dart +++ b/lib/widgets/previewer.dart @@ -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 { @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"); } }