From 6709616afbce0dbe8733bf94398c7b1cef9638bb Mon Sep 17 00:00:00 2001 From: Youssef Amr El-Shehaby Date: Sat, 2 Mar 2024 12:30:26 +0200 Subject: [PATCH 1/5] remove row for html responses --- lib/widgets/response_widgets.dart | 52 +++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/lib/widgets/response_widgets.dart b/lib/widgets/response_widgets.dart index 539a2a341..faa15d431 100644 --- a/lib/widgets/response_widgets.dart +++ b/lib/widgets/response_widgets.dart @@ -388,6 +388,58 @@ class _BodySuccessState extends State { widget.options.length, constraints.maxWidth, ); + if (widget.mediaType.mimeType == "text/html") { + return Padding( + padding: kP10, + child: Column( + children: [ + kVSpacer10, + switch (currentSeg) { + ResponseBodyView.preview || ResponseBodyView.none => Expanded( + child: Container( + width: double.maxFinite, + padding: kP8, + decoration: textContainerdecoration, + child: Previewer( + bytes: widget.bytes, + body: widget.body, + type: widget.mediaType.type, + subtype: widget.mediaType.subtype, + hasRaw: widget.options.contains(ResponseBodyView.raw), + ), + ), + ), + ResponseBodyView.code => Expanded( + child: Container( + width: double.maxFinite, + padding: kP8, + decoration: textContainerdecoration, + child: CodePreviewer( + code: widget.formattedBody ?? widget.body, + theme: codeTheme, + language: widget.highlightLanguage, + textStyle: kCodeStyle, + ), + ), + ), + ResponseBodyView.raw => Expanded( + child: Container( + width: double.maxFinite, + padding: kP8, + decoration: textContainerdecoration, + child: SingleChildScrollView( + child: SelectableText( + widget.formattedBody ?? widget.body, + style: kCodeStyle, + ), + ), + ), + ), + } + ], + ), + ); + } return Padding( padding: kP10, child: Column( From e41c6ad2c51fb9978bb84568ad7007d11735a919 Mon Sep 17 00:00:00 2001 From: Youssef Amr El-Shehaby Date: Sat, 2 Mar 2024 12:36:03 +0200 Subject: [PATCH 2/5] refactor: check by MediaType object --- lib/widgets/response_widgets.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/widgets/response_widgets.dart b/lib/widgets/response_widgets.dart index faa15d431..62379a3cd 100644 --- a/lib/widgets/response_widgets.dart +++ b/lib/widgets/response_widgets.dart @@ -388,7 +388,7 @@ class _BodySuccessState extends State { widget.options.length, constraints.maxWidth, ); - if (widget.mediaType.mimeType == "text/html") { + if (widget.mediaType == MediaType(kTypeText, kSubTypeHtml)) { return Padding( padding: kP10, child: Column( From 99e91cbff43a106398ea8f5d956af464eccc518a Mon Sep 17 00:00:00 2001 From: Youssef Amr El-Shehaby Date: Sat, 2 Mar 2024 12:52:51 +0200 Subject: [PATCH 3/5] create html previewer --- lib/widgets/html_previewer.dart | 20 ++++++++++++++++++++ lib/widgets/previewer.dart | 4 ++++ lib/widgets/response_widgets.dart | 3 +-- 3 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 lib/widgets/html_previewer.dart diff --git a/lib/widgets/html_previewer.dart b/lib/widgets/html_previewer.dart new file mode 100644 index 000000000..ad0095925 --- /dev/null +++ b/lib/widgets/html_previewer.dart @@ -0,0 +1,20 @@ +import 'package:flutter/material.dart'; + +class HtmlPreviewer extends StatefulWidget { + const HtmlPreviewer({super.key, required this.htmlContent}); + final String htmlContent; + + @override + _HtmlPreviewerState createState() => _HtmlPreviewerState(); +} + +class _HtmlPreviewerState extends State { + @override + Widget build(BuildContext context) { + return const SingleChildScrollView( + child: Center( + child: Text("Some HTML Page"), + ), + ); + } +} diff --git a/lib/widgets/previewer.dart b/lib/widgets/previewer.dart index dd2f51869..a5df6b55a 100644 --- a/lib/widgets/previewer.dart +++ b/lib/widgets/previewer.dart @@ -1,4 +1,5 @@ import 'dart:convert'; +import 'package:apidash/widgets/html_previewer.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:printing/printing.dart'; @@ -88,6 +89,9 @@ class _PreviewerState extends State { if (widget.type == kTypeVideo) { // TODO: Video Player } + if (widget.subtype == kSubTypeHtml) { + return HtmlPreviewer(htmlContent: widget.body); + } String message = widget.hasRaw ? "$kMimeTypeRawRaiseIssueStart${widget.type}/${widget.subtype}$kMimeTypeRaiseIssue" : "$kMimeTypeRaiseIssueStart${widget.type}/${widget.subtype}$kMimeTypeRaiseIssue"; diff --git a/lib/widgets/response_widgets.dart b/lib/widgets/response_widgets.dart index 62379a3cd..8723084db 100644 --- a/lib/widgets/response_widgets.dart +++ b/lib/widgets/response_widgets.dart @@ -388,12 +388,11 @@ class _BodySuccessState extends State { widget.options.length, constraints.maxWidth, ); - if (widget.mediaType == MediaType(kTypeText, kSubTypeHtml)) { + if (widget.mediaType.subtype == kSubTypeHtml) { return Padding( padding: kP10, child: Column( children: [ - kVSpacer10, switch (currentSeg) { ResponseBodyView.preview || ResponseBodyView.none => Expanded( child: Container( From c398b5ea82cc40ef04d4208e1bdd0c44065b7560 Mon Sep 17 00:00:00 2001 From: Youssef Amr El-Shehaby Date: Sat, 2 Mar 2024 17:46:15 +0200 Subject: [PATCH 4/5] initialize webview_cef --- lib/widgets/html_previewer.dart | 55 +++++++++++++++++++++++++++---- lib/widgets/previewer.dart | 2 +- lib/widgets/response_widgets.dart | 50 +++++++--------------------- pubspec.lock | 42 ++++++++++++++++++++++- pubspec.yaml | 2 ++ 5 files changed, 104 insertions(+), 47 deletions(-) diff --git a/lib/widgets/html_previewer.dart b/lib/widgets/html_previewer.dart index ad0095925..060d6fab9 100644 --- a/lib/widgets/html_previewer.dart +++ b/lib/widgets/html_previewer.dart @@ -1,20 +1,61 @@ import 'package:flutter/material.dart'; +import 'package:webview_cef/webview_cef.dart'; class HtmlPreviewer extends StatefulWidget { - const HtmlPreviewer({super.key, required this.htmlContent}); - final String htmlContent; + const HtmlPreviewer({super.key, required this.url}); + final String url; @override _HtmlPreviewerState createState() => _HtmlPreviewerState(); } class _HtmlPreviewerState extends State { + final WebViewController _controller = WebViewController(); + final _textController = TextEditingController(); + String title = ""; + Future initPlatformState() async { + _textController.text = widget.url; + await _controller.initialize(); + await _controller.loadUrl(widget.url); + _controller.setWebviewListener(WebviewEventsListener( + onTitleChanged: (t) { + setState(() { + title = t; + }); + }, + onUrlChanged: (url) { + _textController.text = url; + }, + )); + + // ignore: prefer_collection_literals + final Set jsChannels = [ + JavascriptChannel( + name: 'Print', + onMessageReceived: (JavascriptMessage message) { + _controller.sendJavaScriptChannelCallBack( + false, + "{'code':'200','message':'print succeed!'}", + message.callbackId, + message.frameId); + }), + ].toSet(); + + await _controller.setJavaScriptChannels(jsChannels); + + await _controller.executeJavaScript("function abc(e){console.log(e)}"); + + if (!mounted) return; + } + + @override + void initState() { + super.initState(); + initPlatformState(); + } + @override Widget build(BuildContext context) { - return const SingleChildScrollView( - child: Center( - child: Text("Some HTML Page"), - ), - ); + return Expanded(child: WebView(_controller)); } } diff --git a/lib/widgets/previewer.dart b/lib/widgets/previewer.dart index a5df6b55a..634eacf72 100644 --- a/lib/widgets/previewer.dart +++ b/lib/widgets/previewer.dart @@ -90,7 +90,7 @@ class _PreviewerState extends State { // TODO: Video Player } if (widget.subtype == kSubTypeHtml) { - return HtmlPreviewer(htmlContent: widget.body); + return HtmlPreviewer(url: widget.body); } String message = widget.hasRaw ? "$kMimeTypeRawRaiseIssueStart${widget.type}/${widget.subtype}$kMimeTypeRaiseIssue" diff --git a/lib/widgets/response_widgets.dart b/lib/widgets/response_widgets.dart index 8723084db..dd4074c95 100644 --- a/lib/widgets/response_widgets.dart +++ b/lib/widgets/response_widgets.dart @@ -1,3 +1,4 @@ +import 'package:apidash/widgets/html_previewer.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:http_parser/http_parser.dart'; @@ -388,52 +389,25 @@ class _BodySuccessState extends State { widget.options.length, constraints.maxWidth, ); - if (widget.mediaType.subtype == kSubTypeHtml) { + if (widget.mediaType.subtype == kSubTypeHtml && + widget.mediaType.type == kTypeText) { return Padding( padding: kP10, child: Column( children: [ switch (currentSeg) { - ResponseBodyView.preview || ResponseBodyView.none => Expanded( - child: Container( - width: double.maxFinite, - padding: kP8, - decoration: textContainerdecoration, - child: Previewer( - bytes: widget.bytes, - body: widget.body, - type: widget.mediaType.type, - subtype: widget.mediaType.subtype, - hasRaw: widget.options.contains(ResponseBodyView.raw), - ), - ), - ), + ResponseBodyView.preview || + ResponseBodyView.none => + Container(), ResponseBodyView.code => Expanded( child: Container( - width: double.maxFinite, - padding: kP8, - decoration: textContainerdecoration, - child: CodePreviewer( - code: widget.formattedBody ?? widget.body, - theme: codeTheme, - language: widget.highlightLanguage, - textStyle: kCodeStyle, - ), - ), - ), - ResponseBodyView.raw => Expanded( - child: Container( - width: double.maxFinite, - padding: kP8, - decoration: textContainerdecoration, - child: SingleChildScrollView( - child: SelectableText( - widget.formattedBody ?? widget.body, - style: kCodeStyle, - ), - ), - ), + width: double.maxFinite, + padding: kP8, + decoration: textContainerdecoration, + child: const HtmlPreviewer( + url: "https://fluttergems.dev/")), ), + ResponseBodyView.raw => Container() } ], ), diff --git a/pubspec.lock b/pubspec.lock index f48a9105f..5243898e3 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1326,6 +1326,46 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.1" + webview_cef: + dependency: "direct main" + description: + name: webview_cef + sha256: a3e7a8e54ea612324be47359601808af9b9cdc57bdb96056b88394c345e3c3c6 + url: "https://pub.dev" + source: hosted + version: "0.1.0" + webview_flutter: + dependency: "direct main" + description: + name: webview_flutter + sha256: "25e1b6e839e8cbfbd708abc6f85ed09d1727e24e08e08c6b8590d7c65c9a8932" + url: "https://pub.dev" + source: hosted + version: "4.7.0" + webview_flutter_android: + dependency: transitive + description: + name: webview_flutter_android + sha256: "3e5f4e9d818086b0d01a66fb1ff9cc72ab0cc58c71980e3d3661c5685ea0efb0" + url: "https://pub.dev" + source: hosted + version: "3.15.0" + webview_flutter_platform_interface: + dependency: transitive + description: + name: webview_flutter_platform_interface + sha256: d937581d6e558908d7ae3dc1989c4f87b786891ab47bb9df7de548a151779d8d + url: "https://pub.dev" + source: hosted + version: "2.10.0" + webview_flutter_wkwebview: + dependency: transitive + description: + name: webview_flutter_wkwebview + sha256: "9bf168bccdf179ce90450b5f37e36fe263f591c9338828d6bf09b6f8d0f57f86" + url: "https://pub.dev" + source: hosted + version: "3.12.0" win32: dependency: transitive description: @@ -1377,4 +1417,4 @@ packages: version: "3.1.2" sdks: dart: ">=3.2.3 <4.0.0" - flutter: ">=3.16.0" + flutter: ">=3.16.6" diff --git a/pubspec.yaml b/pubspec.yaml index 8441a0f52..fbbda52fe 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -55,6 +55,8 @@ dependencies: dart_style: ^2.3.4 json_text_field: ^1.1.0 csv: ^5.1.1 + webview_cef: ^0.1.0 + webview_flutter: ^4.7.0 dev_dependencies: flutter_test: From 9b5ffe128de71f1d9067ea64faf30f7256c98b9f Mon Sep 17 00:00:00 2001 From: Youssef Amr El-Shehaby Date: Sat, 2 Mar 2024 19:04:21 +0200 Subject: [PATCH 5/5] make preview widget dynamic --- lib/widgets/previewer.dart | 3 - lib/widgets/response_widgets.dart | 149 +++++++++++++++--------------- 2 files changed, 73 insertions(+), 79 deletions(-) diff --git a/lib/widgets/previewer.dart b/lib/widgets/previewer.dart index 634eacf72..46130ace5 100644 --- a/lib/widgets/previewer.dart +++ b/lib/widgets/previewer.dart @@ -89,9 +89,6 @@ class _PreviewerState extends State { if (widget.type == kTypeVideo) { // TODO: Video Player } - if (widget.subtype == kSubTypeHtml) { - return HtmlPreviewer(url: widget.body); - } String message = widget.hasRaw ? "$kMimeTypeRawRaiseIssueStart${widget.type}/${widget.subtype}$kMimeTypeRaiseIssue" : "$kMimeTypeRaiseIssueStart${widget.type}/${widget.subtype}$kMimeTypeRaiseIssue"; diff --git a/lib/widgets/response_widgets.dart b/lib/widgets/response_widgets.dart index dd4074c95..bba43bc03 100644 --- a/lib/widgets/response_widgets.dart +++ b/lib/widgets/response_widgets.dart @@ -1,12 +1,16 @@ +import 'package:apidash/providers/collection_providers.dart'; +import 'package:apidash/providers/providers.dart'; import 'package:apidash/widgets/html_previewer.dart'; 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:lottie/lottie.dart'; import 'package:apidash/utils/utils.dart'; import 'package:apidash/widgets/widgets.dart'; import 'package:apidash/models/models.dart'; import 'package:apidash/consts.dart'; +import 'package:provider/provider.dart'; class NotSentWidget extends StatelessWidget { const NotSentWidget({super.key}); @@ -344,7 +348,7 @@ class ResponseBody extends StatelessWidget { } } -class BodySuccess extends StatefulWidget { +class BodySuccess extends ConsumerStatefulWidget { const BodySuccess( {super.key, required this.mediaType, @@ -360,14 +364,20 @@ class BodySuccess extends StatefulWidget { final String? formattedBody; final String? highlightLanguage; @override - State createState() => _BodySuccessState(); + ConsumerState createState() => _BodySuccessState(); } -class _BodySuccessState extends State { +class _BodySuccessState extends ConsumerState { int segmentIdx = 0; + @override + void initState() { + super.initState(); + ref.read(selectedRequestModelProvider); + } @override Widget build(BuildContext context) { + final String url = ref.watch(selectedRequestModelProvider)!.url; var currentSeg = widget.options[segmentIdx]; var codeTheme = Theme.of(context).brightness == Brightness.light ? kLightCodeTheme @@ -389,79 +399,64 @@ class _BodySuccessState extends State { widget.options.length, constraints.maxWidth, ); - if (widget.mediaType.subtype == kSubTypeHtml && - widget.mediaType.type == kTypeText) { - return Padding( - padding: kP10, - child: Column( - children: [ - switch (currentSeg) { - ResponseBodyView.preview || - ResponseBodyView.none => - Container(), - ResponseBodyView.code => Expanded( - child: Container( - width: double.maxFinite, - padding: kP8, - decoration: textContainerdecoration, - child: const HtmlPreviewer( - url: "https://fluttergems.dev/")), - ), - ResponseBodyView.raw => Container() - } - ], - ), - ); - } + final isHtmlResponse = widget.mediaType.type == kTypeText && + widget.mediaType.subtype == kSubTypeHtml; return Padding( padding: kP10, child: Column( children: [ - Row( - children: [ - (widget.options == kRawBodyViewOptions) - ? const SizedBox() - : SegmentedButton( - style: const ButtonStyle( - padding: MaterialStatePropertyAll( - EdgeInsets.symmetric( - horizontal: 8, - ), + !isHtmlResponse + ? Column( + children: [ + Row( + children: [ + (widget.options == kRawBodyViewOptions) + ? const SizedBox() + : SegmentedButton( + style: const ButtonStyle( + padding: MaterialStatePropertyAll( + EdgeInsets.symmetric( + horizontal: 8, + ), + ), + ), + selectedIcon: Icon(currentSeg.icon), + segments: widget.options + .map>( + (e) => + ButtonSegment( + value: e, + label: Text(e.label), + icon: Icon(e.icon), + ), + ) + .toList(), + selected: {currentSeg}, + onSelectionChanged: (newSelection) { + setState(() { + segmentIdx = widget.options + .indexOf(newSelection.first); + }); + }, + ), + const Spacer(), + kCodeRawBodyViewOptions.contains(currentSeg) + ? CopyButton( + toCopy: widget.formattedBody ?? widget.body, + showLabel: showLabel, + ) + : const SizedBox(), + SaveInDownloadsButton( + content: widget.bytes, + mimeType: widget.mediaType.mimeType, + showLabel: showLabel, ), - ), - selectedIcon: Icon(currentSeg.icon), - segments: widget.options - .map>( - (e) => ButtonSegment( - value: e, - label: Text(e.label), - icon: Icon(e.icon), - ), - ) - .toList(), - selected: {currentSeg}, - onSelectionChanged: (newSelection) { - setState(() { - segmentIdx = - widget.options.indexOf(newSelection.first); - }); - }, + ], ), - const Spacer(), - kCodeRawBodyViewOptions.contains(currentSeg) - ? CopyButton( - toCopy: widget.formattedBody ?? widget.body, - showLabel: showLabel, - ) - : const SizedBox(), - SaveInDownloadsButton( - content: widget.bytes, - mimeType: widget.mediaType.mimeType, - showLabel: showLabel, - ), - ], - ), - kVSpacer10, + kVSpacer10 + ], + ) + : Container(), switch (currentSeg) { ResponseBodyView.preview || ResponseBodyView.none => Expanded( child: Container( @@ -482,12 +477,14 @@ class _BodySuccessState extends State { width: double.maxFinite, padding: kP8, decoration: textContainerdecoration, - child: CodePreviewer( - code: widget.formattedBody ?? widget.body, - theme: codeTheme, - language: widget.highlightLanguage, - textStyle: kCodeStyle, - ), + child: !isHtmlResponse + ? CodePreviewer( + code: widget.formattedBody ?? widget.body, + theme: codeTheme, + language: widget.highlightLanguage, + textStyle: kCodeStyle, + ) + : HtmlPreviewer(url: url), ), ), ResponseBodyView.raw => Expanded(