From a9810ef7c2ce9b0caa99dd479859d49e71315dfe Mon Sep 17 00:00:00 2001 From: Ankit Mahato Date: Sun, 5 Mar 2023 14:51:15 +0530 Subject: [PATCH] Fully functional prototype with final UI design --- analysis_options.yaml | 10 + lib/components/collection_pane.dart | 15 +- lib/components/editor_pane/body_editor.dart | 159 ++++++----- lib/components/editor_pane/details_card.dart | 5 +- lib/components/editor_pane/request_pane.dart | 52 ++-- lib/components/editor_pane/response_pane.dart | 270 +++++++++++++++++- lib/components/editor_pane/tables.dart | 153 +++++----- lib/components/editor_pane/url_card.dart | 40 ++- lib/components/styles.dart | 44 ++- lib/consts.dart | 70 +++++ lib/main.dart | 8 +- lib/screens/home_page.dart | 5 +- lib/services/http_service.dart | 2 +- lib/utils/utils.dart | 38 +++ pubspec.lock | 138 ++++++++- pubspec.yaml | 4 +- 16 files changed, 800 insertions(+), 213 deletions(-) diff --git a/analysis_options.yaml b/analysis_options.yaml index f9b30346..13bea7e3 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1 +1,11 @@ include: package:flutter_lints/flutter.yaml + +analyzer: + errors: + invalid_annotation_target: ignore + enable-experiment: + - records + +linter: + rules: + constant_identifier_names: false diff --git a/lib/components/collection_pane.dart b/lib/components/collection_pane.dart index 54f4ac55..a9d1e413 100644 --- a/lib/components/collection_pane.dart +++ b/lib/components/collection_pane.dart @@ -3,6 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../models/models.dart'; import '../providers/providers.dart'; import '../utils/utils.dart'; +import 'styles.dart'; import '../consts.dart'; class CollectionPane extends ConsumerStatefulWidget { @@ -38,7 +39,10 @@ class _CollectionPaneState extends ConsumerState { .read(activeItemIdStateProvider.notifier) .update((state) => newId); }, - child: const Text('+ New'), + child: const Text( + '+ New', + style: textStyleButton, + ), ), ], ), @@ -120,12 +124,9 @@ class RequestItem extends ConsumerStatefulWidget { } class _RequestItemState extends ConsumerState { - late Color _color; - @override void initState() { super.initState(); - _color = Colors.grey.shade50; } @override @@ -133,11 +134,11 @@ class _RequestItemState extends ConsumerState { final activeRequest = ref.watch(activeItemIdStateProvider); bool isActiveId = activeRequest == widget.id; return Material( - borderRadius: BorderRadius.circular(10.0), + borderRadius: borderRadius10, elevation: isActiveId ? 2 : 0, - color: isActiveId ? Colors.grey.shade300 : _color, + color: isActiveId ? colorGrey300 : colorGrey50, child: InkWell( - borderRadius: BorderRadius.circular(10.0), + borderRadius: borderRadius10, onTap: () { ref .read(activeItemIdStateProvider.notifier) diff --git a/lib/components/editor_pane/body_editor.dart b/lib/components/editor_pane/body_editor.dart index 43f4abb1..30ff6f9a 100644 --- a/lib/components/editor_pane/body_editor.dart +++ b/lib/components/editor_pane/body_editor.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../providers/providers.dart'; +import '../styles.dart'; import '../../consts.dart'; class EditRequestBody extends StatefulWidget { @@ -13,23 +14,92 @@ class EditRequestBody extends StatefulWidget { class _EditRequestBodyState extends State { @override Widget build(BuildContext context) { - return Column( - children: [ - Padding( - padding: const EdgeInsets.all(10), - child: Row( - children: [ - SizedBox( - child: DropdownButtonBodyContentType(), - height: 30, - ) - ], + return Container( + decoration: tableContainerDecoration, + margin: p5, + child: Column( + children: [ + Padding( + padding: p10, + child: Row( + children: const [ + Text( + "Select Content Type:", + //style: Theme.of(context).textTheme.titleMedium, + ), + SizedBox( + height: 30, + child: DropdownButtonBodyContentType(), + ), + ], + ), ), - ), - Expanded( - child: TextFieldEditor(), - ) - ], + const Divider(), + const Expanded( + child: Padding( + padding: p10, + child: TextFieldEditor(), + ), + ) + ], + ), + ); + } +} + +class DropdownButtonBodyContentType extends ConsumerStatefulWidget { + const DropdownButtonBodyContentType({ + super.key, + }); + + @override + ConsumerState createState() => + _DropdownButtonBodyContentTypeState(); +} + +class _DropdownButtonBodyContentTypeState + extends ConsumerState { + @override + Widget build(BuildContext context) { + final activeId = ref.watch(activeItemIdStateProvider); + final collection = ref.read(collectionStateNotifierProvider); + final idIdx = collection.indexWhere((m) => m.id == activeId); + final requestBodyContentType = ref.watch(collectionStateNotifierProvider + .select((value) => value[idIdx].requestBodyContentType)); + return DropdownButton( + focusColor: colorGrey50, + value: requestBodyContentType, + icon: const Icon( + Icons.unfold_more_rounded, + size: 16, + ), + elevation: 4, + style: codeStyle.copyWith( + color: Theme.of(context) + .colorScheme + .primary), //Theme.of(context).textTheme.bodyMedium, + underline: Container( + height: 0, + ), + onChanged: (ContentType? value) { + ref + .read(collectionStateNotifierProvider.notifier) + .update(activeId!, requestBodyContentType: value); + }, + borderRadius: borderRadius10, + items: ContentType.values + .map>((ContentType value) { + return DropdownMenuItem( + value: value, + child: Padding( + padding: const EdgeInsets.only(left: 8), + child: Text( + value.name, + style: textStyleButton, + ), + ), + ); + }).toList(), ); } } @@ -61,9 +131,10 @@ class _TextFieldEditorState extends ConsumerState { .read(collectionStateNotifierProvider.notifier) .update(activeId, requestBody: value); }, + style: codeStyle, decoration: InputDecoration( - hintText: "Enter body", - hintStyle: TextStyle(color: Colors.grey.shade500), + hintText: "Enter content (body)", + hintStyle: codeStyle.copyWith(color: colorGrey500), border: InputBorder.none, ), keyboardType: TextInputType.multiline, @@ -72,55 +143,3 @@ class _TextFieldEditorState extends ConsumerState { ); } } - -class DropdownButtonBodyContentType extends ConsumerStatefulWidget { - const DropdownButtonBodyContentType({ - super.key, - }); - - @override - ConsumerState createState() => - _DropdownButtonBodyContentTypeState(); -} - -class _DropdownButtonBodyContentTypeState - extends ConsumerState { - @override - Widget build(BuildContext context) { - final activeId = ref.watch(activeItemIdStateProvider); - final reqestModel = ref - .read(collectionStateNotifierProvider.notifier) - .getRequestModel(activeId!); - return DropdownButton( - focusColor: Colors.white, - value: reqestModel.requestBodyContentType, - icon: const Icon( - Icons.unfold_more_rounded, - size: 16, - ), - elevation: 4, - underline: Container( - height: 0, - //color: Colors.deepPurpleAccent, - ), - onChanged: (ContentType? value) { - ref - .read(collectionStateNotifierProvider.notifier) - .update(activeId!, requestBodyContentType: value); - }, - borderRadius: BorderRadius.circular(10), - items: ContentType.values - .map>((ContentType value) { - return DropdownMenuItem( - value: value, - child: Padding( - padding: const EdgeInsets.only(left: 8), - child: Text( - value.name, - ), - ), - ); - }).toList(), - ); - } -} diff --git a/lib/components/editor_pane/details_card.dart b/lib/components/editor_pane/details_card.dart index bbfe0aca..2da12e87 100644 --- a/lib/components/editor_pane/details_card.dart +++ b/lib/components/editor_pane/details_card.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:multi_split_view/multi_split_view.dart'; import 'request_pane.dart'; import 'response_pane.dart'; @@ -30,8 +29,8 @@ class _EditorPaneRequestDetailsCardState data: MultiSplitViewThemeData( dividerThickness: 3, dividerPainter: DividerPainters.background( - color: Colors.grey.shade200, - highlightedColor: Colors.grey.shade400, + color: colorGrey200, + highlightedColor: colorGrey400, animationEnabled: false, ), ), diff --git a/lib/components/editor_pane/request_pane.dart b/lib/components/editor_pane/request_pane.dart index 4e73d4c2..d77a906f 100644 --- a/lib/components/editor_pane/request_pane.dart +++ b/lib/components/editor_pane/request_pane.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:tab_container/tab_container.dart'; import '../../providers/providers.dart'; +import '../styles.dart'; import 'tables.dart'; import 'body_editor.dart'; @@ -28,28 +29,41 @@ class _EditRequestPaneState extends ConsumerState { .read(collectionStateNotifierProvider.notifier) .getRequestModel(activeId!) .requestTabIndex); - return Padding( - padding: const EdgeInsets.all(5), - child: TabContainer( - key: Key(activeId), - controller: _controller, - color: Colors.grey.shade100, - onEnd: () { - ref - .read(collectionStateNotifierProvider.notifier) - .update(activeId, requestTabIndex: _controller.index); - }, - tabs: const [ + return TabContainer( + key: Key(activeId), + controller: _controller, + color: colorGrey100, + onEnd: () { + ref + .read(collectionStateNotifierProvider.notifier) + .update(activeId, requestTabIndex: _controller.index); + }, + isStringTabs: false, + tabs: const [ + Text( 'URL Params', + textAlign: TextAlign.center, + overflow: TextOverflow.fade, + style: textStyleButton, + ), + Text( 'Headers', + textAlign: TextAlign.center, + overflow: TextOverflow.fade, + style: textStyleButton, + ), + Text( 'Body', - ], - children: const [ - EditRequestURLParams(), - EditRequestHeaders(), - EditRequestBody(), - ], - ), + textAlign: TextAlign.center, + overflow: TextOverflow.fade, + style: textStyleButton, + ) + ], + children: const [ + EditRequestURLParams(), + EditRequestHeaders(), + EditRequestBody(), + ], ); } diff --git a/lib/components/editor_pane/response_pane.dart b/lib/components/editor_pane/response_pane.dart index a6bcb8f9..12a0842b 100644 --- a/lib/components/editor_pane/response_pane.dart +++ b/lib/components/editor_pane/response_pane.dart @@ -1,5 +1,13 @@ +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 '../../providers/providers.dart'; +import '../../models/request_model.dart'; +import '../styles.dart'; +import "../../utils/utils.dart"; +import "../../consts.dart"; class ResponsePane extends ConsumerStatefulWidget { const ResponsePane({super.key}); @@ -16,12 +24,28 @@ class _ResponsePaneState extends ConsumerState { @override Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.all(5), - child: Column( - children: [], - ), - ); + final activeId = ref.watch(activeItemIdStateProvider); + final collection = ref.read(collectionStateNotifierProvider); + final idIdx = collection.indexWhere((m) => m.id == activeId); + final status = ref.watch(collectionStateNotifierProvider + .select((value) => (value[idIdx].responseStatus, + value[idIdx].message, + value[idIdx].responseModel))); + if (status.$0 == null) { + return const NotSentWidget(); + } + if (status.$0 == -1) { + return ErrorMessage(message: status.$1); + } + else{ + //var responseModel = ref + // .read(collectionStateNotifierProvider.notifier) + // .getRequestModel(activeId!) + // .responseModel; + return ResponseViewer(statusCode: status.$0!, + message: status.$1, + responseModel: status.$2!); + } } @override @@ -29,3 +53,237 @@ class _ResponsePaneState extends ConsumerState { super.dispose(); } } + +class NotSentWidget extends StatelessWidget { + const NotSentWidget({super.key}); + + @override + Widget build(BuildContext context) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.north_east_rounded, + size: 40, + color: colorErrorMsg, + ), + Text( + 'Not Sent', + style: Theme.of(context) + .textTheme + .titleMedium + ?.copyWith(color: colorErrorMsg), + ), + ], + ), + ); + } +} + +class ErrorMessage extends StatelessWidget { + const ErrorMessage({super.key, required this.message}); + + final String? message; + + @override + Widget build(BuildContext context) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.warning_rounded, + size: 40, + color: colorErrorMsg, + ), + Text( + message ?? 'And error occurred.', + style: Theme.of(context) + .textTheme + .titleMedium + ?.copyWith(color: colorErrorMsg), + ), + ], + ), + ); + } +} + +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 StatelessWidget { + final int statusCode; + final String? message; + final ResponseModel responseModel; + + const ResponseViewer({ + 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, + ), + ], + ), + 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), + ], + ), + ), + ); + } +} diff --git a/lib/components/editor_pane/tables.dart b/lib/components/editor_pane/tables.dart index 5ab32ce8..5136f175 100644 --- a/lib/components/editor_pane/tables.dart +++ b/lib/components/editor_pane/tables.dart @@ -3,6 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:davi/davi.dart'; import '../../models/models.dart'; import '../../providers/providers.dart'; +import '../styles.dart'; class EditRequestURLParams extends ConsumerStatefulWidget { const EditRequestURLParams({Key? key}) : super(key: key); @@ -26,7 +27,11 @@ class EditRequestURLParamsState extends ConsumerState { return TextFormField( key: Key("$activeId-$idx-params-k"), initialValue: rows[idx].k, - decoration: const InputDecoration(hintText: "Add URL Parameter"), + style: codeStyle, + decoration: InputDecoration( + hintStyle: codeStyle, + hintText: "Add URL Parameter", + ), onChanged: (value) { rows[idx] = rows[idx].copyWith(k: value); _onFieldChange(activeId!); @@ -39,7 +44,11 @@ class EditRequestURLParamsState extends ConsumerState { return TextFormField( key: Key("$activeId-$idx-params-v"), initialValue: rows[idx].v, - decoration: const InputDecoration(hintText: "Add Value"), + style: codeStyle, + decoration: InputDecoration( + hintStyle: codeStyle, + hintText: "Add Value", + ), onChanged: (value) { rows[idx] = rows[idx].copyWith(v: value); _onFieldChange(activeId!); @@ -75,44 +84,36 @@ class EditRequestURLParamsState extends ConsumerState { ), ], ); - return Column( - children: [ - Padding( - padding: const EdgeInsets.all(10), - child: Row( - children: [ - ElevatedButton( - onPressed: () { - rows.add(const KVRow("", "")); - model.addRow(const KVRow("", "")); - }, - child: const Text("+ Add Param"), - ), - ], - ), - ), - Expanded( - child: DaviTheme( - data: DaviThemeData( - columnDividerThickness: 1, - columnDividerColor: Colors.grey.shade100, - row: RowThemeData(dividerColor: Colors.grey.shade100), - decoration: const BoxDecoration( - border: Border(), - ), - header: HeaderThemeData( - color: Colors.grey.shade100, - columnDividerColor: Colors.grey.shade100, - bottomBorderHeight: 1, - ), - headerCell: const HeaderCellThemeData( - alignment: Alignment.center, - ), + return Container( + decoration: tableContainerDecoration, + margin: p5, + child: Column( + children: [ + Padding( + padding: p10, + child: Row( + children: [ + ElevatedButton( + onPressed: () { + rows.add(const KVRow("", "")); + model.addRow(const KVRow("", "")); + }, + child: const Text( + "+ Add Param", + style: textStyleButton, + ), + ), + ], ), - child: Davi(model), ), - ), - ], + Expanded( + child: DaviTheme( + data: tableThemeData, + child: Davi(model), + ), + ), + ], + ), ); } } @@ -138,7 +139,11 @@ class EditRequestHeadersState extends ConsumerState { return TextFormField( key: Key("$activeId-$idx-headers-k"), initialValue: rows[idx].k, - decoration: const InputDecoration(hintText: "Add Header Name"), + style: codeStyle, + decoration: InputDecoration( + hintStyle: codeStyle, + hintText: "Add Header Name", + ), onChanged: (value) { rows[idx] = rows[idx].copyWith(k: value); _onFieldChange(activeId!); @@ -151,7 +156,11 @@ class EditRequestHeadersState extends ConsumerState { return TextFormField( key: Key("$activeId-$idx-headers-v"), initialValue: rows[idx].v, - decoration: const InputDecoration(hintText: "Add Header Value"), + style: codeStyle, + decoration: InputDecoration( + hintStyle: codeStyle, + hintText: "Add Header Value", + ), onChanged: (value) { rows[idx] = rows[idx].copyWith(v: value); _onFieldChange(activeId!); @@ -187,44 +196,36 @@ class EditRequestHeadersState extends ConsumerState { ), ], ); - return Column( - children: [ - Padding( - padding: const EdgeInsets.all(10), - child: Row( - children: [ - ElevatedButton( - onPressed: () { - rows.add(const KVRow("", "")); - model.addRow(const KVRow("", "")); - }, - child: const Text("+ Add Header"), - ), - ], - ), - ), - Expanded( - child: DaviTheme( - data: DaviThemeData( - columnDividerThickness: 1, - columnDividerColor: Colors.grey.shade100, - row: RowThemeData(dividerColor: Colors.grey.shade100), - decoration: const BoxDecoration( - border: Border(), - ), - header: HeaderThemeData( - color: Colors.grey.shade100, - columnDividerColor: Colors.grey.shade100, - bottomBorderHeight: 1, - ), - headerCell: const HeaderCellThemeData( - alignment: Alignment.center, - ), + return Container( + decoration: tableContainerDecoration, + margin: p5, + child: Column( + children: [ + Padding( + padding: p10, + child: Row( + children: [ + ElevatedButton( + onPressed: () { + rows.add(const KVRow("", "")); + model.addRow(const KVRow("", "")); + }, + child: const Text( + "+ Add Header", + style: textStyleButton, + ), + ), + ], ), - child: Davi(model), ), - ), - ], + Expanded( + child: DaviTheme( + data: tableThemeData, + child: Davi(model), + ), + ), + ], + ), ); } } diff --git a/lib/components/editor_pane/url_card.dart b/lib/components/editor_pane/url_card.dart index 668be3df..4e07bccf 100644 --- a/lib/components/editor_pane/url_card.dart +++ b/lib/components/editor_pane/url_card.dart @@ -26,23 +26,23 @@ class _EditorPaneRequestURLCardState return Card( shape: cardShape, child: Padding( - padding: EdgeInsets.symmetric( + padding: const EdgeInsets.symmetric( vertical: 5, horizontal: 20, ), child: Row( children: [ - DropdownButtonHTTPMethod(), + const DropdownButtonHTTPMethod(), const SizedBox( width: 20, ), - Expanded( + const Expanded( child: URLTextField(), ), const SizedBox( width: 20, ), - Container( + SizedBox( height: 36, child: ElevatedButton( onPressed: () async { @@ -51,8 +51,11 @@ class _EditorPaneRequestURLCardState .sendRequest(activeId!); }, child: Row( - children: [ - Text("Send"), + children: const [ + Text( + "Send", + style: textStyleButton, + ), SizedBox( width: 10, ), @@ -80,14 +83,9 @@ class DropdownButtonHTTPMethod extends ConsumerStatefulWidget { class _DropdownButtonHTTPMethodState extends ConsumerState { - //late HTTPVerb dropdownValue; - @override void initState() { super.initState(); - //dropdownValue = ref - // .read(collectionStateNotifierProvider.notifier) - // .idxOfId(String id); } @override @@ -97,34 +95,31 @@ class _DropdownButtonHTTPMethodState final idIdx = collection.indexWhere((m) => m.id == activeId); final method = ref.watch( collectionStateNotifierProvider.select((value) => value[idIdx].method)); - //final model = ref - // .read(collectionStateNotifierProvider.notifier) - // .getRequestModel(activeId!); return DropdownButton( - focusColor: Colors.white, - //value: collection[idIdx].method, - value: method, //model.method, + focusColor: colorBg, + value: method, icon: const Icon(Icons.unfold_more_rounded), elevation: 4, underline: Container( height: 0, - //color: Colors.deepPurpleAccent, ), onChanged: (HTTPVerb? value) { ref .read(collectionStateNotifierProvider.notifier) .update(activeId!, method: value); }, - borderRadius: BorderRadius.circular(10), + borderRadius: borderRadius10, items: HTTPVerb.values.map>((HTTPVerb value) { return DropdownMenuItem( - //alignment: AlignmentDirectional.center, value: value, child: Padding( padding: const EdgeInsets.only(left: 16), child: Text( value.name.toUpperCase(), - style: TextStyle(color: getHTTPMethodColor(value)), + style: codeStyle.copyWith( + fontWeight: FontWeight.bold, + color: getHTTPMethodColor(value), + ), ), ), ); @@ -157,9 +152,10 @@ class _URLTextFieldState extends ConsumerState { .read(collectionStateNotifierProvider.notifier) .getRequestModel(activeId!) .url, + style: codeStyle, decoration: InputDecoration( hintText: "Enter API endpoint like api.foss42.com/country/codes", - hintStyle: TextStyle(color: Colors.grey.shade500), + hintStyle: codeStyle.copyWith(color: colorGrey500), border: InputBorder.none, ), onChanged: (value) { diff --git a/lib/components/styles.dart b/lib/components/styles.dart index f389b3d8..96048514 100644 --- a/lib/components/styles.dart +++ b/lib/components/styles.dart @@ -1,11 +1,51 @@ import 'package:flutter/material.dart'; +import 'package:davi/davi.dart'; +import 'package:google_fonts/google_fonts.dart'; +final codeStyle = GoogleFonts.sourceCodePro(); + +const textStyleButton = TextStyle(fontWeight: FontWeight.bold); + +const colorBg = Colors.white; +final colorGrey50 = Colors.grey.shade50; +final colorGrey100 = Colors.grey.shade100; +final colorGrey200 = Colors.grey.shade200; +final colorGrey300 = Colors.grey.shade300; +final colorGrey400 = Colors.grey.shade400; +final colorGrey500 = Colors.grey.shade500; +final colorErrorMsg = colorGrey500; + +final borderRadius10 = BorderRadius.circular(10); final cardShape = RoundedRectangleBorder( side: const BorderSide( color: Colors.white70, width: 1, ), - borderRadius: BorderRadius.circular(10), + borderRadius: borderRadius10, ); -final colorErrorMsg = Colors.grey.shade500; +final tableContainerDecoration = BoxDecoration( + color: colorBg, + borderRadius: borderRadius10, +); +final tableThemeData = DaviThemeData( + columnDividerThickness: 1, + columnDividerColor: colorGrey100, + row: RowThemeData(dividerColor: colorGrey100), + decoration: const BoxDecoration( + border: Border(), + ), + header: HeaderThemeData( + color: colorGrey50, + columnDividerColor: colorGrey100, + bottomBorderHeight: 1, + bottomBorderColor: colorGrey100, + ), + headerCell: const HeaderCellThemeData( + alignment: Alignment.center, + textStyle: null, + ), +); + +const p5 = EdgeInsets.all(5); +const p10 = EdgeInsets.all(10); diff --git a/lib/consts.dart b/lib/consts.dart index 3b6425c9..2524e950 100644 --- a/lib/consts.dart +++ b/lib/consts.dart @@ -4,3 +4,73 @@ enum ContentType { json, text } const DEFAULT_METHOD = HTTPVerb.get; const DEFAULT_BODY_CONTENT_TYPE = ContentType.json; +const JSON_MIMETYPE = 'application/json'; + +const RESPONSE_CODE_REASONS = { + // 100s + 100: 'Continue', + 101: 'Switching Protocols', + // 200s + 200: 'OK', + 201: 'Created', + 202: 'Accepted', + 203: 'Non-Authoritative Information', + 204: 'No Content', + 205: 'Reset Content', + 206: 'Partial Content', + 207: 'Multi-Status', + 208: 'Already Reported', + 226: 'IM Used', + // 300s + 300: 'Multiple Choices', + 301: 'Moved Permanently', + 302: 'Found', + 303: 'See Other', + 304: 'Not Modified', + 305: 'Use Proxy', + 306: 'Switch Proxy', + 307: 'Temporary Redirect', + 308: 'Permanent Redirect', + // 400s + 400: 'Bad Request', + 401: 'Unauthorized', + 402: 'Payment Required', + 403: 'Forbidden', + 404: 'Not Found', + 405: 'Method Not Allowed', + 406: 'Not Acceptable', + 407: 'Proxy Authentication Required', + 408: 'Request Timeout', + 409: 'Conflict', + 410: 'Gone', + 411: 'Length Required', + 412: 'Precondition Failed', + 413: 'Payload Too Large', + 414: 'URI Too Long', + 415: 'Unsupported Media Type', + 416: 'Range Not Satisfiable', + 417: 'Expectation Failed', + 418: "I'm a Teapot", + 421: 'Misdirected Request', + 422: 'Unprocessable Entity', + 423: 'Locked', + 424: 'Failed Dependency', + 425: 'Too Early', + 426: 'Upgrade Required', + 428: 'Precondition Required', + 429: 'Too Many Requests', + 431: 'Request Header Fields Too Large', + 451: 'Unavailable For Legal Reasons', + // 500s + 500: 'Internal Server Error', + 501: 'Not Implemented', + 502: 'Bad Gateway', + 503: 'Service Unavailable', + 504: 'Gateway Timeout', + 505: 'HTTP Version Not Supported', + 506: 'Variant Also Negotiates', + 507: 'Insufficient Storage', + 508: 'Loop Detected', + 510: 'Not Extended', + 511: 'Network Authentication Required', +}; diff --git a/lib/main.dart b/lib/main.dart index b6158e6d..30c97c4b 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -2,13 +2,14 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:desktop_window/desktop_window.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:google_fonts/google_fonts.dart'; import 'screens/screens.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); if (!kIsWeb) { - await DesktopWindow.setWindowSize(Size(1400, 800)); - await DesktopWindow.setMinWindowSize(Size(1200, 800)); + await DesktopWindow.setWindowSize(const Size(1400, 800)); + await DesktopWindow.setMinWindowSize(const Size(1200, 800)); } runApp( const ProviderScope( @@ -25,7 +26,8 @@ class App extends StatelessWidget { return MaterialApp( debugShowCheckedModeBanner: false, theme: ThemeData( - primarySwatch: Colors.blue, + fontFamily: GoogleFonts.openSans().fontFamily, + primarySwatch: Colors.indigo, ), home: const HomePage(), ); diff --git a/lib/screens/home_page.dart b/lib/screens/home_page.dart index d6af7bf9..6029a6d5 100644 --- a/lib/screens/home_page.dart +++ b/lib/screens/home_page.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:multi_split_view/multi_split_view.dart'; import '../components/components.dart'; +import '../components/styles.dart'; class HomePage extends StatefulWidget { const HomePage({Key? key}) : super(key: key); @@ -32,8 +33,8 @@ class HomePageState extends State { data: MultiSplitViewThemeData( dividerThickness: 4, dividerPainter: DividerPainters.background( - color: Colors.grey.shade200, - highlightedColor: Colors.grey.shade400, + color: colorGrey200, + highlightedColor: colorGrey400, animationEnabled: false, ), ), diff --git a/lib/services/http_service.dart b/lib/services/http_service.dart index ca71c2e6..469d7a76 100644 --- a/lib/services/http_service.dart +++ b/lib/services/http_service.dart @@ -1,10 +1,10 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; -import 'package:flutter_api_tool/consts.dart'; import 'package:http/http.dart' as http; import 'package:collection/collection.dart' show mergeMaps; import '../models/models.dart'; +import '../../consts.dart'; const SUPPORTED_URI_SCHEMES = [ "https", diff --git a/lib/utils/utils.dart b/lib/utils/utils.dart index 0a41df62..7a72698b 100644 --- a/lib/utils/utils.dart +++ b/lib/utils/utils.dart @@ -1,6 +1,25 @@ import 'package:flutter/material.dart'; import '../consts.dart'; +Color? getResponseStatusCodeColor(int? statusCode) { + if (statusCode == null) { + return Colors.grey.shade700; + } + if (statusCode >= 500) { + return Colors.amber.shade900; + } + if (statusCode >= 400) { + return Colors.red.shade800; + } + if (statusCode >= 300) { + return Colors.blue.shade800; + } + if (statusCode >= 200) { + return Colors.green.shade800; + } + return Colors.grey.shade700; +} + Color getHTTPMethodColor(HTTPVerb method) { Color col; switch (method) { @@ -39,3 +58,22 @@ String getRequestTitleFromUrl(String? url) { } return url; } + +String humanizeDuration(Duration? duration) { + if (duration == null) { + return ""; + } + if (duration.inMinutes >= 1) { + var min = duration.inMinutes; + var secs = duration.inSeconds.remainder(60); + return "$min.$secs m"; + } + if (duration.inSeconds >= 1) { + var secs = duration.inSeconds; + var mili = duration.inMilliseconds.remainder(1000) ~/ 10; + return "$secs.$mili s"; + } else { + var mili = duration.inMilliseconds.remainder(1000); + return "$mili ms"; + } +} diff --git a/pubspec.lock b/pubspec.lock index 83e2ab52..584b2f11 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -42,7 +42,7 @@ packages: source: hosted version: "1.1.1" collection: - dependency: transitive + dependency: "direct main" description: name: collection sha256: cfc915e6923fe5ce6e153b0723c753045de46de1b4d63771530504004a45fae0 @@ -81,11 +81,35 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.1" + ffi: + dependency: transitive + description: + name: ffi + sha256: a38574032c5f1dd06c4aee541789906c12ccaab8ba01446e800d9c5b79c4a978 + url: "https://pub.dev" + source: hosted + version: "2.0.1" + file: + dependency: transitive + description: + name: file + sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d" + url: "https://pub.dev" + source: hosted + version: "6.1.4" flutter: dependency: "direct main" description: flutter source: sdk version: "0.0.0" + flutter_json_view: + dependency: "direct main" + description: + name: flutter_json_view + sha256: "4adc84e96c8bbd1ee874c5b8a3f6a404edccab9757a12edcde4c45c382b95214" + url: "https://pub.dev" + source: hosted + version: "1.0.0" flutter_lints: dependency: "direct dev" description: @@ -107,6 +131,30 @@ packages: description: flutter source: sdk version: "0.0.0" + google_fonts: + dependency: "direct main" + description: + name: google_fonts + sha256: "927573f2e8a8d65c17931e21918ad0ab0666b1b636537de7c4932bdb487b190f" + url: "https://pub.dev" + source: hosted + version: "4.0.3" + http: + dependency: "direct main" + description: + name: http + sha256: "6aa2946395183537c8b880962d935877325d6a09a2867c3970c05c0fed6ac482" + url: "https://pub.dev" + source: hosted + version: "0.13.5" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + url: "https://pub.dev" + source: hosted + version: "4.0.2" js: dependency: transitive description: @@ -163,6 +211,78 @@ packages: url: "https://pub.dev" source: hosted version: "1.8.2" + path_provider: + dependency: transitive + description: + name: path_provider + sha256: "04890b994ee89bfa80bf3080bfec40d5a92c5c7a785ebb02c13084a099d2b6f9" + url: "https://pub.dev" + source: hosted + version: "2.0.13" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: "7623b7d4be0f0f7d9a8b5ee6879fc13e4522d4c875ab86801dee4af32b54b83e" + url: "https://pub.dev" + source: hosted + version: "2.0.23" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: eec003594f19fe2456ea965ae36b3fc967bc5005f508890aafe31fa75e41d972 + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: "525ad5e07622d19447ad740b1ed5070031f7a5437f44355ae915ff56e986429a" + url: "https://pub.dev" + source: hosted + version: "2.1.9" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "57585299a729335f1298b43245842678cb9f43a6310351b18fb577d6e33165ec" + url: "https://pub.dev" + source: hosted + version: "2.0.6" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: "642ddf65fde5404f83267e8459ddb4556316d3ee6d511ed193357e25caa3632d" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + platform: + dependency: transitive + description: + name: platform + sha256: "4a451831508d7d6ca779f7ac6e212b4023dd5a7d08a27a63da33756410e32b76" + url: "https://pub.dev" + source: hosted + version: "3.1.0" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "6a2128648c854906c53fa8e33986fc0247a1116122f9534dd20e3ab9e16a32bc" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + process: + dependency: transitive + description: + name: process + sha256: "53fd8db9cec1d37b0574e12f07520d582019cb6c44abf5479a01505099a34a09" + url: "https://pub.dev" + source: hosted + version: "4.2.4" riverpod: dependency: transitive description: @@ -264,6 +384,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + win32: + dependency: transitive + description: + name: win32 + sha256: c9ebe7ee4ab0c2194e65d3a07d8c54c5d00bb001b76081c4a04cdb8448b59e46 + url: "https://pub.dev" + source: hosted + version: "3.1.3" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: ee1505df1426458f7f60aac270645098d318a8b4766d85fde75f76f2e21807d1 + url: "https://pub.dev" + source: hosted + version: "1.0.0" sdks: dart: ">=2.19.2 <3.0.0" flutter: ">=3.0.0" diff --git a/pubspec.yaml b/pubspec.yaml index 8b9de4c5..9c50d205 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -17,7 +17,9 @@ dependencies: davi: ^3.2.0 http: ^0.13.5 collection: ^1.17.0 - + flutter_json_view: ^1.0.0 + google_fonts: ^4.0.3 + dev_dependencies: flutter_test: sdk: flutter