diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a975ee6..ef684ccd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,21 @@ # API Dash ⚡️ Changelog -## v0.4.0 [WIP] +## v0.5.0 [WIP] + +In this release, we have added the following features: + +1. Create workspace (directory) to persist your entire data locally in the provided file-system location +2. Environment varibles +3. GraphQL support +4. Import requests from cURL & Postman Collection (v2.1) +5. History of your requests & response +6. Support for Request cancellation +7. Disable SSL verification +8. Codegen for Swift, hyper, etc. + +A big shout-out to all the contributors 🎉 + +## v0.4.0 [iOS Release] In this release, we have added the following features: @@ -15,7 +30,7 @@ In this release, we have added the following features: 9. Dart http codegen has been rewritten using dart code builder 10. Ability to override request contenttype 11. More header suggestions - +12. Mobile platform support ## v0.3.0 [29-11-2023] diff --git a/README.md b/README.md index bcb6d23e..e5070a32 100644 --- a/README.md +++ b/README.md @@ -76,11 +76,31 @@ API Dash can be downloaded from the links below: ## List of Features +| API Type | Supported | +| --- | --- | +| HTTP | ✅ | +| GraphQL | ✅ | +| SSE | https://github.com/foss42/apidash/issues/116 | +| WebSocket | https://github.com/foss42/apidash/issues/15 | +| MQTT | https://github.com/foss42/apidash/issues/115 | +| gRPC | https://github.com/foss42/apidash/issues/14 | + +| Import Collection From | Supported | +| --- | --- | +| Postman | ✅ | +| cURL | ✅ | +| Insomnia | https://github.com/foss42/apidash/issues/125 | +| OpenAPI | https://github.com/foss42/apidash/issues/121 | +| hurl | https://github.com/foss42/apidash/issues/123 | +| HAR | https://github.com/foss42/apidash/issues/122 | + + **↗️ Create & Customize API Requests** - Create different types of HTTP requests (`GET`, `HEAD`, `POST`, `PATCH`, `PUT` and `DELETE`). - Easily manipulate and play around with request inputs like `headers`, `query parameters` and `body`. - Full support to send text content with 🥳 Unicode/Emoji and preview any API response containing Unicode/Emoji. +- Create GraphQL requests with `headers` and `query`. **💼 Organize Requests in Collections & Folders** @@ -222,7 +242,7 @@ Here is the complete list of MIME types that are syntax highlighted in API Dash: | `text/javascript` | `.js` | | | `text/markdown` | `.md` | | -## What's new in v0.3.0? +## What's new in v0.5.0? Visit [CHANGELOG.md](CHANGELOG.md) diff --git a/ROADMAP.md b/ROADMAP.md index 04dfeb08..f18b4757 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -5,14 +5,14 @@ - [x] Remaining Code Generators (https://github.com/foss42/apidash/discussions/80) - [x] Environment Variables (https://github.com/foss42/apidash/issues/25) - [x] Integration Testing (https://github.com/foss42/apidash/issues/119) -- [ ] Paste not working on iOS and desktop (Right Click -> Paste) (https://github.com/foss42/apidash/issues/490) +- [x] Paste not working on iOS and desktop (Right Click -> Paste) (https://github.com/foss42/apidash/issues/490) - [ ] WebSocket support (https://github.com/foss42/apidash/issues/15) - [ ] SSE support (https://github.com/foss42/apidash/issues/116) - [ ] MQTT support (https://github.com/foss42/apidash/issues/115) -- [ ] GraphQL support (https://github.com/foss42/apidash/issues/117) +- [x] GraphQL support (https://github.com/foss42/apidash/issues/117) - [ ] gRPC support (https://github.com/foss42/apidash/issues/14) - [ ] Figuring out how to build for various Linux packaging formats (https://github.com/foss42/apidash/discussions/240) [Docs] -- [ ] Save items in Response headers/body in an Environment variable to be used by other requests (https://github.com/foss42/apidash/issues/465) +- [ ] Save items in Response headers/body in an Environment variable to be used by other requests. Something like a post-processing script. (https://github.com/foss42/apidash/issues/465) - [ ] Importers - [ ] OpenAPI (https://github.com/foss42/apidash/issues/121) - [ ] Insomnia (https://github.com/foss42/apidash/issues/125) diff --git a/doc/user_guide/README.md b/doc/user_guide/README.md index a22842ae..7d0795af 100644 --- a/doc/user_guide/README.md +++ b/doc/user_guide/README.md @@ -1,8 +1,8 @@ -## API Dash User Guide +# API Dash User Guide - [History of Requests](https://github.com/foss42/apidash/blob/main/doc/user_guide/his_user_guide.md) - [Environment Variables Manager](https://github.com/foss42/apidash/blob/main/doc/user_guide/env_user_guide.md) - [How to Disable SSL for Requests](https://github.com/foss42/apidash/blob/main/doc/user_guide/disable_ssl.md) -- Import Request for cURL (Doc TODO) -- How to use Code Generator (Doc TODO) +- Import Request for cURL (Contributions Welcome!) +- [How to Run Generated Code for a Programming Language](https://github.com/foss42/apidash/blob/main/doc/user_guide/instructions_to_run_generated_code.md) (Contributions Welcome!) - [API Dash on Mobile](https://github.com/foss42/apidash/blob/main/doc/user_guide/req_user_guide.md) diff --git a/doc/user_guide/instructions_to_run_generated_code.md b/doc/user_guide/instructions_to_run_generated_code.md new file mode 100644 index 00000000..ce5c9589 --- /dev/null +++ b/doc/user_guide/instructions_to_run_generated_code.md @@ -0,0 +1,150 @@ +# How to Run Generated Code for a Programming Language + +Choose your programming language/library from the list provided below to learn more how you can execute them: +- [cURL](#curl) +- [C (libcurl)](#c-libcurl) +- [C# (HttpClient)](#c-httpclient) +- [C# (RestSharp)](#c-restsharp) +- [Dart (http)](#dart-http) +- [Dart (dio)](#dart-dio) +- [Go (net/http)](#go-nethttp) +- [JavaScript (axios)](#javascript-axios) +- [JavaScript (fetch)](#javascript-fetch) +- [node.js (JavaScript, axios)](#nodejs-javascript-axios) +- [node.js (JavaScript, fetch)](#nodejs-javascript-fetch) +- [Java (asynchttpclient)](#java-asynchttpclient) +- [Java (HttpClient)](#java-httpclient) +- [Java (okhttp3)](#java-okhttp3) +- [Java (Unirest)](#java-unirest) +- [Julia (HTTP)](#julia-http) +- [Kotlin (okhttp3)](#kotlin-okhttp3) +- [PHP (curl)](#php-curl) +- [PHP (guzzle)](#php-guzzle) +- [PHP (HTTPlug)](#php-httplug) +- [Python (requests)](#python-requests) +- [Python (http.client)](#python-httpclient) +- [Ruby (faraday)](#ruby-faraday) +- [Ruby (net/http)](#ruby-nethttp) +- [Rust (hyper)](#rust-hyper) +- [Rust (reqwest)](#rust-reqwest) +- [Rust (ureq)](#rust-ureq) +- [Rust (Actix Client)](#rust-actix-client) +- [Swift](#swift) + +**Please raise a GitHub issue in case any instruction is not clear or if it is not working.** + +## cURL + +TODO + +## C (libcurl) + +TODO + +## C# (HttpClient) + +TODO + +## C# (RestSharp) + +TODO + +## Dart (http) + +TODO + +## Dart (dio) + +TODO + +## Go (net/http) + +TODO + +## JavaScript (axios) + +TODO + +## JavaScript (fetch) + +TODO + +## node.js (JavaScript, axios) + +TODO + +## node.js (JavaScript, fetch) + +TODO + +## Java (asynchttpclient) + +TODO + +## Java (HttpClient) + +TODO + +## Java (okhttp3) + +TODO + +## Java (Unirest) + +TODO + +## Julia (HTTP) + +TODO + +## Kotlin (okhttp3) + +TODO + +## PHP (curl) + +TODO + +## PHP (guzzle) + +TODO + +## PHP (HTTPlug) + +TODO + +## Python (requests) + +TODO + +## Python (http.client) + +TODO + +## Ruby (faraday) + +TODO + +## Ruby (net/http) + +TODO + +## Rust (hyper) + +TODO + +## Rust (reqwest) + +TODO + +## Rust (ureq) + +TODO + +## Rust (Actix Client) + +TODO + +## Swift + +TODO diff --git a/integration_test/env_helper.dart b/integration_test/env_helper.dart index c26775a7..6f116674 100644 --- a/integration_test/env_helper.dart +++ b/integration_test/env_helper.dart @@ -26,7 +26,7 @@ class ApidashTestEnvHelper { Future renameNewEnvironment(String newEnvName) async { Finder envItems = find.byType(EnvironmentItem); Finder newEnvItem = envItems.at(1); - expect(find.descendant(of: newEnvItem, matching: find.text("untitled")), + expect(find.descendant(of: newEnvItem, matching: find.text(kUntitled)), findsOneWidget); Finder itemCardMenu = find.descendant(of: newEnvItem, matching: find.byType(ItemCardMenu)); diff --git a/integration_test/req_helper.dart b/integration_test/req_helper.dart index 8697c1bc..4a18745a 100644 --- a/integration_test/req_helper.dart +++ b/integration_test/req_helper.dart @@ -29,7 +29,7 @@ class ApidashTestRequestHelper { Future renameNewRequest(String newReqName) async { Finder reqItems = find.byType(RequestItem); Finder newReqItem = reqItems.at(0); - expect(find.descendant(of: newReqItem, matching: find.text("untitled")), + expect(find.descendant(of: newReqItem, matching: find.text(kUntitled)), findsOneWidget); Finder itemCardMenu = find.descendant(of: newReqItem, matching: find.byType(ItemCardMenu)); diff --git a/lib/consts.dart b/lib/consts.dart index ec042f85..50401b29 100644 --- a/lib/consts.dart +++ b/lib/consts.dart @@ -432,6 +432,7 @@ const kLabelSelect = "Select"; const kLabelContinue = "Continue"; const kLabelCancel = "Cancel"; const kLabelOk = "Ok"; +const kUntitled = "untitled"; // Request Pane const kLabelRequest = "Request"; const kLabelHideCode = "Hide Code"; @@ -439,6 +440,7 @@ const kLabelViewCode = "View Code"; const kLabelURLParams = "URL Params"; const kLabelHeaders = "Headers"; const kLabelBody = "Body"; +const kLabelQuery = "Query"; const kNameCheckbox = "Checkbox"; const kNameURLParam = "URL Parameter"; const kNameHeader = "Header Name"; @@ -453,6 +455,10 @@ const kLabelAddHeader = "Add Header"; const kLabelAddVariable = "Add Variable"; const kLabelSelectFile = "Select File"; const kLabelAddFormField = "Add Form Field"; +const kHintContent = "Enter content"; +const kHintText = "Enter text"; +const kHintJson = "Enter JSON"; +const kHintQuery = "Enter Query"; // Response Pane const kLabelNotSent = "Not Sent"; const kLabelResponse = "Response"; diff --git a/lib/models/history_meta_model.dart b/lib/models/history_meta_model.dart index 0e2bdb43..c0226086 100644 --- a/lib/models/history_meta_model.dart +++ b/lib/models/history_meta_model.dart @@ -9,6 +9,7 @@ class HistoryMetaModel with _$HistoryMetaModel { const factory HistoryMetaModel({ required String historyId, required String requestId, + required APIType apiType, @Default("") String name, required String url, required HTTPVerb method, diff --git a/lib/models/history_meta_model.freezed.dart b/lib/models/history_meta_model.freezed.dart index 38931547..97a068b2 100644 --- a/lib/models/history_meta_model.freezed.dart +++ b/lib/models/history_meta_model.freezed.dart @@ -22,6 +22,7 @@ HistoryMetaModel _$HistoryMetaModelFromJson(Map json) { mixin _$HistoryMetaModel { String get historyId => throw _privateConstructorUsedError; String get requestId => throw _privateConstructorUsedError; + APIType get apiType => throw _privateConstructorUsedError; String get name => throw _privateConstructorUsedError; String get url => throw _privateConstructorUsedError; HTTPVerb get method => throw _privateConstructorUsedError; @@ -47,6 +48,7 @@ abstract class $HistoryMetaModelCopyWith<$Res> { $Res call( {String historyId, String requestId, + APIType apiType, String name, String url, HTTPVerb method, @@ -71,6 +73,7 @@ class _$HistoryMetaModelCopyWithImpl<$Res, $Val extends HistoryMetaModel> $Res call({ Object? historyId = null, Object? requestId = null, + Object? apiType = null, Object? name = null, Object? url = null, Object? method = null, @@ -86,6 +89,10 @@ class _$HistoryMetaModelCopyWithImpl<$Res, $Val extends HistoryMetaModel> ? _value.requestId : requestId // ignore: cast_nullable_to_non_nullable as String, + apiType: null == apiType + ? _value.apiType + : apiType // ignore: cast_nullable_to_non_nullable + as APIType, name: null == name ? _value.name : name // ignore: cast_nullable_to_non_nullable @@ -121,6 +128,7 @@ abstract class _$$HistoryMetaModelImplCopyWith<$Res> $Res call( {String historyId, String requestId, + APIType apiType, String name, String url, HTTPVerb method, @@ -143,6 +151,7 @@ class __$$HistoryMetaModelImplCopyWithImpl<$Res> $Res call({ Object? historyId = null, Object? requestId = null, + Object? apiType = null, Object? name = null, Object? url = null, Object? method = null, @@ -158,6 +167,10 @@ class __$$HistoryMetaModelImplCopyWithImpl<$Res> ? _value.requestId : requestId // ignore: cast_nullable_to_non_nullable as String, + apiType: null == apiType + ? _value.apiType + : apiType // ignore: cast_nullable_to_non_nullable + as APIType, name: null == name ? _value.name : name // ignore: cast_nullable_to_non_nullable @@ -188,6 +201,7 @@ class _$HistoryMetaModelImpl implements _HistoryMetaModel { const _$HistoryMetaModelImpl( {required this.historyId, required this.requestId, + required this.apiType, this.name = "", required this.url, required this.method, @@ -202,6 +216,8 @@ class _$HistoryMetaModelImpl implements _HistoryMetaModel { @override final String requestId; @override + final APIType apiType; + @override @JsonKey() final String name; @override @@ -215,7 +231,7 @@ class _$HistoryMetaModelImpl implements _HistoryMetaModel { @override String toString() { - return 'HistoryMetaModel(historyId: $historyId, requestId: $requestId, name: $name, url: $url, method: $method, responseStatus: $responseStatus, timeStamp: $timeStamp)'; + return 'HistoryMetaModel(historyId: $historyId, requestId: $requestId, apiType: $apiType, name: $name, url: $url, method: $method, responseStatus: $responseStatus, timeStamp: $timeStamp)'; } @override @@ -227,6 +243,7 @@ class _$HistoryMetaModelImpl implements _HistoryMetaModel { other.historyId == historyId) && (identical(other.requestId, requestId) || other.requestId == requestId) && + (identical(other.apiType, apiType) || other.apiType == apiType) && (identical(other.name, name) || other.name == name) && (identical(other.url, url) || other.url == url) && (identical(other.method, method) || other.method == method) && @@ -238,8 +255,8 @@ class _$HistoryMetaModelImpl implements _HistoryMetaModel { @JsonKey(includeFromJson: false, includeToJson: false) @override - int get hashCode => Object.hash(runtimeType, historyId, requestId, name, url, - method, responseStatus, timeStamp); + int get hashCode => Object.hash(runtimeType, historyId, requestId, apiType, + name, url, method, responseStatus, timeStamp); /// Create a copy of HistoryMetaModel /// with the given fields replaced by the non-null parameter values. @@ -262,6 +279,7 @@ abstract class _HistoryMetaModel implements HistoryMetaModel { const factory _HistoryMetaModel( {required final String historyId, required final String requestId, + required final APIType apiType, final String name, required final String url, required final HTTPVerb method, @@ -276,6 +294,8 @@ abstract class _HistoryMetaModel implements HistoryMetaModel { @override String get requestId; @override + APIType get apiType; + @override String get name; @override String get url; diff --git a/lib/models/history_meta_model.g.dart b/lib/models/history_meta_model.g.dart index 3d8af833..a2b2b7b1 100644 --- a/lib/models/history_meta_model.g.dart +++ b/lib/models/history_meta_model.g.dart @@ -11,6 +11,7 @@ _$HistoryMetaModelImpl _$$HistoryMetaModelImplFromJson( _$HistoryMetaModelImpl( historyId: json['historyId'] as String, requestId: json['requestId'] as String, + apiType: $enumDecode(_$APITypeEnumMap, json['apiType']), name: json['name'] as String? ?? "", url: json['url'] as String, method: $enumDecode(_$HTTPVerbEnumMap, json['method']), @@ -23,6 +24,7 @@ Map _$$HistoryMetaModelImplToJson( { 'historyId': instance.historyId, 'requestId': instance.requestId, + 'apiType': _$APITypeEnumMap[instance.apiType]!, 'name': instance.name, 'url': instance.url, 'method': _$HTTPVerbEnumMap[instance.method]!, @@ -30,6 +32,11 @@ Map _$$HistoryMetaModelImplToJson( 'timeStamp': instance.timeStamp.toIso8601String(), }; +const _$APITypeEnumMap = { + APIType.rest: 'rest', + APIType.graphql: 'graphql', +}; + const _$HTTPVerbEnumMap = { HTTPVerb.get: 'get', HTTPVerb.head: 'head', diff --git a/lib/models/request_model.g.dart b/lib/models/request_model.g.dart index be2132fe..68b56395 100644 --- a/lib/models/request_model.g.dart +++ b/lib/models/request_model.g.dart @@ -43,4 +43,5 @@ Map _$$RequestModelImplToJson(_$RequestModelImpl instance) => const _$APITypeEnumMap = { APIType.rest: 'rest', + APIType.graphql: 'graphql', }; diff --git a/lib/providers/collection_providers.dart b/lib/providers/collection_providers.dart index c3f50291..2fb83077 100644 --- a/lib/providers/collection_providers.dart +++ b/lib/providers/collection_providers.dart @@ -1,4 +1,5 @@ import 'package:apidash_core/apidash_core.dart'; +import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:apidash/consts.dart'; import 'providers.dart'; @@ -112,10 +113,11 @@ class CollectionStateNotifier unsave(); } - void remove(String id) { + void remove({String? id}) { + final rId = id ?? ref.read(selectedIdStateProvider); var itemIds = ref.read(requestSequenceProvider); - int idx = itemIds.indexOf(id); - itemIds.remove(id); + int idx = itemIds.indexOf(rId!); + itemIds.remove(rId); ref.read(requestSequenceProvider.notifier).state = [...itemIds]; String? newId; @@ -130,14 +132,15 @@ class CollectionStateNotifier ref.read(selectedIdStateProvider.notifier).state = newId; var map = {...state!}; - map.remove(id); + map.remove(rId); state = map; unsave(); } - void clearResponse(String? id) { - if (id == null || state?[id] == null) return; - var currentModel = state![id]!; + void clearResponse({String? id}) { + final rId = id ?? ref.read(selectedIdStateProvider); + if (rId == null || state?[rId] == null) return; + var currentModel = state![rId]!; final newModel = currentModel.copyWith( responseStatus: null, message: null, @@ -146,17 +149,18 @@ class CollectionStateNotifier sendingTime: null, ); var map = {...state!}; - map[id] = newModel; + map[rId] = newModel; state = map; unsave(); } - void duplicate(String id) { + void duplicate({String? id}) { + final rId = id ?? ref.read(selectedIdStateProvider); final newId = getNewUuid(); var itemIds = ref.read(requestSequenceProvider); - int idx = itemIds.indexOf(id); - var currentModel = state![id]!; + int idx = itemIds.indexOf(rId!); + var currentModel = state![rId]!; final newModel = currentModel.copyWith( id: newId, name: "${currentModel.name} (copy)", @@ -204,9 +208,10 @@ class CollectionStateNotifier unsave(); } - void update( - String id, { + void update({ + String? id, HTTPVerb? method, + APIType? apiType, String? url, String? name, String? description, @@ -217,14 +222,21 @@ class CollectionStateNotifier List? isParamEnabledList, ContentType? bodyContentType, String? body, + String? query, List? formData, int? responseStatus, String? message, HttpResponseModel? httpResponseModel, }) { - var currentModel = state![id]!; + final rId = id ?? ref.read(selectedIdStateProvider); + if (rId == null) { + debugPrint("Unable to update as Request Id is null"); + return; + } + var currentModel = state![rId]!; var currentHttpRequestModel = currentModel.httpRequestModel; final newModel = currentModel.copyWith( + apiType: apiType ?? currentModel.apiType, name: name ?? currentModel.name, description: description ?? currentModel.description, requestTabIndex: requestTabIndex ?? currentModel.requestTabIndex, @@ -240,6 +252,7 @@ class CollectionStateNotifier bodyContentType: bodyContentType ?? currentHttpRequestModel.bodyContentType, body: body ?? currentHttpRequestModel.body, + query: query ?? currentHttpRequestModel.query, formData: formData ?? currentHttpRequestModel.formData, ), responseStatus: responseStatus ?? currentModel.responseStatus, @@ -248,7 +261,7 @@ class CollectionStateNotifier ); var map = {...state!}; - map[id] = newModel; + map[rId] = newModel; state = map; unsave(); } @@ -267,8 +280,9 @@ class CollectionStateNotifier return; } + APIType apiType = requestModel!.apiType; HttpRequestModel substitutedHttpRequestModel = - getSubstitutedHttpRequestModel(requestModel!.httpRequestModel!); + getSubstitutedHttpRequestModel(requestModel.httpRequestModel!); // set current model's isWorking to true and update state var map = {...state!}; @@ -281,6 +295,7 @@ class CollectionStateNotifier bool noSSL = ref.read(settingsProvider).isSSLDisabled; (HttpResponse?, Duration?, String?)? responseRec = await request( requestId, + apiType, substitutedHttpRequestModel, defaultUriScheme: defaultUriScheme, noSSL: noSSL, @@ -311,6 +326,7 @@ class CollectionStateNotifier metaData: HistoryMetaModel( historyId: newHistoryId, requestId: requestId, + apiType: requestModel.apiType, name: requestModel.name, url: substitutedHttpRequestModel.url, method: substitutedHttpRequestModel.method, diff --git a/lib/screens/common_widgets/api_type_dropdown.dart b/lib/screens/common_widgets/api_type_dropdown.dart new file mode 100644 index 00000000..c35645d6 --- /dev/null +++ b/lib/screens/common_widgets/api_type_dropdown.dart @@ -0,0 +1,23 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:apidash/providers/providers.dart'; +import 'package:apidash/widgets/widgets.dart'; + +class APITypeDropdown extends ConsumerWidget { + const APITypeDropdown({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + ref.watch(selectedIdStateProvider); + final apiType = ref + .watch(selectedRequestModelProvider.select((value) => value?.apiType)); + return APITypePopupMenu( + apiType: apiType, + onChanged: (type) { + ref + .read(collectionStateNotifierProvider.notifier) + .update(apiType: type); + }, + ); + } +} diff --git a/lib/screens/common_widgets/code_pane.dart b/lib/screens/common_widgets/code_pane.dart index 1da84542..654b9440 100644 --- a/lib/screens/common_widgets/code_pane.dart +++ b/lib/screens/common_widgets/code_pane.dart @@ -1,3 +1,4 @@ +import 'package:apidash_core/apidash_core.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:apidash/providers/providers.dart'; @@ -39,6 +40,13 @@ class CodePane extends ConsumerWidget { final code = codegen.getCode( codegenLanguage, substitutedRequestModel!, defaultUriScheme); + + // TODO: Add GraphQL Codegen + if (substitutedRequestModel.apiType == APIType.graphql) { + return const ErrorMessage( + message: "Code generation for GraphQL is currently not available.", + ); + } if (code == null) { return const ErrorMessage( message: "An error was encountered while generating code. $kRaiseIssue", diff --git a/lib/screens/common_widgets/common_widgets.dart b/lib/screens/common_widgets/common_widgets.dart index ee150dd8..d6932407 100644 --- a/lib/screens/common_widgets/common_widgets.dart +++ b/lib/screens/common_widgets/common_widgets.dart @@ -1,3 +1,4 @@ +export 'api_type_dropdown.dart'; export 'button_navbar.dart'; export 'code_pane.dart'; export 'editor_title.dart'; diff --git a/lib/screens/history/history_page.dart b/lib/screens/history/history_page.dart index 37ca2c5e..5db100e6 100644 --- a/lib/screens/history/history_page.dart +++ b/lib/screens/history/history_page.dart @@ -5,7 +5,7 @@ import 'package:apidash/widgets/widgets.dart'; import 'package:apidash/providers/providers.dart'; import 'package:apidash/utils/utils.dart'; import 'package:apidash/consts.dart'; -import 'history_pane.dart'; +import 'history_sidebar.dart'; import 'history_viewer.dart'; class HistoryPage extends ConsumerWidget { diff --git a/lib/screens/history/history_pane.dart b/lib/screens/history/history_sidebar.dart similarity index 99% rename from lib/screens/history/history_pane.dart rename to lib/screens/history/history_sidebar.dart index 95cbb559..77e4b36e 100644 --- a/lib/screens/history/history_pane.dart +++ b/lib/screens/history/history_sidebar.dart @@ -142,6 +142,7 @@ class _HistoryExpansionTileState extends ConsumerState padding: kPv2 + kPh4, child: SidebarHistoryCard( id: item.first.historyId, + apiType: item.first.apiType, models: item, method: item.first.method, isSelected: selectedGroupId == getHistoryRequestKey(item.first), diff --git a/lib/screens/history/history_widgets/his_request_pane.dart b/lib/screens/history/history_widgets/his_request_pane.dart index f24fd668..dc618e77 100644 --- a/lib/screens/history/history_widgets/his_request_pane.dart +++ b/lib/screens/history/history_widgets/his_request_pane.dart @@ -18,7 +18,8 @@ class HistoryRequestPane extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final selectedId = ref.watch(selectedHistoryIdStateProvider); final codePaneVisible = ref.watch(historyCodePaneVisibleStateProvider); - + final apiType = ref.watch(selectedHistoryRequestModelProvider + .select((value) => value?.metaData.apiType)); final headersMap = ref.watch(selectedHistoryRequestModelProvider .select((value) => value?.httpRequestModel.headersMap)) ?? {}; @@ -33,31 +34,69 @@ class HistoryRequestPane extends ConsumerWidget { .select((value) => value?.httpRequestModel.hasBody)) ?? false; - return RequestPane( - selectedId: selectedId, - codePaneVisible: codePaneVisible, - onPressedCodeButton: () { - ref.read(historyCodePaneVisibleStateProvider.notifier).state = - !codePaneVisible; - }, - showViewCodeButton: !isCompact, - showIndicators: [ - paramLength > 0, - headerLength > 0, - hasBody, - ], - children: [ - RequestDataTable( - rows: paramsMap, - keyName: kNameURLParam, + final hasQuery = ref.watch(selectedHistoryRequestModelProvider + .select((value) => value?.httpRequestModel.hasQuery)) ?? + false; + + return switch (apiType) { + APIType.rest => RequestPane( + key: const Key("history-request-pane-rest"), + selectedId: selectedId, + codePaneVisible: codePaneVisible, + onPressedCodeButton: () { + ref.read(historyCodePaneVisibleStateProvider.notifier).state = + !codePaneVisible; + }, + showViewCodeButton: !isCompact, + showIndicators: [ + paramLength > 0, + headerLength > 0, + hasBody, + ], + tabLabels: const [ + kLabelURLParams, + kLabelHeaders, + kLabelBody, + ], + children: [ + RequestDataTable( + rows: paramsMap, + keyName: kNameURLParam, + ), + RequestDataTable( + rows: headersMap, + keyName: kNameHeader, + ), + const HisRequestBody(), + ], ), - RequestDataTable( - rows: headersMap, - keyName: kNameHeader, + APIType.graphql => RequestPane( + key: const Key("history-request-pane-graphql"), + selectedId: selectedId, + codePaneVisible: codePaneVisible, + onPressedCodeButton: () { + ref.read(historyCodePaneVisibleStateProvider.notifier).state = + !codePaneVisible; + }, + showViewCodeButton: !isCompact, + showIndicators: [ + headerLength > 0, + hasQuery, + ], + tabLabels: const [ + kLabelHeaders, + kLabelQuery, + ], + children: [ + RequestDataTable( + rows: headersMap, + keyName: kNameHeader, + ), + const HisRequestBody(), + ], ), - const HisRequestBody(), - ], - ); + _ => kSizedBoxEmpty, + }; } } @@ -67,58 +106,72 @@ class HisRequestBody extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final selectedHistoryModel = ref.watch(selectedHistoryRequestModelProvider); + final apiType = selectedHistoryModel?.metaData.apiType; final requestModel = selectedHistoryModel?.httpRequestModel; final contentType = requestModel?.bodyContentType; - return Column( - children: [ - kVSpacer5, - RichText( - text: TextSpan( - style: Theme.of(context).textTheme.labelLarge, - children: [ - const TextSpan( - text: "Content Type: ", + return switch (apiType) { + APIType.rest => Column( + children: [ + kVSpacer5, + RichText( + text: TextSpan( + style: Theme.of(context).textTheme.labelLarge, + children: [ + const TextSpan( + text: "Content Type: ", + ), + TextSpan( + text: contentType?.name ?? "text", + style: Theme.of(context).textTheme.titleSmall?.copyWith( + color: Theme.of(context).colorScheme.primary, + fontWeight: FontWeight.bold, + )), + ], ), - TextSpan( - text: contentType?.name ?? "text", - style: Theme.of(context).textTheme.titleSmall?.copyWith( - color: Theme.of(context).colorScheme.primary, - fontWeight: FontWeight.bold, - )), - ], + ), + kVSpacer5, + Expanded( + child: switch (contentType) { + ContentType.formdata => Padding( + padding: kPh4, + child: RequestFormDataTable( + rows: requestModel?.formData ?? [])), + // TODO: Fix JsonTextFieldEditor & plug it here + ContentType.json => Padding( + padding: kPt5o10, + child: TextFieldEditor( + key: Key("${selectedHistoryModel?.historyId}-json-body"), + fieldKey: + "${selectedHistoryModel?.historyId}-json-body-viewer", + initialValue: requestModel?.body, + readOnly: true, + ), + ), + _ => Padding( + padding: kPt5o10, + child: TextFieldEditor( + key: Key("${selectedHistoryModel?.historyId}-body"), + fieldKey: + "${selectedHistoryModel?.historyId}-body-viewer", + initialValue: requestModel?.body, + readOnly: true, + ), + ), + }, + ) + ], + ), + APIType.graphql => Padding( + padding: kPt5o10, + child: TextFieldEditor( + key: Key("${selectedHistoryModel?.historyId}-query"), + fieldKey: "${selectedHistoryModel?.historyId}-query-viewer", + initialValue: requestModel?.query, + readOnly: true, ), ), - kVSpacer5, - Expanded( - child: switch (contentType) { - ContentType.formdata => Padding( - padding: kPh4, - child: - RequestFormDataTable(rows: requestModel?.formData ?? [])), - // TODO: Fix JsonTextFieldEditor & plug it here - ContentType.json => Padding( - padding: kPt5o10, - child: TextFieldEditor( - key: Key("${selectedHistoryModel?.historyId}-json-body"), - fieldKey: - "${selectedHistoryModel?.historyId}-json-body-viewer", - initialValue: requestModel?.body, - readOnly: true, - ), - ), - _ => Padding( - padding: kPt5o10, - child: TextFieldEditor( - key: Key("${selectedHistoryModel?.historyId}-body"), - fieldKey: "${selectedHistoryModel?.historyId}-body-viewer", - initialValue: requestModel?.body, - readOnly: true, - ), - ), - }, - ) - ], - ); + _ => kSizedBoxEmpty, + }; } } diff --git a/lib/screens/history/history_widgets/his_sidebar_header.dart b/lib/screens/history/history_widgets/his_sidebar_header.dart index 9f929e40..ad6fb1da 100644 --- a/lib/screens/history/history_widgets/his_sidebar_header.dart +++ b/lib/screens/history/history_widgets/his_sidebar_header.dart @@ -1,3 +1,4 @@ +import 'package:apidash/services/services.dart'; import 'package:apidash_design_system/apidash_design_system.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; @@ -21,11 +22,19 @@ class HistorySidebarHeader extends ConsumerWidget { style: Theme.of(context).textTheme.titleMedium, ), const Spacer(), - IconButton( + ADIconButton( + icon: Icons.delete_forever, + iconSize: kButtonIconSizeLarge, + tooltip: "Clear History", + color: Theme.of(context).brightness == Brightness.dark + ? kColorDarkDanger + : kColorLightDanger, + onPressed: () => hiveHandler.clearAllHistory(), + ), + ADIconButton( + icon: Icons.manage_history_rounded, + iconSize: kButtonIconSizeLarge, tooltip: "Manage History", - style: IconButton.styleFrom( - foregroundColor: Theme.of(context).colorScheme.primary, - ), onPressed: () { showHistoryRetentionDialog( context, @@ -36,10 +45,6 @@ class HistorySidebarHeader extends ConsumerWidget { ); }); }, - icon: const Icon( - Icons.manage_history_rounded, - size: 20, - ), ), context.width <= kMinWindowSize.width ? IconButton( diff --git a/lib/screens/history/history_widgets/his_url_card.dart b/lib/screens/history/history_widgets/his_url_card.dart index 7ce5833c..61e568f4 100644 --- a/lib/screens/history/history_widgets/his_url_card.dart +++ b/lib/screens/history/history_widgets/his_url_card.dart @@ -1,3 +1,4 @@ +import 'package:apidash_core/apidash_core.dart'; import 'package:apidash_design_system/apidash_design_system.dart'; import 'package:flutter/material.dart'; import 'package:apidash/widgets/widgets.dart'; @@ -16,6 +17,7 @@ class HistoryURLCard extends StatelessWidget { @override Widget build(BuildContext context) { + final apiType = historyRequestModel?.metaData.apiType; final method = historyRequestModel?.metaData.method; final url = historyRequestModel?.metaData.url; final fontSize = Theme.of(context).textTheme.titleMedium?.fontSize; @@ -41,18 +43,20 @@ class HistoryURLCard extends StatelessWidget { child: Row( children: [ isCompact ? const SizedBox.shrink() : kHSpacer10, - Text( - method!.name.toUpperCase(), - style: kCodeStyle.copyWith( - fontSize: fontSize, - fontWeight: FontWeight.bold, - color: getHTTPMethodColor( - method, - brightness: Theme.of(context).brightness, + if (apiType == APIType.rest) ...[ + Text( + method!.name.toUpperCase(), + style: kCodeStyle.copyWith( + fontSize: fontSize, + fontWeight: FontWeight.bold, + color: getHTTPMethodColor( + method, + brightness: Theme.of(context).brightness, + ), ), ), - ), - isCompact ? kHSpacer10 : kHSpacer20, + isCompact ? kHSpacer10 : kHSpacer20, + ], Expanded( child: ReadOnlyTextField( initialValue: url, diff --git a/lib/screens/home_page/collection_pane.dart b/lib/screens/home_page/collection_pane.dart index bfac27a8..4573b7ea 100644 --- a/lib/screens/home_page/collection_pane.dart +++ b/lib/screens/home_page/collection_pane.dart @@ -186,6 +186,7 @@ class RequestItem extends ConsumerWidget { return SidebarRequestCard( id: id, + apiType: requestModel.apiType, method: requestModel.httpRequestModel!.method, name: requestModel.name, url: requestModel.httpRequestModel?.url, @@ -208,7 +209,7 @@ class RequestItem extends ConsumerWidget { value = value.trim(); ref .read(collectionStateNotifierProvider.notifier) - .update(editRequestId!, name: value); + .update(id: editRequestId!, name: value); }, onTapOutsideNameEditor: () { ref.read(selectedIdEditStateProvider.notifier).state = null; @@ -231,10 +232,10 @@ class RequestItem extends ConsumerWidget { ); } if (item == ItemMenuOption.delete) { - ref.read(collectionStateNotifierProvider.notifier).remove(id); + ref.read(collectionStateNotifierProvider.notifier).remove(id: id); } if (item == ItemMenuOption.duplicate) { - ref.read(collectionStateNotifierProvider.notifier).duplicate(id); + ref.read(collectionStateNotifierProvider.notifier).duplicate(id: id); } }, ); diff --git a/lib/screens/home_page/editor_pane/details_card/request_pane/request_body.dart b/lib/screens/home_page/editor_pane/details_card/request_pane/request_body.dart index 31c52de4..26686e67 100644 --- a/lib/screens/home_page/editor_pane/details_card/request_pane/request_body.dart +++ b/lib/screens/home_page/editor_pane/details_card/request_pane/request_body.dart @@ -18,6 +18,8 @@ class EditRequestBody extends ConsumerWidget { .getRequestModel(selectedId!); final contentType = ref.watch(selectedRequestModelProvider .select((value) => value?.httpRequestModel?.bodyContentType)); + final apiType = ref + .watch(selectedRequestModelProvider.select((value) => value?.apiType)); // TODO: #178 GET->POST Currently switches to POST everytime user edits body even if the user intentionally chooses GET // final sm = ScaffoldMessenger.of(context); @@ -36,57 +38,80 @@ class EditRequestBody extends ConsumerWidget { return Column( children: [ - const SizedBox( - height: kHeaderHeight, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - "Select Content Type:", - ), - DropdownButtonBodyContentType(), - ], - ), - ), - Expanded( - child: switch (contentType) { - ContentType.formdata => const Padding( - padding: kPh4, - child: FormDataWidget( - // TODO: See changeToPostMethod above - // changeMethodToPost: changeToPostMethod, - )), - // TODO: Fix JsonTextFieldEditor & plug it here - ContentType.json => Padding( + (apiType == APIType.rest) + ? const SizedBox( + height: kHeaderHeight, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + "Select Content Type:", + ), + DropdownButtonBodyContentType(), + ], + ), + ) + : kSizedBoxEmpty, + switch (apiType) { + APIType.rest => Expanded( + child: switch (contentType) { + ContentType.formdata => const Padding( + padding: kPh4, + child: FormDataWidget( + // TODO: See changeToPostMethod above + // changeMethodToPost: changeToPostMethod, + )), + // TODO: Fix JsonTextFieldEditor & plug it here + ContentType.json => Padding( + padding: kPt5o10, + child: TextFieldEditor( + key: Key("$selectedId-json-body"), + fieldKey: "$selectedId-json-body-editor", + initialValue: requestModel?.httpRequestModel?.body, + onChanged: (String value) { + // changeToPostMethod(); + ref + .read(collectionStateNotifierProvider.notifier) + .update(body: value); + }, + hintText: kHintJson, + ), + ), + _ => Padding( + padding: kPt5o10, + child: TextFieldEditor( + key: Key("$selectedId-body"), + fieldKey: "$selectedId-body-editor", + initialValue: requestModel?.httpRequestModel?.body, + onChanged: (String value) { + // changeToPostMethod(); + ref + .read(collectionStateNotifierProvider.notifier) + .update(body: value); + }, + hintText: kHintText, + ), + ), + }, + ), + APIType.graphql => Expanded( + child: Padding( padding: kPt5o10, child: TextFieldEditor( - key: Key("$selectedId-json-body"), - fieldKey: "$selectedId-json-body-editor", - initialValue: requestModel?.httpRequestModel?.body, + key: Key("$selectedId-query"), + fieldKey: "$selectedId-query-editor", + initialValue: requestModel?.httpRequestModel?.query, onChanged: (String value) { - // changeToPostMethod(); ref .read(collectionStateNotifierProvider.notifier) - .update(selectedId, body: value); + .update(query: value); }, + hintText: kHintQuery, ), ), - _ => Padding( - padding: kPt5o10, - child: TextFieldEditor( - key: Key("$selectedId-body"), - fieldKey: "$selectedId-body-editor", - initialValue: requestModel?.httpRequestModel?.body, - onChanged: (String value) { - // changeToPostMethod(); - ref - .read(collectionStateNotifierProvider.notifier) - .update(selectedId, body: value); - }, - ), - ), - }, - ) + ), + _ => kSizedBoxEmpty, + } ], ); } @@ -99,7 +124,7 @@ class DropdownButtonBodyContentType extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final selectedId = ref.watch(selectedIdStateProvider); + ref.watch(selectedIdStateProvider); final requestBodyContentType = ref.watch(selectedRequestModelProvider .select((value) => value?.httpRequestModel?.bodyContentType)); return DropdownButtonContentType( @@ -107,7 +132,7 @@ class DropdownButtonBodyContentType extends ConsumerWidget { onChanged: (ContentType? value) { ref .read(collectionStateNotifierProvider.notifier) - .update(selectedId!, bodyContentType: value); + .update(bodyContentType: value); }, ); } diff --git a/lib/screens/home_page/editor_pane/details_card/request_pane/request_form_data.dart b/lib/screens/home_page/editor_pane/details_card/request_pane/request_form_data.dart index a25faebd..6b0a48c2 100644 --- a/lib/screens/home_page/editor_pane/details_card/request_pane/request_form_data.dart +++ b/lib/screens/home_page/editor_pane/details_card/request_pane/request_form_data.dart @@ -27,9 +27,8 @@ class _FormDataBodyState extends ConsumerState { seed = random.nextInt(kRandMax); } - void _onFieldChange(String selectedId) { + void _onFieldChange() { ref.read(collectionStateNotifierProvider.notifier).update( - selectedId, formData: formRows.sublist(0, formRows.length - 1), ); } @@ -93,7 +92,7 @@ class _FormDataBodyState extends ConsumerState { isAddingRow = true; formRows.add(kFormDataEmptyModel); } - _onFieldChange(selectedId!); + _onFieldChange(); }, colorScheme: Theme.of(context).colorScheme, ), @@ -119,7 +118,7 @@ class _FormDataBodyState extends ConsumerState { formRows.add(kFormDataEmptyModel); } setState(() {}); - _onFieldChange(selectedId!); + _onFieldChange(); }, ), ), @@ -134,7 +133,7 @@ class _FormDataBodyState extends ConsumerState { value: pickedResult.path, ); setState(() {}); - _onFieldChange(selectedId!); + _onFieldChange(); } }, initialValue: formRows[index].value, @@ -150,7 +149,7 @@ class _FormDataBodyState extends ConsumerState { isAddingRow = true; formRows.add(kFormDataEmptyModel); } - _onFieldChange(selectedId!); + _onFieldChange(); }, colorScheme: Theme.of(context).colorScheme, ), @@ -170,7 +169,7 @@ class _FormDataBodyState extends ConsumerState { } else { formRows.removeAt(index); } - _onFieldChange(selectedId!); + _onFieldChange(); }, child: Theme.of(context).brightness == Brightness.dark ? kIconRemoveDark @@ -216,7 +215,7 @@ class _FormDataBodyState extends ConsumerState { child: ElevatedButton.icon( onPressed: () { formRows.add(kFormDataEmptyModel); - _onFieldChange(selectedId!); + _onFieldChange(); }, icon: const Icon(Icons.add), label: const Text( diff --git a/lib/screens/home_page/editor_pane/details_card/request_pane/request_headers.dart b/lib/screens/home_page/editor_pane/details_card/request_pane/request_headers.dart index 3efd41bb..ecb3f6da 100644 --- a/lib/screens/home_page/editor_pane/details_card/request_pane/request_headers.dart +++ b/lib/screens/home_page/editor_pane/details_card/request_pane/request_headers.dart @@ -29,9 +29,8 @@ class EditRequestHeadersState extends ConsumerState { seed = random.nextInt(kRandMax); } - void _onFieldChange(String selectedId) { + void _onFieldChange() { ref.read(collectionStateNotifierProvider.notifier).update( - selectedId, headers: headerRows.sublist(0, headerRows.length - 1), isHeaderEnabledList: isRowEnabledList.sublist(0, headerRows.length - 1), @@ -99,7 +98,7 @@ class EditRequestHeadersState extends ConsumerState { setState(() { isRowEnabledList[index] = value!; }); - _onFieldChange(selectedId!); + _onFieldChange(); }, colorScheme: Theme.of(context).colorScheme, ), @@ -117,7 +116,7 @@ class EditRequestHeadersState extends ConsumerState { headerRows.add(kNameValueEmptyModel); isRowEnabledList.add(false); } - _onFieldChange(selectedId!); + _onFieldChange(); }, colorScheme: Theme.of(context).colorScheme, ), @@ -143,7 +142,7 @@ class EditRequestHeadersState extends ConsumerState { headerRows.add(kNameValueEmptyModel); isRowEnabledList.add(false); } - _onFieldChange(selectedId!); + _onFieldChange(); }, colorScheme: Theme.of(context).colorScheme, ), @@ -165,7 +164,7 @@ class EditRequestHeadersState extends ConsumerState { headerRows.removeAt(index); isRowEnabledList.removeAt(index); } - _onFieldChange(selectedId!); + _onFieldChange(); }, child: Theme.of(context).brightness == Brightness.dark ? kIconRemoveDark @@ -212,7 +211,7 @@ class EditRequestHeadersState extends ConsumerState { onPressed: () { headerRows.add(kNameValueEmptyModel); isRowEnabledList.add(false); - _onFieldChange(selectedId!); + _onFieldChange(); }, icon: const Icon(Icons.add), label: const Text( diff --git a/lib/screens/home_page/editor_pane/details_card/request_pane/request_pane.dart b/lib/screens/home_page/editor_pane/details_card/request_pane/request_pane.dart index ed385bb6..9c852c71 100644 --- a/lib/screens/home_page/editor_pane/details_card/request_pane/request_pane.dart +++ b/lib/screens/home_page/editor_pane/details_card/request_pane/request_pane.dart @@ -1,54 +1,23 @@ +import 'package:apidash_core/apidash_core.dart'; +import 'package:apidash_design_system/apidash_design_system.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:apidash/providers/providers.dart'; -import 'package:apidash/widgets/widgets.dart'; -import 'request_headers.dart'; -import 'request_params.dart'; -import 'request_body.dart'; +import 'request_pane_graphql.dart'; +import 'request_pane_rest.dart'; class EditRequestPane extends ConsumerWidget { const EditRequestPane({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { - final selectedId = ref.watch(selectedIdStateProvider); - final codePaneVisible = ref.watch(codePaneVisibleStateProvider); - final tabIndex = ref.watch( - selectedRequestModelProvider.select((value) => value?.requestTabIndex)); - - final headerLength = ref.watch(selectedRequestModelProvider - .select((value) => value?.httpRequestModel?.headersMap.length)) ?? - 0; - final paramLength = ref.watch(selectedRequestModelProvider - .select((value) => value?.httpRequestModel?.paramsMap.length)) ?? - 0; - final hasBody = ref.watch(selectedRequestModelProvider - .select((value) => value?.httpRequestModel?.hasBody)) ?? - false; - - return RequestPane( - selectedId: selectedId, - codePaneVisible: codePaneVisible, - tabIndex: tabIndex, - onPressedCodeButton: () { - ref.read(codePaneVisibleStateProvider.notifier).state = - !codePaneVisible; - }, - onTapTabBar: (index) { - ref - .read(collectionStateNotifierProvider.notifier) - .update(selectedId!, requestTabIndex: index); - }, - showIndicators: [ - paramLength > 0, - headerLength > 0, - hasBody, - ], - children: const [ - EditRequestURLParams(), - EditRequestHeaders(), - EditRequestBody(), - ], - ); + ref.watch(selectedIdStateProvider); + final apiType = ref + .watch(selectedRequestModelProvider.select((value) => value?.apiType)); + return switch (apiType) { + APIType.rest => const EditRestRequestPane(), + APIType.graphql => const EditGraphQLRequestPane(), + _ => kSizedBoxEmpty, + }; } } diff --git a/lib/screens/home_page/editor_pane/details_card/request_pane/request_pane_graphql.dart b/lib/screens/home_page/editor_pane/details_card/request_pane/request_pane_graphql.dart new file mode 100644 index 00000000..beae2b6c --- /dev/null +++ b/lib/screens/home_page/editor_pane/details_card/request_pane/request_pane_graphql.dart @@ -0,0 +1,54 @@ +import 'package:apidash/consts.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:apidash/providers/providers.dart'; +import 'package:apidash/widgets/widgets.dart'; +import 'request_headers.dart'; +import 'request_body.dart'; + +class EditGraphQLRequestPane extends ConsumerWidget { + const EditGraphQLRequestPane({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final selectedId = ref.watch(selectedIdStateProvider); + var tabIndex = ref.watch( + selectedRequestModelProvider.select((value) => value?.requestTabIndex)); + final codePaneVisible = ref.watch(codePaneVisibleStateProvider); + final headerLength = ref.watch(selectedRequestModelProvider + .select((value) => value?.httpRequestModel?.headersMap.length)) ?? + 0; + final hasQuery = ref.watch(selectedRequestModelProvider + .select((value) => value?.httpRequestModel?.hasQuery)) ?? + false; + if (tabIndex >= 2) { + tabIndex = 0; + } + return RequestPane( + selectedId: selectedId, + codePaneVisible: codePaneVisible, + tabIndex: tabIndex, + onPressedCodeButton: () { + ref.read(codePaneVisibleStateProvider.notifier).state = + !codePaneVisible; + }, + onTapTabBar: (index) { + ref + .read(collectionStateNotifierProvider.notifier) + .update(requestTabIndex: index); + }, + showIndicators: [ + headerLength > 0, + hasQuery, + ], + tabLabels: const [ + kLabelHeaders, + kLabelQuery, + ], + children: const [ + EditRequestHeaders(), + EditRequestBody(), + ], + ); + } +} diff --git a/lib/screens/home_page/editor_pane/details_card/request_pane/request_pane_rest.dart b/lib/screens/home_page/editor_pane/details_card/request_pane/request_pane_rest.dart new file mode 100644 index 00000000..e323f83b --- /dev/null +++ b/lib/screens/home_page/editor_pane/details_card/request_pane/request_pane_rest.dart @@ -0,0 +1,60 @@ +import 'package:apidash/consts.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:apidash/providers/providers.dart'; +import 'package:apidash/widgets/widgets.dart'; +import 'request_headers.dart'; +import 'request_params.dart'; +import 'request_body.dart'; + +class EditRestRequestPane extends ConsumerWidget { + const EditRestRequestPane({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final selectedId = ref.watch(selectedIdStateProvider); + final codePaneVisible = ref.watch(codePaneVisibleStateProvider); + final tabIndex = ref.watch( + selectedRequestModelProvider.select((value) => value?.requestTabIndex)); + + final headerLength = ref.watch(selectedRequestModelProvider + .select((value) => value?.httpRequestModel?.headersMap.length)) ?? + 0; + final paramLength = ref.watch(selectedRequestModelProvider + .select((value) => value?.httpRequestModel?.paramsMap.length)) ?? + 0; + final hasBody = ref.watch(selectedRequestModelProvider + .select((value) => value?.httpRequestModel?.hasBody)) ?? + false; + + return RequestPane( + selectedId: selectedId, + codePaneVisible: codePaneVisible, + tabIndex: tabIndex, + onPressedCodeButton: () { + ref.read(codePaneVisibleStateProvider.notifier).state = + !codePaneVisible; + }, + onTapTabBar: (index) { + ref + .read(collectionStateNotifierProvider.notifier) + .update(requestTabIndex: index); + }, + showIndicators: [ + paramLength > 0, + headerLength > 0, + hasBody, + ], + tabLabels: const [ + kLabelURLParams, + kLabelHeaders, + kLabelBody, + ], + children: const [ + EditRequestURLParams(), + EditRequestHeaders(), + EditRequestBody(), + ], + ); + } +} diff --git a/lib/screens/home_page/editor_pane/details_card/request_pane/request_params.dart b/lib/screens/home_page/editor_pane/details_card/request_pane/request_params.dart index ccd1814a..a583b183 100644 --- a/lib/screens/home_page/editor_pane/details_card/request_pane/request_params.dart +++ b/lib/screens/home_page/editor_pane/details_card/request_pane/request_params.dart @@ -29,9 +29,8 @@ class EditRequestURLParamsState extends ConsumerState { seed = random.nextInt(kRandMax); } - void _onFieldChange(String selectedId) { + void _onFieldChange() { ref.read(collectionStateNotifierProvider.notifier).update( - selectedId, params: paramRows.sublist(0, paramRows.length - 1), isParamEnabledList: isRowEnabledList.sublist(0, paramRows.length - 1), ); @@ -98,7 +97,7 @@ class EditRequestURLParamsState extends ConsumerState { setState(() { isRowEnabledList[index] = value!; }); - _onFieldChange(selectedId!); + _onFieldChange(); }, colorScheme: Theme.of(context).colorScheme, ), @@ -116,7 +115,7 @@ class EditRequestURLParamsState extends ConsumerState { paramRows.add(kNameValueEmptyModel); isRowEnabledList.add(false); } - _onFieldChange(selectedId!); + _onFieldChange(); }, colorScheme: Theme.of(context).colorScheme, ), @@ -142,7 +141,7 @@ class EditRequestURLParamsState extends ConsumerState { paramRows.add(kNameValueEmptyModel); isRowEnabledList.add(false); } - _onFieldChange(selectedId!); + _onFieldChange(); }, colorScheme: Theme.of(context).colorScheme, ), @@ -164,7 +163,7 @@ class EditRequestURLParamsState extends ConsumerState { paramRows.removeAt(index); isRowEnabledList.removeAt(index); } - _onFieldChange(selectedId!); + _onFieldChange(); }, child: Theme.of(context).brightness == Brightness.dark ? kIconRemoveDark @@ -212,7 +211,7 @@ class EditRequestURLParamsState extends ConsumerState { onPressed: () { paramRows.add(kNameValueEmptyModel); isRowEnabledList.add(false); - _onFieldChange(selectedId!); + _onFieldChange(); }, icon: const Icon(Icons.add), label: const Text( diff --git a/lib/screens/home_page/editor_pane/details_card/response_pane.dart b/lib/screens/home_page/editor_pane/details_card/response_pane.dart index 322da874..50d5531a 100644 --- a/lib/screens/home_page/editor_pane/details_card/response_pane.dart +++ b/lib/screens/home_page/editor_pane/details_card/response_pane.dart @@ -61,10 +61,7 @@ class ResponseDetails extends ConsumerWidget { message: message, time: responseModel?.time, onClearResponse: () { - final selectedRequest = ref.read(selectedRequestModelProvider); - ref - .read(collectionStateNotifierProvider.notifier) - .clearResponse(selectedRequest?.id); + ref.read(collectionStateNotifierProvider.notifier).clearResponse(); }, ), const Expanded( diff --git a/lib/screens/home_page/editor_pane/request_editor_top_bar.dart b/lib/screens/home_page/editor_pane/request_editor_top_bar.dart index 9be8990c..022b50ff 100644 --- a/lib/screens/home_page/editor_pane/request_editor_top_bar.dart +++ b/lib/screens/home_page/editor_pane/request_editor_top_bar.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:apidash/providers/providers.dart'; import 'package:apidash/widgets/widgets.dart'; +import '../../../consts.dart'; import '../../common_widgets/common_widgets.dart'; class RequestEditorTopBar extends ConsumerWidget { @@ -11,26 +12,18 @@ class RequestEditorTopBar extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final id = ref.watch(selectedIdStateProvider); + ref.watch(selectedIdStateProvider); final name = ref.watch(selectedRequestModelProvider.select((value) => value?.name)); return Padding( - padding: const EdgeInsets.only( - left: 12.0, - top: 4.0, - right: 4.0, - bottom: 4.0, - ), + padding: kP4, child: Row( children: [ - DropdownButtonAPIType( - apiType: APIType.rest, - onChanged: (apiType) {}, - ), + const APITypeDropdown(), kHSpacer10, Expanded( child: Text( - name ?? "", + name.isNullOrEmpty() ? kUntitled : name!, style: Theme.of(context).textTheme.bodyMedium, overflow: TextOverflow.ellipsis, maxLines: 1, @@ -42,14 +35,13 @@ class RequestEditorTopBar extends ConsumerWidget { showRenameDialog(context, "Rename Request", name, (val) { ref .read(collectionStateNotifierProvider.notifier) - .update(id!, name: val); + .update(name: val); }); }, - onDuplicatePressed: () => ref - .read(collectionStateNotifierProvider.notifier) - .duplicate(id!), + onDuplicatePressed: () => + ref.read(collectionStateNotifierProvider.notifier).duplicate(), onDeletePressed: () => - ref.read(collectionStateNotifierProvider.notifier).remove(id!), + ref.read(collectionStateNotifierProvider.notifier).remove(), ), kHSpacer10, const EnvironmentDropdown(), diff --git a/lib/screens/home_page/editor_pane/url_card.dart b/lib/screens/home_page/editor_pane/url_card.dart index 2caac33b..829bc5c9 100644 --- a/lib/screens/home_page/editor_pane/url_card.dart +++ b/lib/screens/home_page/editor_pane/url_card.dart @@ -6,11 +6,14 @@ import 'package:apidash/providers/providers.dart'; import 'package:apidash/widgets/widgets.dart'; import '../../common_widgets/common_widgets.dart'; -class EditorPaneRequestURLCard extends StatelessWidget { +class EditorPaneRequestURLCard extends ConsumerWidget { const EditorPaneRequestURLCard({super.key}); @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { + ref.watch(selectedIdStateProvider); + final apiType = ref + .watch(selectedRequestModelProvider.select((value) => value?.apiType)); return Card( color: kColorTransparent, surfaceTintColor: kColorTransparent, @@ -27,24 +30,38 @@ class EditorPaneRequestURLCard extends StatelessWidget { horizontal: !context.isMediumWindow ? 20 : 6, ), child: context.isMediumWindow - ? const Row( + ? Row( children: [ - DropdownButtonHTTPMethod(), - kHSpacer5, - Expanded( + switch (apiType) { + APIType.rest => const DropdownButtonHTTPMethod(), + APIType.graphql => kSizedBoxEmpty, + null => kSizedBoxEmpty, + }, + switch (apiType) { + APIType.rest => kHSpacer5, + _ => kHSpacer8, + }, + const Expanded( child: URLTextField(), ), ], ) - : const Row( + : Row( children: [ - DropdownButtonHTTPMethod(), - kHSpacer20, - Expanded( + switch (apiType) { + APIType.rest => const DropdownButtonHTTPMethod(), + APIType.graphql => kSizedBoxEmpty, + null => kSizedBoxEmpty, + }, + switch (apiType) { + APIType.rest => kHSpacer20, + _ => kHSpacer8, + }, + const Expanded( child: URLTextField(), ), kHSpacer20, - SizedBox( + const SizedBox( height: 36, child: SendRequestButton(), ) @@ -67,10 +84,9 @@ class DropdownButtonHTTPMethod extends ConsumerWidget { return DropdownButtonHttpMethod( method: method, onChanged: (HTTPVerb? value) { - final selectedId = ref.read(selectedRequestModelProvider)!.id; ref .read(collectionStateNotifierProvider.notifier) - .update(selectedId, method: value); + .update(method: value); }, ); } @@ -92,9 +108,7 @@ class URLTextField extends ConsumerWidget { ?.httpRequestModel ?.url, onChanged: (value) { - ref - .read(collectionStateNotifierProvider.notifier) - .update(selectedId, url: value); + ref.read(collectionStateNotifierProvider.notifier).update(url: value); }, onFieldSubmitted: (value) { ref.read(collectionStateNotifierProvider.notifier).sendRequest(); diff --git a/lib/screens/mobile/requests_page/request_tabs.dart b/lib/screens/mobile/requests_page/request_tabs.dart index 1cfe1ef0..54404a86 100644 --- a/lib/screens/mobile/requests_page/request_tabs.dart +++ b/lib/screens/mobile/requests_page/request_tabs.dart @@ -1,21 +1,34 @@ -import 'package:apidash/screens/common_widgets/common_widgets.dart'; import 'package:apidash_design_system/apidash_design_system.dart'; import 'package:flutter/material.dart'; import 'package:apidash/widgets/widgets.dart'; -import 'package:apidash/consts.dart'; +import '../../../consts.dart'; +import '../../common_widgets/common_widgets.dart'; import '../../home_page/editor_pane/details_card/response_pane.dart'; import '../../home_page/editor_pane/editor_request.dart'; import '../../home_page/editor_pane/url_card.dart'; class RequestTabs extends StatelessWidget { - const RequestTabs({super.key, required this.controller}); + const RequestTabs({ + super.key, + required this.controller, + }); final TabController controller; @override Widget build(BuildContext context) { return Column( children: [ - kVSpacer5, + const Padding( + padding: kPh8, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + APITypeDropdown(), + EnvironmentDropdown(), + ], + ), + ), + kVSpacer3, const Padding( padding: kPh4, child: EditorPaneRequestURLCard(), diff --git a/lib/screens/mobile/requests_page/requests_page.dart b/lib/screens/mobile/requests_page/requests_page.dart index 77fc6ce7..ce66bc73 100644 --- a/lib/screens/mobile/requests_page/requests_page.dart +++ b/lib/screens/mobile/requests_page/requests_page.dart @@ -40,19 +40,19 @@ class _RequestResponsePageState extends ConsumerState showRenameDialog(context, "Rename Request", name, (val) { ref .read(collectionStateNotifierProvider.notifier) - .update(id!, name: val); + .update(name: val); }); } if (item == ItemMenuOption.delete) { - ref.read(collectionStateNotifierProvider.notifier).remove(id!); + ref.read(collectionStateNotifierProvider.notifier).remove(); } if (item == ItemMenuOption.duplicate) { - ref.read(collectionStateNotifierProvider.notifier).duplicate(id!); + ref.read(collectionStateNotifierProvider.notifier).duplicate(); } }, ), leftDrawerContent: const CollectionPane(), - actions: const [Padding(padding: kPh8, child: EnvironmentDropdown())], + actions: const [kVSpacer16], mainContent: id == null ? const RequestEditorDefault() : RequestTabs( diff --git a/lib/services/hive_services.dart b/lib/services/hive_services.dart index 09ac8489..5ca47607 100644 --- a/lib/services/hive_services.dart +++ b/lib/services/hive_services.dart @@ -140,6 +140,11 @@ class HiveHandler { Future deleteHistoryRequest(String id) => historyLazyBox.delete(id); + Future clearAllHistory() async { + await historyMetaBox.clear(); + await historyLazyBox.clear(); + } + Future clear() async { await dataBox.clear(); await environmentBox.clear(); diff --git a/lib/utils/envvar_utils.dart b/lib/utils/envvar_utils.dart index af0195ff..620b8f4a 100644 --- a/lib/utils/envvar_utils.dart +++ b/lib/utils/envvar_utils.dart @@ -4,7 +4,7 @@ import 'package:apidash/models/models.dart'; String getEnvironmentTitle(String? name) { if (name == null || name.trim() == "") { - return "untitled"; + return kUntitled; } return name; } diff --git a/lib/utils/history_utils.dart b/lib/utils/history_utils.dart index e6db9d38..d1c3dbbc 100644 --- a/lib/utils/history_utils.dart +++ b/lib/utils/history_utils.dart @@ -9,6 +9,7 @@ DateTime stripTime(DateTime dateTime) { RequestModel getRequestModelFromHistoryModel(HistoryRequestModel model) { return RequestModel( id: model.historyId, + apiType: model.metaData.apiType, name: model.metaData.name, responseStatus: model.httpResponseModel.statusCode, message: kResponseCodeReasons[model.httpResponseModel.statusCode], diff --git a/lib/utils/http_utils.dart b/lib/utils/http_utils.dart index 9fcfc677..379a3bf9 100644 --- a/lib/utils/http_utils.dart +++ b/lib/utils/http_utils.dart @@ -3,12 +3,12 @@ import '../consts.dart'; String getRequestTitleFromUrl(String? url) { if (url == null || url.trim() == "") { - return "untitled"; + return kUntitled; } if (url.contains("://")) { String rem = url.split("://")[1]; if (rem.trim() == "") { - return "untitled"; + return kUntitled; } return rem; } diff --git a/lib/widgets/card_sidebar_history.dart b/lib/widgets/card_sidebar_history.dart index 49378125..8ecd1f1e 100644 --- a/lib/widgets/card_sidebar_history.dart +++ b/lib/widgets/card_sidebar_history.dart @@ -3,12 +3,13 @@ import 'package:apidash_design_system/apidash_design_system.dart'; import 'package:flutter/material.dart'; import 'package:apidash/models/models.dart'; import 'package:apidash/utils/utils.dart'; -import 'texts.dart' show MethodBox; +import 'texts.dart'; class SidebarHistoryCard extends StatelessWidget { const SidebarHistoryCard({ super.key, required this.id, + required this.apiType, required this.models, required this.method, this.isSelected = false, @@ -17,6 +18,7 @@ class SidebarHistoryCard extends StatelessWidget { }); final String id; + final APIType apiType; final List models; final HTTPVerb method; final bool isSelected; @@ -63,7 +65,10 @@ class SidebarHistoryCard extends StatelessWidget { height: 20, child: Row( children: [ - MethodBox(method: method), + SidebarRequestCardTextBox( + apiType: apiType, + method: method, + ), kHSpacer4, Expanded( child: Text( diff --git a/lib/widgets/card_sidebar_request.dart b/lib/widgets/card_sidebar_request.dart index 23455fbd..e7826274 100644 --- a/lib/widgets/card_sidebar_request.dart +++ b/lib/widgets/card_sidebar_request.dart @@ -4,12 +4,13 @@ import 'package:flutter/material.dart'; import 'package:apidash/consts.dart'; import 'package:apidash/utils/utils.dart'; import 'menu_item_card.dart'; -import 'texts.dart' show MethodBox; +import 'texts.dart'; class SidebarRequestCard extends StatelessWidget { const SidebarRequestCard({ super.key, required this.id, + required this.apiType, required this.method, this.name, this.url, @@ -26,6 +27,7 @@ class SidebarRequestCard extends StatelessWidget { }); final String id; + final APIType apiType; final String? name; final String? url; final HTTPVerb method; @@ -88,7 +90,10 @@ class SidebarRequestCard extends StatelessWidget { height: 20, child: Row( children: [ - MethodBox(method: method), + SidebarRequestCardTextBox( + apiType: apiType, + method: method, + ), kHSpacer4, Expanded( child: inEditMode diff --git a/lib/widgets/editor.dart b/lib/widgets/editor.dart index 18b391b9..516406b4 100644 --- a/lib/widgets/editor.dart +++ b/lib/widgets/editor.dart @@ -1,4 +1,5 @@ import 'dart:math' as math; +import 'package:apidash/consts.dart'; import 'package:apidash_design_system/apidash_design_system.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -10,12 +11,14 @@ class TextFieldEditor extends StatefulWidget { this.onChanged, this.initialValue, this.readOnly = false, + this.hintText, }); final String fieldKey; final Function(String)? onChanged; final String? initialValue; final bool readOnly; + final String? hintText; @override State createState() => _TextFieldEditorState(); } @@ -25,7 +28,7 @@ class _TextFieldEditorState extends State { late final FocusNode editorFocusNode; void insertTab() { - String sp = " "; + String sp = " "; int offset = math.min( controller.selection.baseOffset, controller.selection.extentOffset); String text = controller.text.substring(0, offset) + @@ -72,14 +75,16 @@ class _TextFieldEditorState extends State { expands: true, maxLines: null, readOnly: widget.readOnly, - style: kCodeStyle, + style: kCodeStyle.copyWith( + fontSize: Theme.of(context).textTheme.bodyMedium?.fontSize, + ), textAlignVertical: TextAlignVertical.top, onChanged: widget.onChanged, onTapOutside: (PointerDownEvent event) { editorFocusNode.unfocus(); }, decoration: InputDecoration( - hintText: "Enter content (body)", + hintText: widget.hintText ?? kHintContent, hintStyle: TextStyle( color: Theme.of(context).colorScheme.outline.withOpacity( kHintOpacity, diff --git a/lib/widgets/editor_json.dart b/lib/widgets/editor_json.dart index 1c1af0d1..3c0c2abf 100644 --- a/lib/widgets/editor_json.dart +++ b/lib/widgets/editor_json.dart @@ -1,4 +1,5 @@ import 'dart:math' as math; +import 'package:apidash/consts.dart'; import 'package:apidash_design_system/apidash_design_system.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -93,7 +94,7 @@ class _JsonTextFieldEditorState extends State { widget.onChanged?.call(value); }, decoration: InputDecoration( - hintText: "Enter content (body)", + hintText: kHintJson, hintStyle: TextStyle( color: Theme.of(context).colorScheme.outline.withOpacity( kHintOpacity, diff --git a/lib/widgets/dropdown_api_type.dart b/lib/widgets/popup_menu_api_type.dart similarity index 63% rename from lib/widgets/dropdown_api_type.dart rename to lib/widgets/popup_menu_api_type.dart index aa9f6922..22996e17 100644 --- a/lib/widgets/dropdown_api_type.dart +++ b/lib/widgets/popup_menu_api_type.dart @@ -2,10 +2,10 @@ import 'package:apidash_core/apidash_core.dart'; import 'package:apidash_design_system/apidash_design_system.dart'; import 'package:flutter/material.dart'; -class DropdownButtonAPIType extends StatelessWidget { - const DropdownButtonAPIType({ +class APITypePopupMenu extends StatelessWidget { + const APITypePopupMenu({ super.key, - this.apiType, + required this.apiType, this.onChanged, }); @@ -14,11 +14,13 @@ class DropdownButtonAPIType extends StatelessWidget { @override Widget build(BuildContext context) { - return ADDropdownButton( - value: apiType, + return ADPopupMenu( + tooltip: "Select API Type", + width: 100, + value: apiType?.label, values: APIType.values.map((e) => (e, e.label)), onChanged: onChanged, - isDense: true, + isOutlined: true, ); } } diff --git a/lib/widgets/request_widgets.dart b/lib/widgets/request_pane.dart similarity index 86% rename from lib/widgets/request_widgets.dart rename to lib/widgets/request_pane.dart index 638c1f7a..95871d53 100644 --- a/lib/widgets/request_widgets.dart +++ b/lib/widgets/request_pane.dart @@ -12,6 +12,7 @@ class RequestPane extends StatefulHookWidget { this.tabIndex, this.onPressedCodeButton, this.onTapTabBar, + required this.tabLabels, required this.children, this.showIndicators = const [false, false, false], this.showViewCodeButton, @@ -22,6 +23,7 @@ class RequestPane extends StatefulHookWidget { final int? tabIndex; final void Function()? onPressedCodeButton; final void Function(int)? onTapTabBar; + final List tabLabels; final List children; final List showIndicators; final bool? showViewCodeButton; @@ -35,7 +37,7 @@ class _RequestPaneState extends State @override Widget build(BuildContext context) { final TabController controller = useTabController( - initialLength: 3, + initialLength: widget.children.length, vsync: this, ); if (widget.tabIndex != null) { @@ -75,27 +77,21 @@ class _RequestPaneState extends State ), ), ) - : const SizedBox.shrink(), + : kVSpacer10, TabBar( key: Key(widget.selectedId!), controller: controller, overlayColor: kColorTransparentState, labelPadding: kPh2, onTap: widget.onTapTabBar, - tabs: [ - TabLabel( - text: kLabelURLParams, - showIndicator: widget.showIndicators[0], - ), - TabLabel( - text: kLabelHeaders, - showIndicator: widget.showIndicators[1], - ), - TabLabel( - text: kLabelBody, - showIndicator: widget.showIndicators[2], - ), - ], + tabs: widget.tabLabels.indexed + .map( + (e) => TabLabel( + text: e.$2, + showIndicator: widget.showIndicators[e.$1], + ), + ) + .toList(), ), kVSpacer5, Expanded( diff --git a/lib/widgets/texts.dart b/lib/widgets/texts.dart index dad5e0ce..cd43714e 100644 --- a/lib/widgets/texts.dart +++ b/lib/widgets/texts.dart @@ -3,11 +3,13 @@ import 'package:apidash_design_system/apidash_design_system.dart'; import 'package:flutter/material.dart'; import 'package:apidash/utils/utils.dart'; -class MethodBox extends StatelessWidget { - const MethodBox({ +class SidebarRequestCardTextBox extends StatelessWidget { + const SidebarRequestCardTextBox({ super.key, + required this.apiType, required this.method, }); + final APIType apiType; final HTTPVerb method; @override @@ -15,15 +17,21 @@ class MethodBox extends StatelessWidget { return SizedBox( width: 24, child: Text( - method.abbr, + switch (apiType) { + APIType.rest => method.abbr, + APIType.graphql => apiType.abbr, + }, textAlign: TextAlign.center, style: TextStyle( fontSize: 8, fontWeight: FontWeight.bold, - color: getHTTPMethodColor( - method, - brightness: Theme.of(context).brightness, - ), + color: switch (apiType) { + APIType.rest => getHTTPMethodColor( + method, + brightness: Theme.of(context).brightness, + ), + APIType.graphql => kColorGQL, + }, ), ), ); diff --git a/lib/widgets/widgets.dart b/lib/widgets/widgets.dart index 1e452d7a..59b61f54 100644 --- a/lib/widgets/widgets.dart +++ b/lib/widgets/widgets.dart @@ -24,7 +24,6 @@ export 'dropdown_content_type.dart'; export 'dropdown_formdata.dart'; export 'dropdown_http_method.dart'; export 'dropdown_import_format.dart'; -export 'dropdown_api_type.dart'; export 'editor_json.dart'; export 'editor.dart'; export 'error_message.dart'; @@ -40,12 +39,13 @@ export 'markdown.dart'; export 'menu_item_card.dart'; export 'menu_sidebar_top.dart'; export 'overlay_widget.dart'; +export 'popup_menu_api_type.dart'; export 'popup_menu_codegen.dart'; export 'popup_menu_env.dart'; export 'popup_menu_history.dart'; export 'popup_menu_uri.dart'; export 'previewer.dart'; -export 'request_widgets.dart'; +export 'request_pane.dart'; export 'response_widgets.dart'; export 'splitview_drawer.dart'; export 'splitview_dashboard.dart'; diff --git a/melos.yaml b/melos.yaml index 76344628..0759b566 100644 --- a/melos.yaml +++ b/melos.yaml @@ -8,6 +8,14 @@ scripts: run: melos exec -- "flutter analyze" description: Analyze all packages + clean: + run: melos exec -- "flutter clean" + description: Clean all packages + + build-gen: + run: melos exec -- "dart run build_runner build --delete-conflicting-outputs" + description: Run build generator for all packages + test: run: melos exec --dir-exists=test -- "flutter test --coverage" description: Run tests for all packages diff --git a/packages/apidash_core/lib/consts.dart b/packages/apidash_core/lib/consts.dart index c17ec451..c3ac388c 100644 --- a/packages/apidash_core/lib/consts.dart +++ b/packages/apidash_core/lib/consts.dart @@ -1,10 +1,12 @@ import 'dart:convert'; enum APIType { - rest("HTTP"); + rest("HTTP", "HTTP"), + graphql("GraphQL", "GQL"); - const APIType(this.label); + const APIType(this.label, this.abbr); final String label; + final String abbr; } enum HTTPVerb { diff --git a/packages/apidash_core/lib/extensions/string_extensions.dart b/packages/apidash_core/lib/extensions/string_extensions.dart index 86813788..6835a923 100644 --- a/packages/apidash_core/lib/extensions/string_extensions.dart +++ b/packages/apidash_core/lib/extensions/string_extensions.dart @@ -19,3 +19,15 @@ extension StringExtension on String { return "${substring(0, limit)}..."; } } + +extension StringOrNullExtension on String? { + bool isNullOrEmpty() { + if (this == null) { + return true; + } + if (this!.isEmpty) { + return true; + } + return false; + } +} diff --git a/packages/apidash_core/lib/models/http_request_model.dart b/packages/apidash_core/lib/models/http_request_model.dart index 90efe4be..cc2d7411 100644 --- a/packages/apidash_core/lib/models/http_request_model.dart +++ b/packages/apidash_core/lib/models/http_request_model.dart @@ -7,7 +7,6 @@ import '../utils/utils.dart' import '../consts.dart'; part 'http_request_model.freezed.dart'; - part 'http_request_model.g.dart'; @freezed @@ -27,6 +26,7 @@ class HttpRequestModel with _$HttpRequestModel { List? isParamEnabledList, @Default(ContentType.json) ContentType bodyContentType, String? body, + String? query, List? formData, }) = _HttpRequestModel; @@ -61,6 +61,7 @@ class HttpRequestModel with _$HttpRequestModel { kMethodsWithBody.contains(method) && hasFormDataContentType && formDataMapList.isNotEmpty; + bool get hasQuery => query?.isNotEmpty ?? false; List get formDataList => formData ?? []; List> get formDataMapList => rowsToFormDataMapList(formDataList) ?? []; diff --git a/packages/apidash_core/lib/models/http_request_model.freezed.dart b/packages/apidash_core/lib/models/http_request_model.freezed.dart index 23c9dbbb..38cd0132 100644 --- a/packages/apidash_core/lib/models/http_request_model.freezed.dart +++ b/packages/apidash_core/lib/models/http_request_model.freezed.dart @@ -28,6 +28,7 @@ mixin _$HttpRequestModel { List? get isParamEnabledList => throw _privateConstructorUsedError; ContentType get bodyContentType => throw _privateConstructorUsedError; String? get body => throw _privateConstructorUsedError; + String? get query => throw _privateConstructorUsedError; List? get formData => throw _privateConstructorUsedError; /// Serializes this HttpRequestModel to a JSON map. @@ -55,6 +56,7 @@ abstract class $HttpRequestModelCopyWith<$Res> { List? isParamEnabledList, ContentType bodyContentType, String? body, + String? query, List? formData}); } @@ -81,6 +83,7 @@ class _$HttpRequestModelCopyWithImpl<$Res, $Val extends HttpRequestModel> Object? isParamEnabledList = freezed, Object? bodyContentType = null, Object? body = freezed, + Object? query = freezed, Object? formData = freezed, }) { return _then(_value.copyWith( @@ -116,6 +119,10 @@ class _$HttpRequestModelCopyWithImpl<$Res, $Val extends HttpRequestModel> ? _value.body : body // ignore: cast_nullable_to_non_nullable as String?, + query: freezed == query + ? _value.query + : query // ignore: cast_nullable_to_non_nullable + as String?, formData: freezed == formData ? _value.formData : formData // ignore: cast_nullable_to_non_nullable @@ -141,6 +148,7 @@ abstract class _$$HttpRequestModelImplCopyWith<$Res> List? isParamEnabledList, ContentType bodyContentType, String? body, + String? query, List? formData}); } @@ -165,6 +173,7 @@ class __$$HttpRequestModelImplCopyWithImpl<$Res> Object? isParamEnabledList = freezed, Object? bodyContentType = null, Object? body = freezed, + Object? query = freezed, Object? formData = freezed, }) { return _then(_$HttpRequestModelImpl( @@ -200,6 +209,10 @@ class __$$HttpRequestModelImplCopyWithImpl<$Res> ? _value.body : body // ignore: cast_nullable_to_non_nullable as String?, + query: freezed == query + ? _value.query + : query // ignore: cast_nullable_to_non_nullable + as String?, formData: freezed == formData ? _value._formData : formData // ignore: cast_nullable_to_non_nullable @@ -221,6 +234,7 @@ class _$HttpRequestModelImpl extends _HttpRequestModel { final List? isParamEnabledList, this.bodyContentType = ContentType.json, this.body, + this.query, final List? formData}) : _headers = headers, _params = params, @@ -285,6 +299,8 @@ class _$HttpRequestModelImpl extends _HttpRequestModel { final ContentType bodyContentType; @override final String? body; + @override + final String? query; final List? _formData; @override List? get formData { @@ -297,7 +313,7 @@ class _$HttpRequestModelImpl extends _HttpRequestModel { @override String toString() { - return 'HttpRequestModel(method: $method, url: $url, headers: $headers, params: $params, isHeaderEnabledList: $isHeaderEnabledList, isParamEnabledList: $isParamEnabledList, bodyContentType: $bodyContentType, body: $body, formData: $formData)'; + return 'HttpRequestModel(method: $method, url: $url, headers: $headers, params: $params, isHeaderEnabledList: $isHeaderEnabledList, isParamEnabledList: $isParamEnabledList, bodyContentType: $bodyContentType, body: $body, query: $query, formData: $formData)'; } @override @@ -316,6 +332,7 @@ class _$HttpRequestModelImpl extends _HttpRequestModel { (identical(other.bodyContentType, bodyContentType) || other.bodyContentType == bodyContentType) && (identical(other.body, body) || other.body == body) && + (identical(other.query, query) || other.query == query) && const DeepCollectionEquality().equals(other._formData, _formData)); } @@ -331,6 +348,7 @@ class _$HttpRequestModelImpl extends _HttpRequestModel { const DeepCollectionEquality().hash(_isParamEnabledList), bodyContentType, body, + query, const DeepCollectionEquality().hash(_formData)); /// Create a copy of HttpRequestModel @@ -360,6 +378,7 @@ abstract class _HttpRequestModel extends HttpRequestModel { final List? isParamEnabledList, final ContentType bodyContentType, final String? body, + final String? query, final List? formData}) = _$HttpRequestModelImpl; const _HttpRequestModel._() : super._(); @@ -383,6 +402,8 @@ abstract class _HttpRequestModel extends HttpRequestModel { @override String? get body; @override + String? get query; + @override List? get formData; /// Create a copy of HttpRequestModel diff --git a/packages/apidash_core/lib/models/http_request_model.g.dart b/packages/apidash_core/lib/models/http_request_model.g.dart index da13d3ae..29005786 100644 --- a/packages/apidash_core/lib/models/http_request_model.g.dart +++ b/packages/apidash_core/lib/models/http_request_model.g.dart @@ -29,6 +29,7 @@ _$HttpRequestModelImpl _$$HttpRequestModelImplFromJson(Map json) => $enumDecodeNullable(_$ContentTypeEnumMap, json['bodyContentType']) ?? ContentType.json, body: json['body'] as String?, + query: json['query'] as String?, formData: (json['formData'] as List?) ?.map((e) => FormDataModel.fromJson(Map.from(e as Map))) @@ -46,6 +47,7 @@ Map _$$HttpRequestModelImplToJson( 'isParamEnabledList': instance.isParamEnabledList, 'bodyContentType': _$ContentTypeEnumMap[instance.bodyContentType]!, 'body': instance.body, + 'query': instance.query, 'formData': instance.formData?.map((e) => e.toJson()).toList(), }; diff --git a/packages/apidash_core/lib/models/http_response_model.dart b/packages/apidash_core/lib/models/http_response_model.dart index 9a3ff7a2..914aaa57 100644 --- a/packages/apidash_core/lib/models/http_response_model.dart +++ b/packages/apidash_core/lib/models/http_response_model.dart @@ -10,7 +10,6 @@ import '../utils/utils.dart'; import '../consts.dart'; part 'http_response_model.freezed.dart'; - part 'http_response_model.g.dart'; class Uint8ListConverter implements JsonConverter?> { diff --git a/packages/apidash_core/lib/services/http_service.dart b/packages/apidash_core/lib/services/http_service.dart index 2fb6aa7a..0bbc0501 100644 --- a/packages/apidash_core/lib/services/http_service.dart +++ b/packages/apidash_core/lib/services/http_service.dart @@ -12,6 +12,7 @@ typedef HttpResponse = http.Response; Future<(HttpResponse?, Duration?, String?)> request( String requestId, + APIType apiType, HttpRequestModel requestModel, { SupportedUriSchemes defaultUriScheme = kDefaultUriScheme, bool noSSL = false, @@ -28,74 +29,96 @@ Future<(HttpResponse?, Duration?, String?)> request( if (uriRec.$1 != null) { Uri requestUrl = uriRec.$1!; Map headers = requestModel.enabledHeadersMap; - HttpResponse response; + HttpResponse? response; String? body; try { Stopwatch stopwatch = Stopwatch()..start(); - var isMultiPartRequest = - requestModel.bodyContentType == ContentType.formdata; + if (apiType == APIType.rest) { + var isMultiPartRequest = + requestModel.bodyContentType == ContentType.formdata; - if (kMethodsWithBody.contains(requestModel.method)) { - var requestBody = requestModel.body; - if (requestBody != null && !isMultiPartRequest) { + if (kMethodsWithBody.contains(requestModel.method)) { + var requestBody = requestModel.body; + if (requestBody != null && !isMultiPartRequest) { + var contentLength = utf8.encode(requestBody).length; + if (contentLength > 0) { + body = requestBody; + headers[HttpHeaders.contentLengthHeader] = + contentLength.toString(); + if (!requestModel.hasContentTypeHeader) { + headers[HttpHeaders.contentTypeHeader] = + requestModel.bodyContentType.header; + } + } + } + if (isMultiPartRequest) { + var multiPartRequest = http.MultipartRequest( + requestModel.method.name.toUpperCase(), + requestUrl, + ); + multiPartRequest.headers.addAll(headers); + for (var formData in requestModel.formDataList) { + if (formData.type == FormDataType.text) { + multiPartRequest.fields.addAll({formData.name: formData.value}); + } else { + multiPartRequest.files.add( + await http.MultipartFile.fromPath( + formData.name, + formData.value, + ), + ); + } + } + http.StreamedResponse multiPartResponse = + await multiPartRequest.send(); + stopwatch.stop(); + http.Response convertedMultiPartResponse = + await convertStreamedResponse(multiPartResponse); + return (convertedMultiPartResponse, stopwatch.elapsed, null); + } + } + switch (requestModel.method) { + case HTTPVerb.get: + response = await client.get(requestUrl, headers: headers); + break; + case HTTPVerb.head: + response = await client.head(requestUrl, headers: headers); + break; + case HTTPVerb.post: + response = + await client.post(requestUrl, headers: headers, body: body); + break; + case HTTPVerb.put: + response = + await client.put(requestUrl, headers: headers, body: body); + break; + case HTTPVerb.patch: + response = + await client.patch(requestUrl, headers: headers, body: body); + break; + case HTTPVerb.delete: + response = + await client.delete(requestUrl, headers: headers, body: body); + break; + } + } + if (apiType == APIType.graphql) { + var requestBody = getGraphQLBody(requestModel); + if (requestBody != null) { var contentLength = utf8.encode(requestBody).length; if (contentLength > 0) { body = requestBody; headers[HttpHeaders.contentLengthHeader] = contentLength.toString(); if (!requestModel.hasContentTypeHeader) { - headers[HttpHeaders.contentTypeHeader] = - requestModel.bodyContentType.header; + headers[HttpHeaders.contentTypeHeader] = ContentType.json.header; } } } - if (isMultiPartRequest) { - var multiPartRequest = http.MultipartRequest( - requestModel.method.name.toUpperCase(), - requestUrl, - ); - multiPartRequest.headers.addAll(headers); - for (var formData in requestModel.formDataList) { - if (formData.type == FormDataType.text) { - multiPartRequest.fields.addAll({formData.name: formData.value}); - } else { - multiPartRequest.files.add( - await http.MultipartFile.fromPath( - formData.name, - formData.value, - ), - ); - } - } - http.StreamedResponse multiPartResponse = - await multiPartRequest.send(); - stopwatch.stop(); - http.Response convertedMultiPartResponse = - await convertStreamedResponse(multiPartResponse); - return (convertedMultiPartResponse, stopwatch.elapsed, null); - } - } - switch (requestModel.method) { - case HTTPVerb.get: - response = await client.get(requestUrl, headers: headers); - break; - case HTTPVerb.head: - response = await client.head(requestUrl, headers: headers); - break; - case HTTPVerb.post: - response = - await client.post(requestUrl, headers: headers, body: body); - break; - case HTTPVerb.put: - response = await client.put(requestUrl, headers: headers, body: body); - break; - case HTTPVerb.patch: - response = - await client.patch(requestUrl, headers: headers, body: body); - break; - case HTTPVerb.delete: - response = - await client.delete(requestUrl, headers: headers, body: body); - break; + response = await client.post( + requestUrl, + headers: headers, + body: body, + ); } stopwatch.stop(); return (response, stopwatch.elapsed, null); diff --git a/packages/apidash_core/lib/utils/graphql_utils.dart b/packages/apidash_core/lib/utils/graphql_utils.dart new file mode 100644 index 00000000..afa874bf --- /dev/null +++ b/packages/apidash_core/lib/utils/graphql_utils.dart @@ -0,0 +1,11 @@ +import '../consts.dart'; +import '../models/models.dart'; + +String? getGraphQLBody(HttpRequestModel httpRequestModel) { + if (httpRequestModel.hasQuery) { + return kJsonEncoder.convert({ + "query": httpRequestModel.query, + }); + } + return null; +} diff --git a/packages/apidash_core/lib/utils/http_request_utils.dart b/packages/apidash_core/lib/utils/http_request_utils.dart index 2e1daea5..7a912ac1 100644 --- a/packages/apidash_core/lib/utils/http_request_utils.dart +++ b/packages/apidash_core/lib/utils/http_request_utils.dart @@ -1,5 +1,8 @@ +import 'package:apidash_core/consts.dart'; import 'package:collection/collection.dart'; import 'package:seed/seed.dart'; +import '../models/models.dart'; +import 'graphql_utils.dart'; Map? rowsToMap( List? kvRows, { @@ -88,3 +91,13 @@ List? getEnabledRows( rows.where((element) => isRowEnabledList[rows.indexOf(element)]).toList(); return finalRows == [] ? null : finalRows; } + +String? getRequestBody(APIType type, HttpRequestModel httpRequestModel) { + return switch (type) { + APIType.rest => + (httpRequestModel.hasJsonData || httpRequestModel.hasTextData) + ? httpRequestModel.body + : null, + APIType.graphql => getGraphQLBody(httpRequestModel), + }; +} diff --git a/packages/apidash_core/lib/utils/utils.dart b/packages/apidash_core/lib/utils/utils.dart index 201d4d64..f39e563d 100644 --- a/packages/apidash_core/lib/utils/utils.dart +++ b/packages/apidash_core/lib/utils/utils.dart @@ -1,4 +1,5 @@ export 'content_type_utils.dart'; +export 'graphql_utils.dart'; export 'http_request_utils.dart'; export 'http_response_utils.dart'; export 'string_utils.dart'; diff --git a/packages/apidash_core/pubspec.yaml b/packages/apidash_core/pubspec.yaml index 78c5bfb2..d29330e4 100644 --- a/packages/apidash_core/pubspec.yaml +++ b/packages/apidash_core/pubspec.yaml @@ -21,7 +21,7 @@ dependencies: path: ../postman insomnia_collection: path: ../insomnia_collection - seed: ^0.0.2 + seed: ^0.0.3 xml: ^6.3.0 dev_dependencies: @@ -30,4 +30,5 @@ dev_dependencies: build_runner: ^2.4.12 flutter_lints: ^4.0.0 freezed: ^2.5.7 + json_serializable: ^6.7.1 test: ^1.25.2 diff --git a/packages/apidash_core/pubspec_overrides.yaml b/packages/apidash_core/pubspec_overrides.yaml index e1c03143..7c2883d9 100644 --- a/packages/apidash_core/pubspec_overrides.yaml +++ b/packages/apidash_core/pubspec_overrides.yaml @@ -1,6 +1,8 @@ -# melos_managed_dependency_overrides: seed,curl_parser +# melos_managed_dependency_overrides: curl_parser,postman,seed dependency_overrides: curl_parser: path: ../curl_parser + postman: + path: ../postman seed: path: ../seed diff --git a/packages/apidash_design_system/lib/tokens/colors.dart b/packages/apidash_design_system/lib/tokens/colors.dart index 287b70b6..7d873fcd 100644 --- a/packages/apidash_design_system/lib/tokens/colors.dart +++ b/packages/apidash_design_system/lib/tokens/colors.dart @@ -24,6 +24,8 @@ final kColorHttpMethodPut = Colors.amber.shade900; final kColorHttpMethodPatch = kColorHttpMethodPut; final kColorHttpMethodDelete = Colors.red.shade800; +final kColorGQL = Colors.pink.shade600; + const kHintOpacity = 0.6; const kForegroundOpacity = 0.05; const kOverlayBackgroundOpacity = 0.5; diff --git a/packages/apidash_design_system/lib/tokens/measurements.dart b/packages/apidash_design_system/lib/tokens/measurements.dart index 4be0ec8a..638e5413 100644 --- a/packages/apidash_design_system/lib/tokens/measurements.dart +++ b/packages/apidash_design_system/lib/tokens/measurements.dart @@ -90,6 +90,7 @@ const kHSpacer10 = SizedBox(width: 10); const kHSpacer12 = SizedBox(width: 12); const kHSpacer20 = SizedBox(width: 20); const kHSpacer40 = SizedBox(width: 40); +const kVSpacer3 = SizedBox(height: 3); const kVSpacer5 = SizedBox(height: 5); const kVSpacer8 = SizedBox(height: 8); const kVSpacer10 = SizedBox(height: 10); diff --git a/packages/apidash_design_system/lib/widgets/popup_menu.dart b/packages/apidash_design_system/lib/widgets/popup_menu.dart index 06adea10..266028ee 100644 --- a/packages/apidash_design_system/lib/widgets/popup_menu.dart +++ b/packages/apidash_design_system/lib/widgets/popup_menu.dart @@ -10,6 +10,7 @@ class ADPopupMenu extends StatelessWidget { this.tooltip, this.width, this.isOutlined = false, + this.borderColor, }); final String? value; @@ -18,6 +19,7 @@ class ADPopupMenu extends StatelessWidget { final String? tooltip; final double? width; final bool isOutlined; + final Color? borderColor; @override Widget build(BuildContext context) { @@ -62,7 +64,8 @@ class ADPopupMenu extends StatelessWidget { return Container( decoration: BoxDecoration( border: Border.all( - color: Theme.of(context).colorScheme.onSurface, + color: borderColor ?? + Theme.of(context).colorScheme.surfaceContainerHighest, ), borderRadius: kBorderRadius8, ), diff --git a/packages/curl_parser/.gitignore b/packages/curl_parser/.gitignore index 04a9b6ff..9e94da4c 100644 --- a/packages/curl_parser/.gitignore +++ b/packages/curl_parser/.gitignore @@ -30,3 +30,4 @@ build/ .vscode/ coverage/ +pubspec_overrides.yaml diff --git a/packages/curl_parser/.pubignore b/packages/curl_parser/.pubignore index 6cea8211..b36f11f5 100644 --- a/packages/curl_parser/.pubignore +++ b/packages/curl_parser/.pubignore @@ -4,3 +4,4 @@ melos_curl_parser.iml build/ coverage/ test/ +pubspec_overrides.yaml diff --git a/packages/curl_parser/CHANGELOG.md b/packages/curl_parser/CHANGELOG.md index 67747c87..a1e45ef0 100644 --- a/packages/curl_parser/CHANGELOG.md +++ b/packages/curl_parser/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.1.2 + +- Bump dependencies. + ## 0.1.1 - Add formdata support and new test cases. diff --git a/packages/curl_parser/pubspec.yaml b/packages/curl_parser/pubspec.yaml index 1f3c969f..82e795ca 100644 --- a/packages/curl_parser/pubspec.yaml +++ b/packages/curl_parser/pubspec.yaml @@ -1,6 +1,6 @@ name: curl_parser description: Parse cURL command to Dart object and convert Dart object to cURL command. -version: 0.1.1 +version: 0.1.2 homepage: https://github.com/foss42/apidash/tree/main/packages/curl_parser repository: https://github.com/foss42/apidash/tree/main/packages/curl_parser issue_tracker: https://github.com/foss42/apidash/issues @@ -19,7 +19,7 @@ environment: dependencies: args: ^2.5.0 equatable: ^2.0.5 - seed: ^0.0.1 + seed: ^0.0.3 shlex: ^2.0.2 dev_dependencies: diff --git a/packages/curl_parser/pubspec_overrides.yaml b/packages/curl_parser/pubspec_overrides.yaml deleted file mode 100644 index 8fa1ad79..00000000 --- a/packages/curl_parser/pubspec_overrides.yaml +++ /dev/null @@ -1,4 +0,0 @@ -# melos_managed_dependency_overrides: seed -dependency_overrides: - seed: - path: ../seed diff --git a/packages/seed/CHANGELOG.md b/packages/seed/CHANGELOG.md index f64fbe64..1415857f 100644 --- a/packages/seed/CHANGELOG.md +++ b/packages/seed/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.0.3 + +- Fix: Add `json_serializable` under dev_dependencies. + ## 0.0.2 - Fix pubspec dependency for freezed_annotation. diff --git a/packages/seed/pubspec.yaml b/packages/seed/pubspec.yaml index 6f52781a..03d4ec9b 100644 --- a/packages/seed/pubspec.yaml +++ b/packages/seed/pubspec.yaml @@ -1,6 +1,6 @@ name: seed description: Seed is a foundational package designed to provide reusable building blocks for API Dash projects. -version: 0.0.2 +version: 0.0.3 homepage: https://github.com/foss42/apidash/tree/main/packages/seed repository: https://github.com/foss42/apidash/tree/main/packages/seed issue_tracker: https://github.com/foss42/apidash/issues @@ -15,5 +15,6 @@ dependencies: dev_dependencies: build_runner: ^2.4.12 freezed: ^2.5.7 + json_serializable: ^6.7.1 lints: ^4.0.0 test: ^1.24.0 diff --git a/pubspec.lock b/pubspec.lock index 5d3b2a79..fdf2db3c 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -306,7 +306,7 @@ packages: path: "packages/curl_parser" relative: true source: path - version: "0.1.1" + version: "0.1.2" dart_style: dependency: "direct main" description: @@ -1379,10 +1379,10 @@ packages: dependency: transitive description: name: seed - sha256: "5c5ac5d73bf94e4b207d8c283903fc2b62ca8015e519b99949a8389605ee0eef" + sha256: "0d74a46abd169c96a73d9dec4739e6623021915661beadf265e885bb1eafd214" url: "https://pub.dev" source: hosted - version: "0.0.2" + version: "0.0.3" shared_preferences: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index ce0bc937..e2d00bad 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: apidash description: API Dash is a beautiful open-source cross-platform API Client built using Flutter which can help you easily create & customize your API requests, visually inspect responses and generate Dart code on the go. publish_to: "none" -version: 0.4.0+4 +version: 0.5.0+5 environment: sdk: ">=3.0.0 <4.0.0" diff --git a/test/models/history_models.dart b/test/models/history_models.dart index 5deeb655..e33c2f99 100644 --- a/test/models/history_models.dart +++ b/test/models/history_models.dart @@ -9,6 +9,7 @@ import 'http_response_models.dart'; final historyMetaModel1 = HistoryMetaModel( historyId: 'historyId1', requestId: 'requestId1', + apiType: APIType.rest, url: 'https://api.apidash.dev/humanize/social', method: HTTPVerb.get, timeStamp: DateTime(2024, 1, 1), @@ -26,6 +27,7 @@ final historyRequestModel1 = HistoryRequestModel( final historyMetaModel2 = HistoryMetaModel( historyId: 'historyId2', requestId: 'requestId2', + apiType: APIType.rest, url: 'https://api.apidash.dev/case/lower', method: HTTPVerb.post, timeStamp: DateTime(2024, 1, 1), @@ -43,6 +45,7 @@ final historyRequestModel2 = HistoryRequestModel( final Map historyMetaModelJson1 = { "historyId": "historyId1", "requestId": "requestId1", + "apiType": "rest", "name": "", "url": "https://api.apidash.dev/humanize/social", "method": "get", @@ -60,6 +63,7 @@ final Map historyRequestModelJson1 = { final Map historyMetaModelJson2 = { "historyId": "historyId2", "requestId": "requestId2", + "apiType": "rest", "name": "", "url": "https://api.apidash.dev/case/lower", "method": "post", diff --git a/test/models/http_request_models.dart b/test/models/http_request_models.dart index f50dd2df..e3448c30 100644 --- a/test/models/http_request_models.dart +++ b/test/models/http_request_models.dart @@ -388,6 +388,7 @@ const httpRequestModelGet4Json = { "isParamEnabledList": null, "bodyContentType": "json", "body": null, + "query": null, "formData": null }; @@ -408,6 +409,7 @@ const httpRequestModelPost10Json = { "body": '''{ "text": "I LOVE Flutter" }''', + "query": null, 'formData': [ {'name': 'token', 'value': 'xyz', 'type': 'text'}, {'name': 'imfile', 'value': '/Documents/up/1.png', 'type': 'file'} diff --git a/test/models/response_model_test.dart b/test/models/response_model_test.dart index 10e9f2e1..a568e383 100644 --- a/test/models/response_model_test.dart +++ b/test/models/response_model_test.dart @@ -16,6 +16,7 @@ void main() { test('Testing fromResponse', () async { (HttpResponse?, Duration?, String?)? responseRec = await request( requestModelGet1.id, + requestModelGet1.apiType, requestModelGet1.httpRequestModel!, defaultUriScheme: kDefaultUriScheme, noSSL: false, @@ -33,6 +34,7 @@ void main() { test('Testing fromResponse for contentType not Json', () async { (HttpResponse?, Duration?, String?)? responseRec = await request( requestModelGet13.id, + requestModelGet1.apiType, requestModelGet13.httpRequestModel!, defaultUriScheme: kDefaultUriScheme, noSSL: false, @@ -48,6 +50,7 @@ void main() { test('Testing fromResponse for Bad SSL with certificate check', () async { (HttpResponse?, Duration?, String?)? responseRec = await request( requestModelGetBadSSL.id, + requestModelGet1.apiType, requestModelGetBadSSL.httpRequestModel!, defaultUriScheme: kDefaultUriScheme, noSSL: false, @@ -59,6 +62,7 @@ void main() { test('Testing fromResponse for Bad SSL with no certificate check', () async { (HttpResponse?, Duration?, String?)? responseRec = await request( requestModelGetBadSSL.id, + requestModelGet1.apiType, requestModelGetBadSSL.httpRequestModel!, defaultUriScheme: kDefaultUriScheme, noSSL: true, diff --git a/test/utils/envvar_utils_test.dart b/test/utils/envvar_utils_test.dart index 5c166566..a6c9ee03 100644 --- a/test/utils/envvar_utils_test.dart +++ b/test/utils/envvar_utils_test.dart @@ -56,20 +56,19 @@ const activeEnvVars = [ void main() { group("Testing getEnvironmentTitle function", () { - String titleUntitled = "untitled"; test("Testing getEnvironmentTitle with null", () { String? envName1; - expect(getEnvironmentTitle(envName1), titleUntitled); + expect(getEnvironmentTitle(envName1), kUntitled); }); test("Testing getEnvironmentTitle with empty string", () { String envName2 = ""; - expect(getEnvironmentTitle(envName2), titleUntitled); + expect(getEnvironmentTitle(envName2), kUntitled); }); test("Testing getEnvironmentTitle with trimmable string", () { String envName3 = " "; - expect(getEnvironmentTitle(envName3), titleUntitled); + expect(getEnvironmentTitle(envName3), kUntitled); }); test("Testing getEnvironmentTitle with non-empty string", () { diff --git a/test/utils/http_utils_test.dart b/test/utils/http_utils_test.dart index 0f777ab1..7ffc7551 100644 --- a/test/utils/http_utils_test.dart +++ b/test/utils/http_utils_test.dart @@ -5,15 +5,14 @@ import 'package:apidash/consts.dart'; void main() { group("Testing getRequestTitleFromUrl function", () { - String titleUntitled = "untitled"; test('Testing getRequestTitleFromUrl using url1', () { String url1 = ""; - expect(getRequestTitleFromUrl(url1), titleUntitled); + expect(getRequestTitleFromUrl(url1), kUntitled); }); test('Testing getRequestTitleFromUrl using url2', () { String url2 = " "; - expect(getRequestTitleFromUrl(url2), titleUntitled); + expect(getRequestTitleFromUrl(url2), kUntitled); }); test('Testing getRequestTitleFromUrl using url3', () { @@ -30,11 +29,11 @@ void main() { test('Testing getRequestTitleFromUrl using url5', () { String url5 = "http://"; - expect(getRequestTitleFromUrl(url5), titleUntitled); + expect(getRequestTitleFromUrl(url5), kUntitled); }); test('Testing getRequestTitleFromUrl for null value', () { - expect(getRequestTitleFromUrl(null), titleUntitled); + expect(getRequestTitleFromUrl(null), kUntitled); }); }); diff --git a/test/widgets/card_history_request_test.dart b/test/widgets/card_history_request_test.dart index 7c811a88..ba3a074b 100644 --- a/test/widgets/card_history_request_test.dart +++ b/test/widgets/card_history_request_test.dart @@ -13,6 +13,7 @@ void main() { final mockModel = HistoryMetaModel( historyId: 'historyId', requestId: 'requestId', + apiType: APIType.rest, url: 'https://api.apidash.dev', method: HTTPVerb.get, timeStamp: DateTime.now(), diff --git a/test/widgets/card_sidebar_history_test.dart b/test/widgets/card_sidebar_history_test.dart index a4d3c0a1..e3022dd3 100644 --- a/test/widgets/card_sidebar_history_test.dart +++ b/test/widgets/card_sidebar_history_test.dart @@ -11,6 +11,7 @@ void main() { HistoryMetaModel( historyId: 'historyId', requestId: 'requestId', + apiType: APIType.rest, url: 'https://api.apidash.dev', method: HTTPVerb.get, timeStamp: DateTime.now(), @@ -30,6 +31,7 @@ void main() { SidebarHistoryCard( id: '1', models: sampleModels, + apiType: APIType.rest, method: HTTPVerb.get, onTap: () { changedValue = 'Tapped'; @@ -68,6 +70,7 @@ void main() { children: [ SidebarHistoryCard( id: '1', + apiType: APIType.rest, models: sampleModels, method: HTTPVerb.get, onTap: () { diff --git a/test/widgets/card_sidebar_request_test.dart b/test/widgets/card_sidebar_request_test.dart index 2d1f2be9..d31b9061 100644 --- a/test/widgets/card_sidebar_request_test.dart +++ b/test/widgets/card_sidebar_request_test.dart @@ -16,6 +16,7 @@ void main() { children: [ SidebarRequestCard( id: '23', + apiType: APIType.rest, selectedId: '2', url: 'https://api.apidash.dev', method: HTTPVerb.get, @@ -61,6 +62,7 @@ void main() { children: [ SidebarRequestCard( id: '2', + apiType: APIType.rest, selectedId: '2', editRequestId: '2', url: 'https://api.apidash.dev', diff --git a/test/widgets/editor_test.dart b/test/widgets/editor_test.dart index 238b432c..12b4991b 100644 --- a/test/widgets/editor_test.dart +++ b/test/widgets/editor_test.dart @@ -1,3 +1,4 @@ +import 'package:apidash/consts.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -28,7 +29,7 @@ void main() { expect(find.byType(TextFormField), findsOneWidget); expect(find.byKey(const Key("2")), findsOneWidget); - expect(find.text('Enter content (body)'), findsOneWidget); + expect(find.text(kHintContent), findsOneWidget); var txtForm = find.byKey(const Key("2")); await tester.enterText(txtForm, 'entering 123 for testing content body'); await tester.pump(); @@ -40,7 +41,7 @@ void main() { await tester.pump(); await tester.pumpAndSettle(); - expect(changedValue, 'entering 123 for testing content body '); + expect(changedValue, 'entering 123 for testing content body '); }); testWidgets('Testing Editor Dark theme', (tester) async { dynamic changedValue; @@ -66,7 +67,7 @@ void main() { expect(find.text('initial'), findsOneWidget); expect(find.byType(TextFormField), findsOneWidget); expect(find.byKey(const Key("2")), findsOneWidget); - expect(find.text('Enter content (body)'), findsOneWidget); + expect(find.text(kHintContent), findsOneWidget); var txtForm = find.byKey(const Key("2")); await tester.enterText(txtForm, 'entering 123 for testing content body'); await tester.pump(); @@ -78,6 +79,6 @@ void main() { await tester.pump(); await tester.pumpAndSettle(); - expect(changedValue, 'entering 123 for testing content body '); + expect(changedValue, 'entering 123 for testing content body '); }); } diff --git a/test/widgets/request_widgets_test.dart b/test/widgets/request_pane_test.dart similarity index 94% rename from test/widgets/request_widgets_test.dart rename to test/widgets/request_pane_test.dart index 26322c24..8522c020 100644 --- a/test/widgets/request_widgets_test.dart +++ b/test/widgets/request_pane_test.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:apidash/widgets/request_widgets.dart'; +import 'package:apidash/widgets/request_pane.dart'; import '../extensions/widget_tester_extensions.dart'; import '../test_consts.dart'; @@ -15,8 +15,9 @@ void main() { body: RequestPane( selectedId: '1', codePaneVisible: true, - children: const [Text('abc'), Text('xyz'), Text('mno')], + tabLabels: const ['URL Params', 'Headers', 'Body'], onPressedCodeButton: () {}, + children: const [Text('abc'), Text('xyz'), Text('mno')], ), ), ), @@ -47,6 +48,7 @@ void main() { codePaneVisible: true, onPressedCodeButton: () {}, tabIndex: 1, + tabLabels: const ['URL Params', 'Headers', 'Body'], children: const [Text('abc'), Text('xyz'), Text('mno')], ), ), @@ -78,6 +80,7 @@ void main() { codePaneVisible: false, onPressedCodeButton: () {}, tabIndex: 2, + tabLabels: const ['URL Params', 'Headers', 'Body'], children: const [Text('abc'), Text('xyz'), Text('mno')], ), ), @@ -111,6 +114,7 @@ void main() { onTapTabBar: (value) { computedTabIndex = value; }, + tabLabels: const ['URL Params', 'Headers', 'Body'], children: const [Text('abc'), Text('xyz'), Text('mno')], ), ), diff --git a/test/widgets/texts_test.dart b/test/widgets/texts_test.dart index 303a6d0f..79107fe1 100644 --- a/test/widgets/texts_test.dart +++ b/test/widgets/texts_test.dart @@ -8,12 +8,16 @@ import 'package:apidash/widgets/texts.dart'; void main() { testWidgets('Testing when method is GET', (tester) async { var methodGet = HTTPVerb.get; + var apiType = APIType.rest; await tester.pumpWidget( MaterialApp( title: 'Texts', theme: ThemeData(brightness: Brightness.light), home: Scaffold( - body: MethodBox(method: methodGet), + body: SidebarRequestCardTextBox( + apiType: apiType, + method: methodGet, + ), ), ), ); @@ -28,12 +32,16 @@ void main() { testWidgets('Testing when method is DELETE', (tester) async { var methodDel = HTTPVerb.delete; + var apiType = APIType.rest; await tester.pumpWidget( MaterialApp( title: 'Texts', theme: ThemeData(brightness: Brightness.dark), home: Scaffold( - body: MethodBox(method: methodDel), + body: SidebarRequestCardTextBox( + apiType: apiType, + method: methodDel, + ), ), ), );