diff --git a/lib/codegen/dart/pkg_http.dart b/lib/codegen/dart/pkg_http.dart index 11808a3f..69464912 100644 --- a/lib/codegen/dart/pkg_http.dart +++ b/lib/codegen/dart/pkg_http.dart @@ -1,3 +1,5 @@ +import 'dart:io'; +import 'dart:convert'; import 'package:jinja/jinja.dart' as jj; import 'package:apidash/consts.dart'; import 'package:apidash/models/models.dart' show RequestModel, rowsToMap; @@ -6,12 +8,14 @@ String kTemplateUrl = """import 'package:http/http.dart' as http; void main() async { var uri = Uri.parse('{{url}}'); + """; String kTemplateParams = """ var queryParams = {{params}}; """; +int kParamsPadding = 20; String kStringUrlParams = """ @@ -25,25 +29,37 @@ String kStringNoUrlParams = """ uri = uri.replace(queryParameters: queryParams); """; -String kTemplateHeaders = """ - - var headers = {{headers}}; -"""; - String kTemplateBody = """ String body = r'''{{body}}'''; + """; +String kBodyImportDartConvert = """ +import 'dart:convert'; +"""; + +String kBodyLength = """ + + var contentLength = utf8.encode(body).length; +"""; + +String kTemplateHeaders = """ + + var headers = {{headers}}; + +"""; +int kHeadersPadding = 16; + String kTemplateRequest = """ - response = await http.{{method}}(requestUrl"""; + final response = await http.{{method}}(uri"""; String kStringRequestHeaders = """, - headers: headers"""; + headers: headers"""; String kStringRequestBody = """, - body: body"""; + body: body"""; String kStringRequestEnd = """); """; @@ -67,6 +83,16 @@ String kStringResult = r""" } """; +String padMultilineString(String text, int padding, + {bool firstLinePadded = false}) { + var lines = kSplitter.convert(text); + int start = firstLinePadded ? 0 : 1; + for (start; start < lines.length; start++) { + lines[start] = ' ' * padding + lines[start]; + } + return lines.join("\n"); +} + String getDartHttpCode(RequestModel requestModel) { //try { String result = ""; @@ -85,7 +111,9 @@ String getDartHttpCode(RequestModel requestModel) { var params = rowsToMap(requestModel.requestParams) ?? {}; if (params.isNotEmpty) { var templateParams = jj.Template(kTemplateParams); - result += templateParams.render({"params": encoder.convert(params)}); + var paramsString = kEncoder.convert(params); + paramsString = padMultilineString(paramsString, kParamsPadding); + result += templateParams.render({"params": paramsString}); Uri uri = Uri.parse(url); if (uri.hasQuery) { result += kStringUrlParams; @@ -95,22 +123,40 @@ String getDartHttpCode(RequestModel requestModel) { } } - var headersList = requestModel.requestHeaders; - if (headersList != null) { - var headers = rowsToMap(requestModel.requestHeaders) ?? {}; - if (headers.isNotEmpty) { - hasHeaders = true; - var templateHeaders = jj.Template(kTemplateHeaders); - result += templateHeaders.render({"headers": encoder.convert(headers)}); + var method = requestModel.method; + if (kMethodsWithBody.contains(method) && requestModel.requestBody != null) { + var contentLength = utf8.encode(requestModel.requestBody).length; + if (contentLength > 0) { + hasBody = true; + var body = requestModel.requestBody; + var templateBody = jj.Template(kTemplateBody); + result += templateBody.render({"body": body}); + result = kBodyImportDartConvert + result; + result += kBodyLength; } } - var method = requestModel.method; - if (kMethodsWithBody.contains(method) && requestModel.requestBody != null) { - var body = requestModel.requestBody; - hasBody = true; - var templateBody = jj.Template(kTemplateBody); - result += templateBody.render({"body": body}); + var headersList = requestModel.requestHeaders; + if (headersList != null || hasBody) { + var headers = rowsToMap(requestModel.requestHeaders) ?? {}; + if (headers.isNotEmpty || hasBody) { + hasHeaders = true; + if (hasBody) { + headers[HttpHeaders.contentLengthHeader] = r"$contentLength"; + switch (requestModel.requestBodyContentType) { + case ContentType.json: + headers[HttpHeaders.contentTypeHeader] = 'application/json'; + break; + case ContentType.text: + headers[HttpHeaders.contentTypeHeader] = 'text/plain'; + break; + } + } + var headersString = kEncoder.convert(headers); + headersString = padMultilineString(headersString, kHeadersPadding); + var templateHeaders = jj.Template(kTemplateHeaders); + result += templateHeaders.render({"headers": headersString}); + } } var templateRequest = jj.Template(kTemplateRequest); diff --git a/lib/screens/home_page/editor_pane/details_card/code_pane/code_pane.dart b/lib/screens/home_page/editor_pane/details_card/code_pane/code_pane.dart index f46ca067..0520a74b 100644 --- a/lib/screens/home_page/editor_pane/details_card/code_pane/code_pane.dart +++ b/lib/screens/home_page/editor_pane/details_card/code_pane/code_pane.dart @@ -24,7 +24,6 @@ class _CodePaneState extends ConsumerState { final collection = ref.watch(collectionStateNotifierProvider); final idIdx = collection.indexWhere((m) => m.id == activeId); final requestModel = collection[idIdx]; - print("update"); var codeTheme = Theme.of(context).brightness == Brightness.light ? kLightCodeTheme : kDarkCodeTheme; @@ -39,17 +38,34 @@ class _CodePaneState extends ConsumerState { border: Border.all(color: Theme.of(context).colorScheme.surfaceVariant), borderRadius: kBorderRadius8, ); + + final code = getDartHttpCode(requestModel); return Padding( padding: kP10, child: Column( children: [ + SizedBox( + height: kHeaderHeight, + child: Row( + children: [ + Expanded( + child: Text( + "Code", + style: Theme.of(context).textTheme.titleMedium, + ), + ), + CopyButton(toCopy: code), + ], + ), + ), + kVSpacer10, Expanded( child: Container( width: double.maxFinite, padding: kP8, decoration: textContainerdecoration, - child: CodePreviewer( - code: getDartHttpCode(requestModel), + child: CodeGenPreviewer( + code: code, theme: codeTheme, language: 'dart', textStyle: kCodeStyle, diff --git a/lib/widgets/code_generator.dart b/lib/widgets/code_generator.dart deleted file mode 100644 index 7f1642b6..00000000 --- a/lib/widgets/code_generator.dart +++ /dev/null @@ -1,15 +0,0 @@ -import 'package:flutter/material.dart'; - -class CodeGenerator extends StatefulWidget { - const CodeGenerator({super.key}); - - @override - State createState() => _CodeGeneratorState(); -} - -class _CodeGeneratorState extends State { - @override - Widget build(BuildContext context) { - return Container(); - } -} diff --git a/lib/widgets/codegen_previewer.dart b/lib/widgets/codegen_previewer.dart new file mode 100644 index 00000000..489f60fe --- /dev/null +++ b/lib/widgets/codegen_previewer.dart @@ -0,0 +1,99 @@ +import 'package:flutter/material.dart'; +import 'package:highlighter/highlighter.dart' show highlight; +import 'code_previewer.dart' show convert; + +class CodeGenPreviewer extends StatefulWidget { + const CodeGenPreviewer({ + super.key, + required this.code, + required this.theme, + this.language, + this.textStyle, + this.padding = EdgeInsets.zero, + }); + + final String code; + final String? language; + final TextStyle? textStyle; + final EdgeInsetsGeometry padding; + final Map theme; + + @override + State createState() => _CodeGenPreviewerState(); +} + +class _CodeGenPreviewerState extends State { + static const _rootKey = 'root'; + static const _defaultFontColor = Color(0xff000000); + late TextStyle textStyle; + final ScrollController controllerH = ScrollController(); + final ScrollController controllerV = ScrollController(); + + @override + void dispose() { + controllerH.dispose(); + controllerV.dispose(); + super.dispose(); + } + + @override + void initState() { + super.initState(); + textStyle = TextStyle( + color: widget.theme[_rootKey]?.color ?? _defaultFontColor, + ); + if (widget.textStyle != null) { + textStyle = textStyle.merge(widget.textStyle); + } + } + + @override + Widget build(BuildContext context) { + final spans = generateSpans(widget.code, widget.language, widget.theme); + return Padding( + padding: widget.padding, + child: Scrollbar( + thickness: 10, + thumbVisibility: true, + controller: controllerV, + child: Scrollbar( + notificationPredicate: (notification) => notification.depth == 1, + thickness: 10, + thumbVisibility: true, + controller: controllerH, + child: SingleChildScrollView( + controller: controllerV, + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + controller: controllerH, + child: Column( + children: [ + Row( + children: [ + SelectionArea( + child: Text.rich( + TextSpan( + children: spans, + style: textStyle, + ), + softWrap: false, + ), + ), + ], + ), + ], + ), + ), + ), + ), + ), + ); + } +} + +List generateSpans( + String code, String? language, Map theme) { + var parsed = highlight.parse(code, language: language); + var spans = convert(parsed.nodes!, theme); + return spans; +} diff --git a/lib/widgets/widgets.dart b/lib/widgets/widgets.dart index a67a3176..0d621a21 100644 --- a/lib/widgets/widgets.dart +++ b/lib/widgets/widgets.dart @@ -1,6 +1,7 @@ export 'editor.dart'; -export 'code_previewer.dart'; export 'buttons.dart'; export 'tables.dart'; export 'previewer.dart'; +export 'code_previewer.dart'; +export 'codegen_previewer.dart'; export 'error_message.dart';