Merge branch 'main' into add-feat-insomia

This commit is contained in:
Ashita Prasad
2025-01-13 02:10:58 +05:30
committed by GitHub
82 changed files with 1029 additions and 412 deletions

View File

@ -1,6 +1,21 @@
# API Dash ⚡️ Changelog # 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: 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 9. Dart http codegen has been rewritten using dart code builder
10. Ability to override request contenttype 10. Ability to override request contenttype
11. More header suggestions 11. More header suggestions
12. Mobile platform support
## v0.3.0 [29-11-2023] ## v0.3.0 [29-11-2023]

View File

@ -76,11 +76,31 @@ API Dash can be downloaded from the links below:
## List of Features ## 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 & Customize API Requests**
- Create different types of HTTP requests (`GET`, `HEAD`, `POST`, `PATCH`, `PUT` and `DELETE`). - 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`. - 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. - 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** **💼 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/javascript` | `.js` | |
| `text/markdown` | `.md` | | | `text/markdown` | `.md` | |
## What's new in v0.3.0? ## What's new in v0.5.0?
Visit [CHANGELOG.md](CHANGELOG.md) Visit [CHANGELOG.md](CHANGELOG.md)

View File

@ -5,14 +5,14 @@
- [x] Remaining Code Generators (https://github.com/foss42/apidash/discussions/80) - [x] Remaining Code Generators (https://github.com/foss42/apidash/discussions/80)
- [x] Environment Variables (https://github.com/foss42/apidash/issues/25) - [x] Environment Variables (https://github.com/foss42/apidash/issues/25)
- [x] Integration Testing (https://github.com/foss42/apidash/issues/119) - [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) - [ ] WebSocket support (https://github.com/foss42/apidash/issues/15)
- [ ] SSE support (https://github.com/foss42/apidash/issues/116) - [ ] SSE support (https://github.com/foss42/apidash/issues/116)
- [ ] MQTT support (https://github.com/foss42/apidash/issues/115) - [ ] 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) - [ ] 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] - [ ] 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 - [ ] Importers
- [ ] OpenAPI (https://github.com/foss42/apidash/issues/121) - [ ] OpenAPI (https://github.com/foss42/apidash/issues/121)
- [ ] Insomnia (https://github.com/foss42/apidash/issues/125) - [ ] Insomnia (https://github.com/foss42/apidash/issues/125)

View File

@ -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) - [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) - [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) - [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) - Import Request for cURL (Contributions Welcome!)
- How to use Code Generator (Doc TODO) - [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) - [API Dash on Mobile](https://github.com/foss42/apidash/blob/main/doc/user_guide/req_user_guide.md)

View File

@ -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

View File

@ -26,7 +26,7 @@ class ApidashTestEnvHelper {
Future<void> renameNewEnvironment(String newEnvName) async { Future<void> renameNewEnvironment(String newEnvName) async {
Finder envItems = find.byType(EnvironmentItem); Finder envItems = find.byType(EnvironmentItem);
Finder newEnvItem = envItems.at(1); Finder newEnvItem = envItems.at(1);
expect(find.descendant(of: newEnvItem, matching: find.text("untitled")), expect(find.descendant(of: newEnvItem, matching: find.text(kUntitled)),
findsOneWidget); findsOneWidget);
Finder itemCardMenu = Finder itemCardMenu =
find.descendant(of: newEnvItem, matching: find.byType(ItemCardMenu)); find.descendant(of: newEnvItem, matching: find.byType(ItemCardMenu));

View File

@ -29,7 +29,7 @@ class ApidashTestRequestHelper {
Future<void> renameNewRequest(String newReqName) async { Future<void> renameNewRequest(String newReqName) async {
Finder reqItems = find.byType(RequestItem); Finder reqItems = find.byType(RequestItem);
Finder newReqItem = reqItems.at(0); Finder newReqItem = reqItems.at(0);
expect(find.descendant(of: newReqItem, matching: find.text("untitled")), expect(find.descendant(of: newReqItem, matching: find.text(kUntitled)),
findsOneWidget); findsOneWidget);
Finder itemCardMenu = Finder itemCardMenu =
find.descendant(of: newReqItem, matching: find.byType(ItemCardMenu)); find.descendant(of: newReqItem, matching: find.byType(ItemCardMenu));

View File

@ -432,6 +432,7 @@ const kLabelSelect = "Select";
const kLabelContinue = "Continue"; const kLabelContinue = "Continue";
const kLabelCancel = "Cancel"; const kLabelCancel = "Cancel";
const kLabelOk = "Ok"; const kLabelOk = "Ok";
const kUntitled = "untitled";
// Request Pane // Request Pane
const kLabelRequest = "Request"; const kLabelRequest = "Request";
const kLabelHideCode = "Hide Code"; const kLabelHideCode = "Hide Code";
@ -439,6 +440,7 @@ const kLabelViewCode = "View Code";
const kLabelURLParams = "URL Params"; const kLabelURLParams = "URL Params";
const kLabelHeaders = "Headers"; const kLabelHeaders = "Headers";
const kLabelBody = "Body"; const kLabelBody = "Body";
const kLabelQuery = "Query";
const kNameCheckbox = "Checkbox"; const kNameCheckbox = "Checkbox";
const kNameURLParam = "URL Parameter"; const kNameURLParam = "URL Parameter";
const kNameHeader = "Header Name"; const kNameHeader = "Header Name";
@ -453,6 +455,10 @@ const kLabelAddHeader = "Add Header";
const kLabelAddVariable = "Add Variable"; const kLabelAddVariable = "Add Variable";
const kLabelSelectFile = "Select File"; const kLabelSelectFile = "Select File";
const kLabelAddFormField = "Add Form Field"; const kLabelAddFormField = "Add Form Field";
const kHintContent = "Enter content";
const kHintText = "Enter text";
const kHintJson = "Enter JSON";
const kHintQuery = "Enter Query";
// Response Pane // Response Pane
const kLabelNotSent = "Not Sent"; const kLabelNotSent = "Not Sent";
const kLabelResponse = "Response"; const kLabelResponse = "Response";

View File

@ -9,6 +9,7 @@ class HistoryMetaModel with _$HistoryMetaModel {
const factory HistoryMetaModel({ const factory HistoryMetaModel({
required String historyId, required String historyId,
required String requestId, required String requestId,
required APIType apiType,
@Default("") String name, @Default("") String name,
required String url, required String url,
required HTTPVerb method, required HTTPVerb method,

View File

@ -22,6 +22,7 @@ HistoryMetaModel _$HistoryMetaModelFromJson(Map<String, dynamic> json) {
mixin _$HistoryMetaModel { mixin _$HistoryMetaModel {
String get historyId => throw _privateConstructorUsedError; String get historyId => throw _privateConstructorUsedError;
String get requestId => throw _privateConstructorUsedError; String get requestId => throw _privateConstructorUsedError;
APIType get apiType => throw _privateConstructorUsedError;
String get name => throw _privateConstructorUsedError; String get name => throw _privateConstructorUsedError;
String get url => throw _privateConstructorUsedError; String get url => throw _privateConstructorUsedError;
HTTPVerb get method => throw _privateConstructorUsedError; HTTPVerb get method => throw _privateConstructorUsedError;
@ -47,6 +48,7 @@ abstract class $HistoryMetaModelCopyWith<$Res> {
$Res call( $Res call(
{String historyId, {String historyId,
String requestId, String requestId,
APIType apiType,
String name, String name,
String url, String url,
HTTPVerb method, HTTPVerb method,
@ -71,6 +73,7 @@ class _$HistoryMetaModelCopyWithImpl<$Res, $Val extends HistoryMetaModel>
$Res call({ $Res call({
Object? historyId = null, Object? historyId = null,
Object? requestId = null, Object? requestId = null,
Object? apiType = null,
Object? name = null, Object? name = null,
Object? url = null, Object? url = null,
Object? method = null, Object? method = null,
@ -86,6 +89,10 @@ class _$HistoryMetaModelCopyWithImpl<$Res, $Val extends HistoryMetaModel>
? _value.requestId ? _value.requestId
: requestId // ignore: cast_nullable_to_non_nullable : requestId // ignore: cast_nullable_to_non_nullable
as String, as String,
apiType: null == apiType
? _value.apiType
: apiType // ignore: cast_nullable_to_non_nullable
as APIType,
name: null == name name: null == name
? _value.name ? _value.name
: name // ignore: cast_nullable_to_non_nullable : name // ignore: cast_nullable_to_non_nullable
@ -121,6 +128,7 @@ abstract class _$$HistoryMetaModelImplCopyWith<$Res>
$Res call( $Res call(
{String historyId, {String historyId,
String requestId, String requestId,
APIType apiType,
String name, String name,
String url, String url,
HTTPVerb method, HTTPVerb method,
@ -143,6 +151,7 @@ class __$$HistoryMetaModelImplCopyWithImpl<$Res>
$Res call({ $Res call({
Object? historyId = null, Object? historyId = null,
Object? requestId = null, Object? requestId = null,
Object? apiType = null,
Object? name = null, Object? name = null,
Object? url = null, Object? url = null,
Object? method = null, Object? method = null,
@ -158,6 +167,10 @@ class __$$HistoryMetaModelImplCopyWithImpl<$Res>
? _value.requestId ? _value.requestId
: requestId // ignore: cast_nullable_to_non_nullable : requestId // ignore: cast_nullable_to_non_nullable
as String, as String,
apiType: null == apiType
? _value.apiType
: apiType // ignore: cast_nullable_to_non_nullable
as APIType,
name: null == name name: null == name
? _value.name ? _value.name
: name // ignore: cast_nullable_to_non_nullable : name // ignore: cast_nullable_to_non_nullable
@ -188,6 +201,7 @@ class _$HistoryMetaModelImpl implements _HistoryMetaModel {
const _$HistoryMetaModelImpl( const _$HistoryMetaModelImpl(
{required this.historyId, {required this.historyId,
required this.requestId, required this.requestId,
required this.apiType,
this.name = "", this.name = "",
required this.url, required this.url,
required this.method, required this.method,
@ -202,6 +216,8 @@ class _$HistoryMetaModelImpl implements _HistoryMetaModel {
@override @override
final String requestId; final String requestId;
@override @override
final APIType apiType;
@override
@JsonKey() @JsonKey()
final String name; final String name;
@override @override
@ -215,7 +231,7 @@ class _$HistoryMetaModelImpl implements _HistoryMetaModel {
@override @override
String toString() { 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 @override
@ -227,6 +243,7 @@ class _$HistoryMetaModelImpl implements _HistoryMetaModel {
other.historyId == historyId) && other.historyId == historyId) &&
(identical(other.requestId, requestId) || (identical(other.requestId, requestId) ||
other.requestId == requestId) && other.requestId == requestId) &&
(identical(other.apiType, apiType) || other.apiType == apiType) &&
(identical(other.name, name) || other.name == name) && (identical(other.name, name) || other.name == name) &&
(identical(other.url, url) || other.url == url) && (identical(other.url, url) || other.url == url) &&
(identical(other.method, method) || other.method == method) && (identical(other.method, method) || other.method == method) &&
@ -238,8 +255,8 @@ class _$HistoryMetaModelImpl implements _HistoryMetaModel {
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
@override @override
int get hashCode => Object.hash(runtimeType, historyId, requestId, name, url, int get hashCode => Object.hash(runtimeType, historyId, requestId, apiType,
method, responseStatus, timeStamp); name, url, method, responseStatus, timeStamp);
/// Create a copy of HistoryMetaModel /// Create a copy of HistoryMetaModel
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@ -262,6 +279,7 @@ abstract class _HistoryMetaModel implements HistoryMetaModel {
const factory _HistoryMetaModel( const factory _HistoryMetaModel(
{required final String historyId, {required final String historyId,
required final String requestId, required final String requestId,
required final APIType apiType,
final String name, final String name,
required final String url, required final String url,
required final HTTPVerb method, required final HTTPVerb method,
@ -276,6 +294,8 @@ abstract class _HistoryMetaModel implements HistoryMetaModel {
@override @override
String get requestId; String get requestId;
@override @override
APIType get apiType;
@override
String get name; String get name;
@override @override
String get url; String get url;

View File

@ -11,6 +11,7 @@ _$HistoryMetaModelImpl _$$HistoryMetaModelImplFromJson(
_$HistoryMetaModelImpl( _$HistoryMetaModelImpl(
historyId: json['historyId'] as String, historyId: json['historyId'] as String,
requestId: json['requestId'] as String, requestId: json['requestId'] as String,
apiType: $enumDecode(_$APITypeEnumMap, json['apiType']),
name: json['name'] as String? ?? "", name: json['name'] as String? ?? "",
url: json['url'] as String, url: json['url'] as String,
method: $enumDecode(_$HTTPVerbEnumMap, json['method']), method: $enumDecode(_$HTTPVerbEnumMap, json['method']),
@ -23,6 +24,7 @@ Map<String, dynamic> _$$HistoryMetaModelImplToJson(
<String, dynamic>{ <String, dynamic>{
'historyId': instance.historyId, 'historyId': instance.historyId,
'requestId': instance.requestId, 'requestId': instance.requestId,
'apiType': _$APITypeEnumMap[instance.apiType]!,
'name': instance.name, 'name': instance.name,
'url': instance.url, 'url': instance.url,
'method': _$HTTPVerbEnumMap[instance.method]!, 'method': _$HTTPVerbEnumMap[instance.method]!,
@ -30,6 +32,11 @@ Map<String, dynamic> _$$HistoryMetaModelImplToJson(
'timeStamp': instance.timeStamp.toIso8601String(), 'timeStamp': instance.timeStamp.toIso8601String(),
}; };
const _$APITypeEnumMap = {
APIType.rest: 'rest',
APIType.graphql: 'graphql',
};
const _$HTTPVerbEnumMap = { const _$HTTPVerbEnumMap = {
HTTPVerb.get: 'get', HTTPVerb.get: 'get',
HTTPVerb.head: 'head', HTTPVerb.head: 'head',

View File

@ -43,4 +43,5 @@ Map<String, dynamic> _$$RequestModelImplToJson(_$RequestModelImpl instance) =>
const _$APITypeEnumMap = { const _$APITypeEnumMap = {
APIType.rest: 'rest', APIType.rest: 'rest',
APIType.graphql: 'graphql',
}; };

View File

@ -1,4 +1,5 @@
import 'package:apidash_core/apidash_core.dart'; import 'package:apidash_core/apidash_core.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:apidash/consts.dart'; import 'package:apidash/consts.dart';
import 'providers.dart'; import 'providers.dart';
@ -112,10 +113,11 @@ class CollectionStateNotifier
unsave(); unsave();
} }
void remove(String id) { void remove({String? id}) {
final rId = id ?? ref.read(selectedIdStateProvider);
var itemIds = ref.read(requestSequenceProvider); var itemIds = ref.read(requestSequenceProvider);
int idx = itemIds.indexOf(id); int idx = itemIds.indexOf(rId!);
itemIds.remove(id); itemIds.remove(rId);
ref.read(requestSequenceProvider.notifier).state = [...itemIds]; ref.read(requestSequenceProvider.notifier).state = [...itemIds];
String? newId; String? newId;
@ -130,14 +132,15 @@ class CollectionStateNotifier
ref.read(selectedIdStateProvider.notifier).state = newId; ref.read(selectedIdStateProvider.notifier).state = newId;
var map = {...state!}; var map = {...state!};
map.remove(id); map.remove(rId);
state = map; state = map;
unsave(); unsave();
} }
void clearResponse(String? id) { void clearResponse({String? id}) {
if (id == null || state?[id] == null) return; final rId = id ?? ref.read(selectedIdStateProvider);
var currentModel = state![id]!; if (rId == null || state?[rId] == null) return;
var currentModel = state![rId]!;
final newModel = currentModel.copyWith( final newModel = currentModel.copyWith(
responseStatus: null, responseStatus: null,
message: null, message: null,
@ -146,17 +149,18 @@ class CollectionStateNotifier
sendingTime: null, sendingTime: null,
); );
var map = {...state!}; var map = {...state!};
map[id] = newModel; map[rId] = newModel;
state = map; state = map;
unsave(); unsave();
} }
void duplicate(String id) { void duplicate({String? id}) {
final rId = id ?? ref.read(selectedIdStateProvider);
final newId = getNewUuid(); final newId = getNewUuid();
var itemIds = ref.read(requestSequenceProvider); var itemIds = ref.read(requestSequenceProvider);
int idx = itemIds.indexOf(id); int idx = itemIds.indexOf(rId!);
var currentModel = state![id]!; var currentModel = state![rId]!;
final newModel = currentModel.copyWith( final newModel = currentModel.copyWith(
id: newId, id: newId,
name: "${currentModel.name} (copy)", name: "${currentModel.name} (copy)",
@ -204,9 +208,10 @@ class CollectionStateNotifier
unsave(); unsave();
} }
void update( void update({
String id, { String? id,
HTTPVerb? method, HTTPVerb? method,
APIType? apiType,
String? url, String? url,
String? name, String? name,
String? description, String? description,
@ -217,14 +222,21 @@ class CollectionStateNotifier
List<bool>? isParamEnabledList, List<bool>? isParamEnabledList,
ContentType? bodyContentType, ContentType? bodyContentType,
String? body, String? body,
String? query,
List<FormDataModel>? formData, List<FormDataModel>? formData,
int? responseStatus, int? responseStatus,
String? message, String? message,
HttpResponseModel? httpResponseModel, 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; var currentHttpRequestModel = currentModel.httpRequestModel;
final newModel = currentModel.copyWith( final newModel = currentModel.copyWith(
apiType: apiType ?? currentModel.apiType,
name: name ?? currentModel.name, name: name ?? currentModel.name,
description: description ?? currentModel.description, description: description ?? currentModel.description,
requestTabIndex: requestTabIndex ?? currentModel.requestTabIndex, requestTabIndex: requestTabIndex ?? currentModel.requestTabIndex,
@ -240,6 +252,7 @@ class CollectionStateNotifier
bodyContentType: bodyContentType:
bodyContentType ?? currentHttpRequestModel.bodyContentType, bodyContentType ?? currentHttpRequestModel.bodyContentType,
body: body ?? currentHttpRequestModel.body, body: body ?? currentHttpRequestModel.body,
query: query ?? currentHttpRequestModel.query,
formData: formData ?? currentHttpRequestModel.formData, formData: formData ?? currentHttpRequestModel.formData,
), ),
responseStatus: responseStatus ?? currentModel.responseStatus, responseStatus: responseStatus ?? currentModel.responseStatus,
@ -248,7 +261,7 @@ class CollectionStateNotifier
); );
var map = {...state!}; var map = {...state!};
map[id] = newModel; map[rId] = newModel;
state = map; state = map;
unsave(); unsave();
} }
@ -267,8 +280,9 @@ class CollectionStateNotifier
return; return;
} }
APIType apiType = requestModel!.apiType;
HttpRequestModel substitutedHttpRequestModel = HttpRequestModel substitutedHttpRequestModel =
getSubstitutedHttpRequestModel(requestModel!.httpRequestModel!); getSubstitutedHttpRequestModel(requestModel.httpRequestModel!);
// set current model's isWorking to true and update state // set current model's isWorking to true and update state
var map = {...state!}; var map = {...state!};
@ -281,6 +295,7 @@ class CollectionStateNotifier
bool noSSL = ref.read(settingsProvider).isSSLDisabled; bool noSSL = ref.read(settingsProvider).isSSLDisabled;
(HttpResponse?, Duration?, String?)? responseRec = await request( (HttpResponse?, Duration?, String?)? responseRec = await request(
requestId, requestId,
apiType,
substitutedHttpRequestModel, substitutedHttpRequestModel,
defaultUriScheme: defaultUriScheme, defaultUriScheme: defaultUriScheme,
noSSL: noSSL, noSSL: noSSL,
@ -311,6 +326,7 @@ class CollectionStateNotifier
metaData: HistoryMetaModel( metaData: HistoryMetaModel(
historyId: newHistoryId, historyId: newHistoryId,
requestId: requestId, requestId: requestId,
apiType: requestModel.apiType,
name: requestModel.name, name: requestModel.name,
url: substitutedHttpRequestModel.url, url: substitutedHttpRequestModel.url,
method: substitutedHttpRequestModel.method, method: substitutedHttpRequestModel.method,

View File

@ -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);
},
);
}
}

View File

@ -1,3 +1,4 @@
import 'package:apidash_core/apidash_core.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:apidash/providers/providers.dart'; import 'package:apidash/providers/providers.dart';
@ -39,6 +40,13 @@ class CodePane extends ConsumerWidget {
final code = codegen.getCode( final code = codegen.getCode(
codegenLanguage, substitutedRequestModel!, defaultUriScheme); 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) { if (code == null) {
return const ErrorMessage( return const ErrorMessage(
message: "An error was encountered while generating code. $kRaiseIssue", message: "An error was encountered while generating code. $kRaiseIssue",

View File

@ -1,3 +1,4 @@
export 'api_type_dropdown.dart';
export 'button_navbar.dart'; export 'button_navbar.dart';
export 'code_pane.dart'; export 'code_pane.dart';
export 'editor_title.dart'; export 'editor_title.dart';

View File

@ -5,7 +5,7 @@ import 'package:apidash/widgets/widgets.dart';
import 'package:apidash/providers/providers.dart'; import 'package:apidash/providers/providers.dart';
import 'package:apidash/utils/utils.dart'; import 'package:apidash/utils/utils.dart';
import 'package:apidash/consts.dart'; import 'package:apidash/consts.dart';
import 'history_pane.dart'; import 'history_sidebar.dart';
import 'history_viewer.dart'; import 'history_viewer.dart';
class HistoryPage extends ConsumerWidget { class HistoryPage extends ConsumerWidget {

View File

@ -142,6 +142,7 @@ class _HistoryExpansionTileState extends ConsumerState<HistoryExpansionTile>
padding: kPv2 + kPh4, padding: kPv2 + kPh4,
child: SidebarHistoryCard( child: SidebarHistoryCard(
id: item.first.historyId, id: item.first.historyId,
apiType: item.first.apiType,
models: item, models: item,
method: item.first.method, method: item.first.method,
isSelected: selectedGroupId == getHistoryRequestKey(item.first), isSelected: selectedGroupId == getHistoryRequestKey(item.first),

View File

@ -18,7 +18,8 @@ class HistoryRequestPane extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final selectedId = ref.watch(selectedHistoryIdStateProvider); final selectedId = ref.watch(selectedHistoryIdStateProvider);
final codePaneVisible = ref.watch(historyCodePaneVisibleStateProvider); final codePaneVisible = ref.watch(historyCodePaneVisibleStateProvider);
final apiType = ref.watch(selectedHistoryRequestModelProvider
.select((value) => value?.metaData.apiType));
final headersMap = ref.watch(selectedHistoryRequestModelProvider final headersMap = ref.watch(selectedHistoryRequestModelProvider
.select((value) => value?.httpRequestModel.headersMap)) ?? .select((value) => value?.httpRequestModel.headersMap)) ??
{}; {};
@ -33,7 +34,13 @@ class HistoryRequestPane extends ConsumerWidget {
.select((value) => value?.httpRequestModel.hasBody)) ?? .select((value) => value?.httpRequestModel.hasBody)) ??
false; false;
return RequestPane( 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, selectedId: selectedId,
codePaneVisible: codePaneVisible, codePaneVisible: codePaneVisible,
onPressedCodeButton: () { onPressedCodeButton: () {
@ -46,6 +53,11 @@ class HistoryRequestPane extends ConsumerWidget {
headerLength > 0, headerLength > 0,
hasBody, hasBody,
], ],
tabLabels: const [
kLabelURLParams,
kLabelHeaders,
kLabelBody,
],
children: [ children: [
RequestDataTable( RequestDataTable(
rows: paramsMap, rows: paramsMap,
@ -57,7 +69,34 @@ class HistoryRequestPane extends ConsumerWidget {
), ),
const HisRequestBody(), const HisRequestBody(),
], ],
); ),
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(),
],
),
_ => kSizedBoxEmpty,
};
} }
} }
@ -67,10 +106,12 @@ class HisRequestBody extends ConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final selectedHistoryModel = ref.watch(selectedHistoryRequestModelProvider); final selectedHistoryModel = ref.watch(selectedHistoryRequestModelProvider);
final apiType = selectedHistoryModel?.metaData.apiType;
final requestModel = selectedHistoryModel?.httpRequestModel; final requestModel = selectedHistoryModel?.httpRequestModel;
final contentType = requestModel?.bodyContentType; final contentType = requestModel?.bodyContentType;
return Column( return switch (apiType) {
APIType.rest => Column(
children: [ children: [
kVSpacer5, kVSpacer5,
RichText( RichText(
@ -94,8 +135,8 @@ class HisRequestBody extends ConsumerWidget {
child: switch (contentType) { child: switch (contentType) {
ContentType.formdata => Padding( ContentType.formdata => Padding(
padding: kPh4, padding: kPh4,
child: child: RequestFormDataTable(
RequestFormDataTable(rows: requestModel?.formData ?? [])), rows: requestModel?.formData ?? [])),
// TODO: Fix JsonTextFieldEditor & plug it here // TODO: Fix JsonTextFieldEditor & plug it here
ContentType.json => Padding( ContentType.json => Padding(
padding: kPt5o10, padding: kPt5o10,
@ -111,7 +152,8 @@ class HisRequestBody extends ConsumerWidget {
padding: kPt5o10, padding: kPt5o10,
child: TextFieldEditor( child: TextFieldEditor(
key: Key("${selectedHistoryModel?.historyId}-body"), key: Key("${selectedHistoryModel?.historyId}-body"),
fieldKey: "${selectedHistoryModel?.historyId}-body-viewer", fieldKey:
"${selectedHistoryModel?.historyId}-body-viewer",
initialValue: requestModel?.body, initialValue: requestModel?.body,
readOnly: true, readOnly: true,
), ),
@ -119,6 +161,17 @@ class HisRequestBody extends ConsumerWidget {
}, },
) )
], ],
); ),
APIType.graphql => Padding(
padding: kPt5o10,
child: TextFieldEditor(
key: Key("${selectedHistoryModel?.historyId}-query"),
fieldKey: "${selectedHistoryModel?.historyId}-query-viewer",
initialValue: requestModel?.query,
readOnly: true,
),
),
_ => kSizedBoxEmpty,
};
} }
} }

View File

@ -1,3 +1,4 @@
import 'package:apidash/services/services.dart';
import 'package:apidash_design_system/apidash_design_system.dart'; import 'package:apidash_design_system/apidash_design_system.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
@ -21,11 +22,19 @@ class HistorySidebarHeader extends ConsumerWidget {
style: Theme.of(context).textTheme.titleMedium, style: Theme.of(context).textTheme.titleMedium,
), ),
const Spacer(), const Spacer(),
IconButton( ADIconButton(
tooltip: "Manage History", icon: Icons.delete_forever,
style: IconButton.styleFrom( iconSize: kButtonIconSizeLarge,
foregroundColor: Theme.of(context).colorScheme.primary, 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",
onPressed: () { onPressed: () {
showHistoryRetentionDialog( showHistoryRetentionDialog(
context, context,
@ -36,10 +45,6 @@ class HistorySidebarHeader extends ConsumerWidget {
); );
}); });
}, },
icon: const Icon(
Icons.manage_history_rounded,
size: 20,
),
), ),
context.width <= kMinWindowSize.width context.width <= kMinWindowSize.width
? IconButton( ? IconButton(

View File

@ -1,3 +1,4 @@
import 'package:apidash_core/apidash_core.dart';
import 'package:apidash_design_system/apidash_design_system.dart'; import 'package:apidash_design_system/apidash_design_system.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:apidash/widgets/widgets.dart'; import 'package:apidash/widgets/widgets.dart';
@ -16,6 +17,7 @@ class HistoryURLCard extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final apiType = historyRequestModel?.metaData.apiType;
final method = historyRequestModel?.metaData.method; final method = historyRequestModel?.metaData.method;
final url = historyRequestModel?.metaData.url; final url = historyRequestModel?.metaData.url;
final fontSize = Theme.of(context).textTheme.titleMedium?.fontSize; final fontSize = Theme.of(context).textTheme.titleMedium?.fontSize;
@ -41,6 +43,7 @@ class HistoryURLCard extends StatelessWidget {
child: Row( child: Row(
children: [ children: [
isCompact ? const SizedBox.shrink() : kHSpacer10, isCompact ? const SizedBox.shrink() : kHSpacer10,
if (apiType == APIType.rest) ...[
Text( Text(
method!.name.toUpperCase(), method!.name.toUpperCase(),
style: kCodeStyle.copyWith( style: kCodeStyle.copyWith(
@ -53,6 +56,7 @@ class HistoryURLCard extends StatelessWidget {
), ),
), ),
isCompact ? kHSpacer10 : kHSpacer20, isCompact ? kHSpacer10 : kHSpacer20,
],
Expanded( Expanded(
child: ReadOnlyTextField( child: ReadOnlyTextField(
initialValue: url, initialValue: url,

View File

@ -186,6 +186,7 @@ class RequestItem extends ConsumerWidget {
return SidebarRequestCard( return SidebarRequestCard(
id: id, id: id,
apiType: requestModel.apiType,
method: requestModel.httpRequestModel!.method, method: requestModel.httpRequestModel!.method,
name: requestModel.name, name: requestModel.name,
url: requestModel.httpRequestModel?.url, url: requestModel.httpRequestModel?.url,
@ -208,7 +209,7 @@ class RequestItem extends ConsumerWidget {
value = value.trim(); value = value.trim();
ref ref
.read(collectionStateNotifierProvider.notifier) .read(collectionStateNotifierProvider.notifier)
.update(editRequestId!, name: value); .update(id: editRequestId!, name: value);
}, },
onTapOutsideNameEditor: () { onTapOutsideNameEditor: () {
ref.read(selectedIdEditStateProvider.notifier).state = null; ref.read(selectedIdEditStateProvider.notifier).state = null;
@ -231,10 +232,10 @@ class RequestItem extends ConsumerWidget {
); );
} }
if (item == ItemMenuOption.delete) { if (item == ItemMenuOption.delete) {
ref.read(collectionStateNotifierProvider.notifier).remove(id); ref.read(collectionStateNotifierProvider.notifier).remove(id: id);
} }
if (item == ItemMenuOption.duplicate) { if (item == ItemMenuOption.duplicate) {
ref.read(collectionStateNotifierProvider.notifier).duplicate(id); ref.read(collectionStateNotifierProvider.notifier).duplicate(id: id);
} }
}, },
); );

View File

@ -18,6 +18,8 @@ class EditRequestBody extends ConsumerWidget {
.getRequestModel(selectedId!); .getRequestModel(selectedId!);
final contentType = ref.watch(selectedRequestModelProvider final contentType = ref.watch(selectedRequestModelProvider
.select((value) => value?.httpRequestModel?.bodyContentType)); .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 // TODO: #178 GET->POST Currently switches to POST everytime user edits body even if the user intentionally chooses GET
// final sm = ScaffoldMessenger.of(context); // final sm = ScaffoldMessenger.of(context);
@ -36,7 +38,8 @@ class EditRequestBody extends ConsumerWidget {
return Column( return Column(
children: [ children: [
const SizedBox( (apiType == APIType.rest)
? const SizedBox(
height: kHeaderHeight, height: kHeaderHeight,
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
@ -47,8 +50,10 @@ class EditRequestBody extends ConsumerWidget {
DropdownButtonBodyContentType(), DropdownButtonBodyContentType(),
], ],
), ),
), )
Expanded( : kSizedBoxEmpty,
switch (apiType) {
APIType.rest => Expanded(
child: switch (contentType) { child: switch (contentType) {
ContentType.formdata => const Padding( ContentType.formdata => const Padding(
padding: kPh4, padding: kPh4,
@ -67,8 +72,9 @@ class EditRequestBody extends ConsumerWidget {
// changeToPostMethod(); // changeToPostMethod();
ref ref
.read(collectionStateNotifierProvider.notifier) .read(collectionStateNotifierProvider.notifier)
.update(selectedId, body: value); .update(body: value);
}, },
hintText: kHintJson,
), ),
), ),
_ => Padding( _ => Padding(
@ -81,12 +87,31 @@ class EditRequestBody extends ConsumerWidget {
// changeToPostMethod(); // changeToPostMethod();
ref ref
.read(collectionStateNotifierProvider.notifier) .read(collectionStateNotifierProvider.notifier)
.update(selectedId, body: value); .update(body: value);
}, },
hintText: kHintText,
), ),
), ),
}, },
) ),
APIType.graphql => Expanded(
child: Padding(
padding: kPt5o10,
child: TextFieldEditor(
key: Key("$selectedId-query"),
fieldKey: "$selectedId-query-editor",
initialValue: requestModel?.httpRequestModel?.query,
onChanged: (String value) {
ref
.read(collectionStateNotifierProvider.notifier)
.update(query: value);
},
hintText: kHintQuery,
),
),
),
_ => kSizedBoxEmpty,
}
], ],
); );
} }
@ -99,7 +124,7 @@ class DropdownButtonBodyContentType extends ConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final selectedId = ref.watch(selectedIdStateProvider); ref.watch(selectedIdStateProvider);
final requestBodyContentType = ref.watch(selectedRequestModelProvider final requestBodyContentType = ref.watch(selectedRequestModelProvider
.select((value) => value?.httpRequestModel?.bodyContentType)); .select((value) => value?.httpRequestModel?.bodyContentType));
return DropdownButtonContentType( return DropdownButtonContentType(
@ -107,7 +132,7 @@ class DropdownButtonBodyContentType extends ConsumerWidget {
onChanged: (ContentType? value) { onChanged: (ContentType? value) {
ref ref
.read(collectionStateNotifierProvider.notifier) .read(collectionStateNotifierProvider.notifier)
.update(selectedId!, bodyContentType: value); .update(bodyContentType: value);
}, },
); );
} }

View File

@ -27,9 +27,8 @@ class _FormDataBodyState extends ConsumerState<FormDataWidget> {
seed = random.nextInt(kRandMax); seed = random.nextInt(kRandMax);
} }
void _onFieldChange(String selectedId) { void _onFieldChange() {
ref.read(collectionStateNotifierProvider.notifier).update( ref.read(collectionStateNotifierProvider.notifier).update(
selectedId,
formData: formRows.sublist(0, formRows.length - 1), formData: formRows.sublist(0, formRows.length - 1),
); );
} }
@ -93,7 +92,7 @@ class _FormDataBodyState extends ConsumerState<FormDataWidget> {
isAddingRow = true; isAddingRow = true;
formRows.add(kFormDataEmptyModel); formRows.add(kFormDataEmptyModel);
} }
_onFieldChange(selectedId!); _onFieldChange();
}, },
colorScheme: Theme.of(context).colorScheme, colorScheme: Theme.of(context).colorScheme,
), ),
@ -119,7 +118,7 @@ class _FormDataBodyState extends ConsumerState<FormDataWidget> {
formRows.add(kFormDataEmptyModel); formRows.add(kFormDataEmptyModel);
} }
setState(() {}); setState(() {});
_onFieldChange(selectedId!); _onFieldChange();
}, },
), ),
), ),
@ -134,7 +133,7 @@ class _FormDataBodyState extends ConsumerState<FormDataWidget> {
value: pickedResult.path, value: pickedResult.path,
); );
setState(() {}); setState(() {});
_onFieldChange(selectedId!); _onFieldChange();
} }
}, },
initialValue: formRows[index].value, initialValue: formRows[index].value,
@ -150,7 +149,7 @@ class _FormDataBodyState extends ConsumerState<FormDataWidget> {
isAddingRow = true; isAddingRow = true;
formRows.add(kFormDataEmptyModel); formRows.add(kFormDataEmptyModel);
} }
_onFieldChange(selectedId!); _onFieldChange();
}, },
colorScheme: Theme.of(context).colorScheme, colorScheme: Theme.of(context).colorScheme,
), ),
@ -170,7 +169,7 @@ class _FormDataBodyState extends ConsumerState<FormDataWidget> {
} else { } else {
formRows.removeAt(index); formRows.removeAt(index);
} }
_onFieldChange(selectedId!); _onFieldChange();
}, },
child: Theme.of(context).brightness == Brightness.dark child: Theme.of(context).brightness == Brightness.dark
? kIconRemoveDark ? kIconRemoveDark
@ -216,7 +215,7 @@ class _FormDataBodyState extends ConsumerState<FormDataWidget> {
child: ElevatedButton.icon( child: ElevatedButton.icon(
onPressed: () { onPressed: () {
formRows.add(kFormDataEmptyModel); formRows.add(kFormDataEmptyModel);
_onFieldChange(selectedId!); _onFieldChange();
}, },
icon: const Icon(Icons.add), icon: const Icon(Icons.add),
label: const Text( label: const Text(

View File

@ -29,9 +29,8 @@ class EditRequestHeadersState extends ConsumerState<EditRequestHeaders> {
seed = random.nextInt(kRandMax); seed = random.nextInt(kRandMax);
} }
void _onFieldChange(String selectedId) { void _onFieldChange() {
ref.read(collectionStateNotifierProvider.notifier).update( ref.read(collectionStateNotifierProvider.notifier).update(
selectedId,
headers: headerRows.sublist(0, headerRows.length - 1), headers: headerRows.sublist(0, headerRows.length - 1),
isHeaderEnabledList: isHeaderEnabledList:
isRowEnabledList.sublist(0, headerRows.length - 1), isRowEnabledList.sublist(0, headerRows.length - 1),
@ -99,7 +98,7 @@ class EditRequestHeadersState extends ConsumerState<EditRequestHeaders> {
setState(() { setState(() {
isRowEnabledList[index] = value!; isRowEnabledList[index] = value!;
}); });
_onFieldChange(selectedId!); _onFieldChange();
}, },
colorScheme: Theme.of(context).colorScheme, colorScheme: Theme.of(context).colorScheme,
), ),
@ -117,7 +116,7 @@ class EditRequestHeadersState extends ConsumerState<EditRequestHeaders> {
headerRows.add(kNameValueEmptyModel); headerRows.add(kNameValueEmptyModel);
isRowEnabledList.add(false); isRowEnabledList.add(false);
} }
_onFieldChange(selectedId!); _onFieldChange();
}, },
colorScheme: Theme.of(context).colorScheme, colorScheme: Theme.of(context).colorScheme,
), ),
@ -143,7 +142,7 @@ class EditRequestHeadersState extends ConsumerState<EditRequestHeaders> {
headerRows.add(kNameValueEmptyModel); headerRows.add(kNameValueEmptyModel);
isRowEnabledList.add(false); isRowEnabledList.add(false);
} }
_onFieldChange(selectedId!); _onFieldChange();
}, },
colorScheme: Theme.of(context).colorScheme, colorScheme: Theme.of(context).colorScheme,
), ),
@ -165,7 +164,7 @@ class EditRequestHeadersState extends ConsumerState<EditRequestHeaders> {
headerRows.removeAt(index); headerRows.removeAt(index);
isRowEnabledList.removeAt(index); isRowEnabledList.removeAt(index);
} }
_onFieldChange(selectedId!); _onFieldChange();
}, },
child: Theme.of(context).brightness == Brightness.dark child: Theme.of(context).brightness == Brightness.dark
? kIconRemoveDark ? kIconRemoveDark
@ -212,7 +211,7 @@ class EditRequestHeadersState extends ConsumerState<EditRequestHeaders> {
onPressed: () { onPressed: () {
headerRows.add(kNameValueEmptyModel); headerRows.add(kNameValueEmptyModel);
isRowEnabledList.add(false); isRowEnabledList.add(false);
_onFieldChange(selectedId!); _onFieldChange();
}, },
icon: const Icon(Icons.add), icon: const Icon(Icons.add),
label: const Text( label: const Text(

View File

@ -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/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:apidash/providers/providers.dart'; import 'package:apidash/providers/providers.dart';
import 'package:apidash/widgets/widgets.dart'; import 'request_pane_graphql.dart';
import 'request_headers.dart'; import 'request_pane_rest.dart';
import 'request_params.dart';
import 'request_body.dart';
class EditRequestPane extends ConsumerWidget { class EditRequestPane extends ConsumerWidget {
const EditRequestPane({super.key}); const EditRequestPane({super.key});
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final selectedId = ref.watch(selectedIdStateProvider); ref.watch(selectedIdStateProvider);
final codePaneVisible = ref.watch(codePaneVisibleStateProvider); final apiType = ref
final tabIndex = ref.watch( .watch(selectedRequestModelProvider.select((value) => value?.apiType));
selectedRequestModelProvider.select((value) => value?.requestTabIndex)); return switch (apiType) {
APIType.rest => const EditRestRequestPane(),
final headerLength = ref.watch(selectedRequestModelProvider APIType.graphql => const EditGraphQLRequestPane(),
.select((value) => value?.httpRequestModel?.headersMap.length)) ?? _ => kSizedBoxEmpty,
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(),
],
);
} }
} }

View File

@ -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(),
],
);
}
}

View File

@ -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(),
],
);
}
}

View File

@ -29,9 +29,8 @@ class EditRequestURLParamsState extends ConsumerState<EditRequestURLParams> {
seed = random.nextInt(kRandMax); seed = random.nextInt(kRandMax);
} }
void _onFieldChange(String selectedId) { void _onFieldChange() {
ref.read(collectionStateNotifierProvider.notifier).update( ref.read(collectionStateNotifierProvider.notifier).update(
selectedId,
params: paramRows.sublist(0, paramRows.length - 1), params: paramRows.sublist(0, paramRows.length - 1),
isParamEnabledList: isRowEnabledList.sublist(0, paramRows.length - 1), isParamEnabledList: isRowEnabledList.sublist(0, paramRows.length - 1),
); );
@ -98,7 +97,7 @@ class EditRequestURLParamsState extends ConsumerState<EditRequestURLParams> {
setState(() { setState(() {
isRowEnabledList[index] = value!; isRowEnabledList[index] = value!;
}); });
_onFieldChange(selectedId!); _onFieldChange();
}, },
colorScheme: Theme.of(context).colorScheme, colorScheme: Theme.of(context).colorScheme,
), ),
@ -116,7 +115,7 @@ class EditRequestURLParamsState extends ConsumerState<EditRequestURLParams> {
paramRows.add(kNameValueEmptyModel); paramRows.add(kNameValueEmptyModel);
isRowEnabledList.add(false); isRowEnabledList.add(false);
} }
_onFieldChange(selectedId!); _onFieldChange();
}, },
colorScheme: Theme.of(context).colorScheme, colorScheme: Theme.of(context).colorScheme,
), ),
@ -142,7 +141,7 @@ class EditRequestURLParamsState extends ConsumerState<EditRequestURLParams> {
paramRows.add(kNameValueEmptyModel); paramRows.add(kNameValueEmptyModel);
isRowEnabledList.add(false); isRowEnabledList.add(false);
} }
_onFieldChange(selectedId!); _onFieldChange();
}, },
colorScheme: Theme.of(context).colorScheme, colorScheme: Theme.of(context).colorScheme,
), ),
@ -164,7 +163,7 @@ class EditRequestURLParamsState extends ConsumerState<EditRequestURLParams> {
paramRows.removeAt(index); paramRows.removeAt(index);
isRowEnabledList.removeAt(index); isRowEnabledList.removeAt(index);
} }
_onFieldChange(selectedId!); _onFieldChange();
}, },
child: Theme.of(context).brightness == Brightness.dark child: Theme.of(context).brightness == Brightness.dark
? kIconRemoveDark ? kIconRemoveDark
@ -212,7 +211,7 @@ class EditRequestURLParamsState extends ConsumerState<EditRequestURLParams> {
onPressed: () { onPressed: () {
paramRows.add(kNameValueEmptyModel); paramRows.add(kNameValueEmptyModel);
isRowEnabledList.add(false); isRowEnabledList.add(false);
_onFieldChange(selectedId!); _onFieldChange();
}, },
icon: const Icon(Icons.add), icon: const Icon(Icons.add),
label: const Text( label: const Text(

View File

@ -61,10 +61,7 @@ class ResponseDetails extends ConsumerWidget {
message: message, message: message,
time: responseModel?.time, time: responseModel?.time,
onClearResponse: () { onClearResponse: () {
final selectedRequest = ref.read(selectedRequestModelProvider); ref.read(collectionStateNotifierProvider.notifier).clearResponse();
ref
.read(collectionStateNotifierProvider.notifier)
.clearResponse(selectedRequest?.id);
}, },
), ),
const Expanded( const Expanded(

View File

@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:apidash/providers/providers.dart'; import 'package:apidash/providers/providers.dart';
import 'package:apidash/widgets/widgets.dart'; import 'package:apidash/widgets/widgets.dart';
import '../../../consts.dart';
import '../../common_widgets/common_widgets.dart'; import '../../common_widgets/common_widgets.dart';
class RequestEditorTopBar extends ConsumerWidget { class RequestEditorTopBar extends ConsumerWidget {
@ -11,26 +12,18 @@ class RequestEditorTopBar extends ConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final id = ref.watch(selectedIdStateProvider); ref.watch(selectedIdStateProvider);
final name = final name =
ref.watch(selectedRequestModelProvider.select((value) => value?.name)); ref.watch(selectedRequestModelProvider.select((value) => value?.name));
return Padding( return Padding(
padding: const EdgeInsets.only( padding: kP4,
left: 12.0,
top: 4.0,
right: 4.0,
bottom: 4.0,
),
child: Row( child: Row(
children: [ children: [
DropdownButtonAPIType( const APITypeDropdown(),
apiType: APIType.rest,
onChanged: (apiType) {},
),
kHSpacer10, kHSpacer10,
Expanded( Expanded(
child: Text( child: Text(
name ?? "", name.isNullOrEmpty() ? kUntitled : name!,
style: Theme.of(context).textTheme.bodyMedium, style: Theme.of(context).textTheme.bodyMedium,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
maxLines: 1, maxLines: 1,
@ -42,14 +35,13 @@ class RequestEditorTopBar extends ConsumerWidget {
showRenameDialog(context, "Rename Request", name, (val) { showRenameDialog(context, "Rename Request", name, (val) {
ref ref
.read(collectionStateNotifierProvider.notifier) .read(collectionStateNotifierProvider.notifier)
.update(id!, name: val); .update(name: val);
}); });
}, },
onDuplicatePressed: () => ref onDuplicatePressed: () =>
.read(collectionStateNotifierProvider.notifier) ref.read(collectionStateNotifierProvider.notifier).duplicate(),
.duplicate(id!),
onDeletePressed: () => onDeletePressed: () =>
ref.read(collectionStateNotifierProvider.notifier).remove(id!), ref.read(collectionStateNotifierProvider.notifier).remove(),
), ),
kHSpacer10, kHSpacer10,
const EnvironmentDropdown(), const EnvironmentDropdown(),

View File

@ -6,11 +6,14 @@ import 'package:apidash/providers/providers.dart';
import 'package:apidash/widgets/widgets.dart'; import 'package:apidash/widgets/widgets.dart';
import '../../common_widgets/common_widgets.dart'; import '../../common_widgets/common_widgets.dart';
class EditorPaneRequestURLCard extends StatelessWidget { class EditorPaneRequestURLCard extends ConsumerWidget {
const EditorPaneRequestURLCard({super.key}); const EditorPaneRequestURLCard({super.key});
@override @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( return Card(
color: kColorTransparent, color: kColorTransparent,
surfaceTintColor: kColorTransparent, surfaceTintColor: kColorTransparent,
@ -27,24 +30,38 @@ class EditorPaneRequestURLCard extends StatelessWidget {
horizontal: !context.isMediumWindow ? 20 : 6, horizontal: !context.isMediumWindow ? 20 : 6,
), ),
child: context.isMediumWindow child: context.isMediumWindow
? const Row( ? Row(
children: [ children: [
DropdownButtonHTTPMethod(), switch (apiType) {
kHSpacer5, APIType.rest => const DropdownButtonHTTPMethod(),
Expanded( APIType.graphql => kSizedBoxEmpty,
null => kSizedBoxEmpty,
},
switch (apiType) {
APIType.rest => kHSpacer5,
_ => kHSpacer8,
},
const Expanded(
child: URLTextField(), child: URLTextField(),
), ),
], ],
) )
: const Row( : Row(
children: [ children: [
DropdownButtonHTTPMethod(), switch (apiType) {
kHSpacer20, APIType.rest => const DropdownButtonHTTPMethod(),
Expanded( APIType.graphql => kSizedBoxEmpty,
null => kSizedBoxEmpty,
},
switch (apiType) {
APIType.rest => kHSpacer20,
_ => kHSpacer8,
},
const Expanded(
child: URLTextField(), child: URLTextField(),
), ),
kHSpacer20, kHSpacer20,
SizedBox( const SizedBox(
height: 36, height: 36,
child: SendRequestButton(), child: SendRequestButton(),
) )
@ -67,10 +84,9 @@ class DropdownButtonHTTPMethod extends ConsumerWidget {
return DropdownButtonHttpMethod( return DropdownButtonHttpMethod(
method: method, method: method,
onChanged: (HTTPVerb? value) { onChanged: (HTTPVerb? value) {
final selectedId = ref.read(selectedRequestModelProvider)!.id;
ref ref
.read(collectionStateNotifierProvider.notifier) .read(collectionStateNotifierProvider.notifier)
.update(selectedId, method: value); .update(method: value);
}, },
); );
} }
@ -92,9 +108,7 @@ class URLTextField extends ConsumerWidget {
?.httpRequestModel ?.httpRequestModel
?.url, ?.url,
onChanged: (value) { onChanged: (value) {
ref ref.read(collectionStateNotifierProvider.notifier).update(url: value);
.read(collectionStateNotifierProvider.notifier)
.update(selectedId, url: value);
}, },
onFieldSubmitted: (value) { onFieldSubmitted: (value) {
ref.read(collectionStateNotifierProvider.notifier).sendRequest(); ref.read(collectionStateNotifierProvider.notifier).sendRequest();

View File

@ -1,21 +1,34 @@
import 'package:apidash/screens/common_widgets/common_widgets.dart';
import 'package:apidash_design_system/apidash_design_system.dart'; import 'package:apidash_design_system/apidash_design_system.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:apidash/widgets/widgets.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/details_card/response_pane.dart';
import '../../home_page/editor_pane/editor_request.dart'; import '../../home_page/editor_pane/editor_request.dart';
import '../../home_page/editor_pane/url_card.dart'; import '../../home_page/editor_pane/url_card.dart';
class RequestTabs extends StatelessWidget { class RequestTabs extends StatelessWidget {
const RequestTabs({super.key, required this.controller}); const RequestTabs({
super.key,
required this.controller,
});
final TabController controller; final TabController controller;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Column( return Column(
children: [ children: [
kVSpacer5, const Padding(
padding: kPh8,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
APITypeDropdown(),
EnvironmentDropdown(),
],
),
),
kVSpacer3,
const Padding( const Padding(
padding: kPh4, padding: kPh4,
child: EditorPaneRequestURLCard(), child: EditorPaneRequestURLCard(),

View File

@ -40,19 +40,19 @@ class _RequestResponsePageState extends ConsumerState<RequestResponsePage>
showRenameDialog(context, "Rename Request", name, (val) { showRenameDialog(context, "Rename Request", name, (val) {
ref ref
.read(collectionStateNotifierProvider.notifier) .read(collectionStateNotifierProvider.notifier)
.update(id!, name: val); .update(name: val);
}); });
} }
if (item == ItemMenuOption.delete) { if (item == ItemMenuOption.delete) {
ref.read(collectionStateNotifierProvider.notifier).remove(id!); ref.read(collectionStateNotifierProvider.notifier).remove();
} }
if (item == ItemMenuOption.duplicate) { if (item == ItemMenuOption.duplicate) {
ref.read(collectionStateNotifierProvider.notifier).duplicate(id!); ref.read(collectionStateNotifierProvider.notifier).duplicate();
} }
}, },
), ),
leftDrawerContent: const CollectionPane(), leftDrawerContent: const CollectionPane(),
actions: const [Padding(padding: kPh8, child: EnvironmentDropdown())], actions: const [kVSpacer16],
mainContent: id == null mainContent: id == null
? const RequestEditorDefault() ? const RequestEditorDefault()
: RequestTabs( : RequestTabs(

View File

@ -140,6 +140,11 @@ class HiveHandler {
Future<void> deleteHistoryRequest(String id) => historyLazyBox.delete(id); Future<void> deleteHistoryRequest(String id) => historyLazyBox.delete(id);
Future clearAllHistory() async {
await historyMetaBox.clear();
await historyLazyBox.clear();
}
Future clear() async { Future clear() async {
await dataBox.clear(); await dataBox.clear();
await environmentBox.clear(); await environmentBox.clear();

View File

@ -4,7 +4,7 @@ import 'package:apidash/models/models.dart';
String getEnvironmentTitle(String? name) { String getEnvironmentTitle(String? name) {
if (name == null || name.trim() == "") { if (name == null || name.trim() == "") {
return "untitled"; return kUntitled;
} }
return name; return name;
} }

View File

@ -9,6 +9,7 @@ DateTime stripTime(DateTime dateTime) {
RequestModel getRequestModelFromHistoryModel(HistoryRequestModel model) { RequestModel getRequestModelFromHistoryModel(HistoryRequestModel model) {
return RequestModel( return RequestModel(
id: model.historyId, id: model.historyId,
apiType: model.metaData.apiType,
name: model.metaData.name, name: model.metaData.name,
responseStatus: model.httpResponseModel.statusCode, responseStatus: model.httpResponseModel.statusCode,
message: kResponseCodeReasons[model.httpResponseModel.statusCode], message: kResponseCodeReasons[model.httpResponseModel.statusCode],

View File

@ -3,12 +3,12 @@ import '../consts.dart';
String getRequestTitleFromUrl(String? url) { String getRequestTitleFromUrl(String? url) {
if (url == null || url.trim() == "") { if (url == null || url.trim() == "") {
return "untitled"; return kUntitled;
} }
if (url.contains("://")) { if (url.contains("://")) {
String rem = url.split("://")[1]; String rem = url.split("://")[1];
if (rem.trim() == "") { if (rem.trim() == "") {
return "untitled"; return kUntitled;
} }
return rem; return rem;
} }

View File

@ -3,12 +3,13 @@ import 'package:apidash_design_system/apidash_design_system.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:apidash/models/models.dart'; import 'package:apidash/models/models.dart';
import 'package:apidash/utils/utils.dart'; import 'package:apidash/utils/utils.dart';
import 'texts.dart' show MethodBox; import 'texts.dart';
class SidebarHistoryCard extends StatelessWidget { class SidebarHistoryCard extends StatelessWidget {
const SidebarHistoryCard({ const SidebarHistoryCard({
super.key, super.key,
required this.id, required this.id,
required this.apiType,
required this.models, required this.models,
required this.method, required this.method,
this.isSelected = false, this.isSelected = false,
@ -17,6 +18,7 @@ class SidebarHistoryCard extends StatelessWidget {
}); });
final String id; final String id;
final APIType apiType;
final List<HistoryMetaModel> models; final List<HistoryMetaModel> models;
final HTTPVerb method; final HTTPVerb method;
final bool isSelected; final bool isSelected;
@ -63,7 +65,10 @@ class SidebarHistoryCard extends StatelessWidget {
height: 20, height: 20,
child: Row( child: Row(
children: [ children: [
MethodBox(method: method), SidebarRequestCardTextBox(
apiType: apiType,
method: method,
),
kHSpacer4, kHSpacer4,
Expanded( Expanded(
child: Text( child: Text(

View File

@ -4,12 +4,13 @@ import 'package:flutter/material.dart';
import 'package:apidash/consts.dart'; import 'package:apidash/consts.dart';
import 'package:apidash/utils/utils.dart'; import 'package:apidash/utils/utils.dart';
import 'menu_item_card.dart'; import 'menu_item_card.dart';
import 'texts.dart' show MethodBox; import 'texts.dart';
class SidebarRequestCard extends StatelessWidget { class SidebarRequestCard extends StatelessWidget {
const SidebarRequestCard({ const SidebarRequestCard({
super.key, super.key,
required this.id, required this.id,
required this.apiType,
required this.method, required this.method,
this.name, this.name,
this.url, this.url,
@ -26,6 +27,7 @@ class SidebarRequestCard extends StatelessWidget {
}); });
final String id; final String id;
final APIType apiType;
final String? name; final String? name;
final String? url; final String? url;
final HTTPVerb method; final HTTPVerb method;
@ -88,7 +90,10 @@ class SidebarRequestCard extends StatelessWidget {
height: 20, height: 20,
child: Row( child: Row(
children: [ children: [
MethodBox(method: method), SidebarRequestCardTextBox(
apiType: apiType,
method: method,
),
kHSpacer4, kHSpacer4,
Expanded( Expanded(
child: inEditMode child: inEditMode

View File

@ -1,4 +1,5 @@
import 'dart:math' as math; import 'dart:math' as math;
import 'package:apidash/consts.dart';
import 'package:apidash_design_system/apidash_design_system.dart'; import 'package:apidash_design_system/apidash_design_system.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
@ -10,12 +11,14 @@ class TextFieldEditor extends StatefulWidget {
this.onChanged, this.onChanged,
this.initialValue, this.initialValue,
this.readOnly = false, this.readOnly = false,
this.hintText,
}); });
final String fieldKey; final String fieldKey;
final Function(String)? onChanged; final Function(String)? onChanged;
final String? initialValue; final String? initialValue;
final bool readOnly; final bool readOnly;
final String? hintText;
@override @override
State<TextFieldEditor> createState() => _TextFieldEditorState(); State<TextFieldEditor> createState() => _TextFieldEditorState();
} }
@ -72,14 +75,16 @@ class _TextFieldEditorState extends State<TextFieldEditor> {
expands: true, expands: true,
maxLines: null, maxLines: null,
readOnly: widget.readOnly, readOnly: widget.readOnly,
style: kCodeStyle, style: kCodeStyle.copyWith(
fontSize: Theme.of(context).textTheme.bodyMedium?.fontSize,
),
textAlignVertical: TextAlignVertical.top, textAlignVertical: TextAlignVertical.top,
onChanged: widget.onChanged, onChanged: widget.onChanged,
onTapOutside: (PointerDownEvent event) { onTapOutside: (PointerDownEvent event) {
editorFocusNode.unfocus(); editorFocusNode.unfocus();
}, },
decoration: InputDecoration( decoration: InputDecoration(
hintText: "Enter content (body)", hintText: widget.hintText ?? kHintContent,
hintStyle: TextStyle( hintStyle: TextStyle(
color: Theme.of(context).colorScheme.outline.withOpacity( color: Theme.of(context).colorScheme.outline.withOpacity(
kHintOpacity, kHintOpacity,

View File

@ -1,4 +1,5 @@
import 'dart:math' as math; import 'dart:math' as math;
import 'package:apidash/consts.dart';
import 'package:apidash_design_system/apidash_design_system.dart'; import 'package:apidash_design_system/apidash_design_system.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
@ -93,7 +94,7 @@ class _JsonTextFieldEditorState extends State<JsonTextFieldEditor> {
widget.onChanged?.call(value); widget.onChanged?.call(value);
}, },
decoration: InputDecoration( decoration: InputDecoration(
hintText: "Enter content (body)", hintText: kHintJson,
hintStyle: TextStyle( hintStyle: TextStyle(
color: Theme.of(context).colorScheme.outline.withOpacity( color: Theme.of(context).colorScheme.outline.withOpacity(
kHintOpacity, kHintOpacity,

View File

@ -2,10 +2,10 @@ import 'package:apidash_core/apidash_core.dart';
import 'package:apidash_design_system/apidash_design_system.dart'; import 'package:apidash_design_system/apidash_design_system.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class DropdownButtonAPIType extends StatelessWidget { class APITypePopupMenu extends StatelessWidget {
const DropdownButtonAPIType({ const APITypePopupMenu({
super.key, super.key,
this.apiType, required this.apiType,
this.onChanged, this.onChanged,
}); });
@ -14,11 +14,13 @@ class DropdownButtonAPIType extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ADDropdownButton<APIType>( return ADPopupMenu<APIType>(
value: apiType, tooltip: "Select API Type",
width: 100,
value: apiType?.label,
values: APIType.values.map((e) => (e, e.label)), values: APIType.values.map((e) => (e, e.label)),
onChanged: onChanged, onChanged: onChanged,
isDense: true, isOutlined: true,
); );
} }
} }

View File

@ -12,6 +12,7 @@ class RequestPane extends StatefulHookWidget {
this.tabIndex, this.tabIndex,
this.onPressedCodeButton, this.onPressedCodeButton,
this.onTapTabBar, this.onTapTabBar,
required this.tabLabels,
required this.children, required this.children,
this.showIndicators = const [false, false, false], this.showIndicators = const [false, false, false],
this.showViewCodeButton, this.showViewCodeButton,
@ -22,6 +23,7 @@ class RequestPane extends StatefulHookWidget {
final int? tabIndex; final int? tabIndex;
final void Function()? onPressedCodeButton; final void Function()? onPressedCodeButton;
final void Function(int)? onTapTabBar; final void Function(int)? onTapTabBar;
final List<String> tabLabels;
final List<Widget> children; final List<Widget> children;
final List<bool> showIndicators; final List<bool> showIndicators;
final bool? showViewCodeButton; final bool? showViewCodeButton;
@ -35,7 +37,7 @@ class _RequestPaneState extends State<RequestPane>
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final TabController controller = useTabController( final TabController controller = useTabController(
initialLength: 3, initialLength: widget.children.length,
vsync: this, vsync: this,
); );
if (widget.tabIndex != null) { if (widget.tabIndex != null) {
@ -75,27 +77,21 @@ class _RequestPaneState extends State<RequestPane>
), ),
), ),
) )
: const SizedBox.shrink(), : kVSpacer10,
TabBar( TabBar(
key: Key(widget.selectedId!), key: Key(widget.selectedId!),
controller: controller, controller: controller,
overlayColor: kColorTransparentState, overlayColor: kColorTransparentState,
labelPadding: kPh2, labelPadding: kPh2,
onTap: widget.onTapTabBar, onTap: widget.onTapTabBar,
tabs: [ tabs: widget.tabLabels.indexed
TabLabel( .map<Widget>(
text: kLabelURLParams, (e) => TabLabel(
showIndicator: widget.showIndicators[0], text: e.$2,
showIndicator: widget.showIndicators[e.$1],
), ),
TabLabel( )
text: kLabelHeaders, .toList(),
showIndicator: widget.showIndicators[1],
),
TabLabel(
text: kLabelBody,
showIndicator: widget.showIndicators[2],
),
],
), ),
kVSpacer5, kVSpacer5,
Expanded( Expanded(

View File

@ -3,11 +3,13 @@ import 'package:apidash_design_system/apidash_design_system.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:apidash/utils/utils.dart'; import 'package:apidash/utils/utils.dart';
class MethodBox extends StatelessWidget { class SidebarRequestCardTextBox extends StatelessWidget {
const MethodBox({ const SidebarRequestCardTextBox({
super.key, super.key,
required this.apiType,
required this.method, required this.method,
}); });
final APIType apiType;
final HTTPVerb method; final HTTPVerb method;
@override @override
@ -15,15 +17,21 @@ class MethodBox extends StatelessWidget {
return SizedBox( return SizedBox(
width: 24, width: 24,
child: Text( child: Text(
method.abbr, switch (apiType) {
APIType.rest => method.abbr,
APIType.graphql => apiType.abbr,
},
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle( style: TextStyle(
fontSize: 8, fontSize: 8,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: getHTTPMethodColor( color: switch (apiType) {
APIType.rest => getHTTPMethodColor(
method, method,
brightness: Theme.of(context).brightness, brightness: Theme.of(context).brightness,
), ),
APIType.graphql => kColorGQL,
},
), ),
), ),
); );

View File

@ -24,7 +24,6 @@ export 'dropdown_content_type.dart';
export 'dropdown_formdata.dart'; export 'dropdown_formdata.dart';
export 'dropdown_http_method.dart'; export 'dropdown_http_method.dart';
export 'dropdown_import_format.dart'; export 'dropdown_import_format.dart';
export 'dropdown_api_type.dart';
export 'editor_json.dart'; export 'editor_json.dart';
export 'editor.dart'; export 'editor.dart';
export 'error_message.dart'; export 'error_message.dart';
@ -40,12 +39,13 @@ export 'markdown.dart';
export 'menu_item_card.dart'; export 'menu_item_card.dart';
export 'menu_sidebar_top.dart'; export 'menu_sidebar_top.dart';
export 'overlay_widget.dart'; export 'overlay_widget.dart';
export 'popup_menu_api_type.dart';
export 'popup_menu_codegen.dart'; export 'popup_menu_codegen.dart';
export 'popup_menu_env.dart'; export 'popup_menu_env.dart';
export 'popup_menu_history.dart'; export 'popup_menu_history.dart';
export 'popup_menu_uri.dart'; export 'popup_menu_uri.dart';
export 'previewer.dart'; export 'previewer.dart';
export 'request_widgets.dart'; export 'request_pane.dart';
export 'response_widgets.dart'; export 'response_widgets.dart';
export 'splitview_drawer.dart'; export 'splitview_drawer.dart';
export 'splitview_dashboard.dart'; export 'splitview_dashboard.dart';

View File

@ -8,6 +8,14 @@ scripts:
run: melos exec -- "flutter analyze" run: melos exec -- "flutter analyze"
description: Analyze all packages 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: test:
run: melos exec --dir-exists=test -- "flutter test --coverage" run: melos exec --dir-exists=test -- "flutter test --coverage"
description: Run tests for all packages description: Run tests for all packages

View File

@ -1,10 +1,12 @@
import 'dart:convert'; import 'dart:convert';
enum APIType { 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 label;
final String abbr;
} }
enum HTTPVerb { enum HTTPVerb {

View File

@ -19,3 +19,15 @@ extension StringExtension on String {
return "${substring(0, limit)}..."; return "${substring(0, limit)}...";
} }
} }
extension StringOrNullExtension on String? {
bool isNullOrEmpty() {
if (this == null) {
return true;
}
if (this!.isEmpty) {
return true;
}
return false;
}
}

View File

@ -7,7 +7,6 @@ import '../utils/utils.dart'
import '../consts.dart'; import '../consts.dart';
part 'http_request_model.freezed.dart'; part 'http_request_model.freezed.dart';
part 'http_request_model.g.dart'; part 'http_request_model.g.dart';
@freezed @freezed
@ -27,6 +26,7 @@ class HttpRequestModel with _$HttpRequestModel {
List<bool>? isParamEnabledList, List<bool>? isParamEnabledList,
@Default(ContentType.json) ContentType bodyContentType, @Default(ContentType.json) ContentType bodyContentType,
String? body, String? body,
String? query,
List<FormDataModel>? formData, List<FormDataModel>? formData,
}) = _HttpRequestModel; }) = _HttpRequestModel;
@ -61,6 +61,7 @@ class HttpRequestModel with _$HttpRequestModel {
kMethodsWithBody.contains(method) && kMethodsWithBody.contains(method) &&
hasFormDataContentType && hasFormDataContentType &&
formDataMapList.isNotEmpty; formDataMapList.isNotEmpty;
bool get hasQuery => query?.isNotEmpty ?? false;
List<FormDataModel> get formDataList => formData ?? <FormDataModel>[]; List<FormDataModel> get formDataList => formData ?? <FormDataModel>[];
List<Map<String, String>> get formDataMapList => List<Map<String, String>> get formDataMapList =>
rowsToFormDataMapList(formDataList) ?? []; rowsToFormDataMapList(formDataList) ?? [];

View File

@ -28,6 +28,7 @@ mixin _$HttpRequestModel {
List<bool>? get isParamEnabledList => throw _privateConstructorUsedError; List<bool>? get isParamEnabledList => throw _privateConstructorUsedError;
ContentType get bodyContentType => throw _privateConstructorUsedError; ContentType get bodyContentType => throw _privateConstructorUsedError;
String? get body => throw _privateConstructorUsedError; String? get body => throw _privateConstructorUsedError;
String? get query => throw _privateConstructorUsedError;
List<FormDataModel>? get formData => throw _privateConstructorUsedError; List<FormDataModel>? get formData => throw _privateConstructorUsedError;
/// Serializes this HttpRequestModel to a JSON map. /// Serializes this HttpRequestModel to a JSON map.
@ -55,6 +56,7 @@ abstract class $HttpRequestModelCopyWith<$Res> {
List<bool>? isParamEnabledList, List<bool>? isParamEnabledList,
ContentType bodyContentType, ContentType bodyContentType,
String? body, String? body,
String? query,
List<FormDataModel>? formData}); List<FormDataModel>? formData});
} }
@ -81,6 +83,7 @@ class _$HttpRequestModelCopyWithImpl<$Res, $Val extends HttpRequestModel>
Object? isParamEnabledList = freezed, Object? isParamEnabledList = freezed,
Object? bodyContentType = null, Object? bodyContentType = null,
Object? body = freezed, Object? body = freezed,
Object? query = freezed,
Object? formData = freezed, Object? formData = freezed,
}) { }) {
return _then(_value.copyWith( return _then(_value.copyWith(
@ -116,6 +119,10 @@ class _$HttpRequestModelCopyWithImpl<$Res, $Val extends HttpRequestModel>
? _value.body ? _value.body
: body // ignore: cast_nullable_to_non_nullable : body // ignore: cast_nullable_to_non_nullable
as String?, as String?,
query: freezed == query
? _value.query
: query // ignore: cast_nullable_to_non_nullable
as String?,
formData: freezed == formData formData: freezed == formData
? _value.formData ? _value.formData
: formData // ignore: cast_nullable_to_non_nullable : formData // ignore: cast_nullable_to_non_nullable
@ -141,6 +148,7 @@ abstract class _$$HttpRequestModelImplCopyWith<$Res>
List<bool>? isParamEnabledList, List<bool>? isParamEnabledList,
ContentType bodyContentType, ContentType bodyContentType,
String? body, String? body,
String? query,
List<FormDataModel>? formData}); List<FormDataModel>? formData});
} }
@ -165,6 +173,7 @@ class __$$HttpRequestModelImplCopyWithImpl<$Res>
Object? isParamEnabledList = freezed, Object? isParamEnabledList = freezed,
Object? bodyContentType = null, Object? bodyContentType = null,
Object? body = freezed, Object? body = freezed,
Object? query = freezed,
Object? formData = freezed, Object? formData = freezed,
}) { }) {
return _then(_$HttpRequestModelImpl( return _then(_$HttpRequestModelImpl(
@ -200,6 +209,10 @@ class __$$HttpRequestModelImplCopyWithImpl<$Res>
? _value.body ? _value.body
: body // ignore: cast_nullable_to_non_nullable : body // ignore: cast_nullable_to_non_nullable
as String?, as String?,
query: freezed == query
? _value.query
: query // ignore: cast_nullable_to_non_nullable
as String?,
formData: freezed == formData formData: freezed == formData
? _value._formData ? _value._formData
: formData // ignore: cast_nullable_to_non_nullable : formData // ignore: cast_nullable_to_non_nullable
@ -221,6 +234,7 @@ class _$HttpRequestModelImpl extends _HttpRequestModel {
final List<bool>? isParamEnabledList, final List<bool>? isParamEnabledList,
this.bodyContentType = ContentType.json, this.bodyContentType = ContentType.json,
this.body, this.body,
this.query,
final List<FormDataModel>? formData}) final List<FormDataModel>? formData})
: _headers = headers, : _headers = headers,
_params = params, _params = params,
@ -285,6 +299,8 @@ class _$HttpRequestModelImpl extends _HttpRequestModel {
final ContentType bodyContentType; final ContentType bodyContentType;
@override @override
final String? body; final String? body;
@override
final String? query;
final List<FormDataModel>? _formData; final List<FormDataModel>? _formData;
@override @override
List<FormDataModel>? get formData { List<FormDataModel>? get formData {
@ -297,7 +313,7 @@ class _$HttpRequestModelImpl extends _HttpRequestModel {
@override @override
String toString() { 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 @override
@ -316,6 +332,7 @@ class _$HttpRequestModelImpl extends _HttpRequestModel {
(identical(other.bodyContentType, bodyContentType) || (identical(other.bodyContentType, bodyContentType) ||
other.bodyContentType == bodyContentType) && other.bodyContentType == bodyContentType) &&
(identical(other.body, body) || other.body == body) && (identical(other.body, body) || other.body == body) &&
(identical(other.query, query) || other.query == query) &&
const DeepCollectionEquality().equals(other._formData, _formData)); const DeepCollectionEquality().equals(other._formData, _formData));
} }
@ -331,6 +348,7 @@ class _$HttpRequestModelImpl extends _HttpRequestModel {
const DeepCollectionEquality().hash(_isParamEnabledList), const DeepCollectionEquality().hash(_isParamEnabledList),
bodyContentType, bodyContentType,
body, body,
query,
const DeepCollectionEquality().hash(_formData)); const DeepCollectionEquality().hash(_formData));
/// Create a copy of HttpRequestModel /// Create a copy of HttpRequestModel
@ -360,6 +378,7 @@ abstract class _HttpRequestModel extends HttpRequestModel {
final List<bool>? isParamEnabledList, final List<bool>? isParamEnabledList,
final ContentType bodyContentType, final ContentType bodyContentType,
final String? body, final String? body,
final String? query,
final List<FormDataModel>? formData}) = _$HttpRequestModelImpl; final List<FormDataModel>? formData}) = _$HttpRequestModelImpl;
const _HttpRequestModel._() : super._(); const _HttpRequestModel._() : super._();
@ -383,6 +402,8 @@ abstract class _HttpRequestModel extends HttpRequestModel {
@override @override
String? get body; String? get body;
@override @override
String? get query;
@override
List<FormDataModel>? get formData; List<FormDataModel>? get formData;
/// Create a copy of HttpRequestModel /// Create a copy of HttpRequestModel

View File

@ -29,6 +29,7 @@ _$HttpRequestModelImpl _$$HttpRequestModelImplFromJson(Map json) =>
$enumDecodeNullable(_$ContentTypeEnumMap, json['bodyContentType']) ?? $enumDecodeNullable(_$ContentTypeEnumMap, json['bodyContentType']) ??
ContentType.json, ContentType.json,
body: json['body'] as String?, body: json['body'] as String?,
query: json['query'] as String?,
formData: (json['formData'] as List<dynamic>?) formData: (json['formData'] as List<dynamic>?)
?.map((e) => ?.map((e) =>
FormDataModel.fromJson(Map<String, Object?>.from(e as Map))) FormDataModel.fromJson(Map<String, Object?>.from(e as Map)))
@ -46,6 +47,7 @@ Map<String, dynamic> _$$HttpRequestModelImplToJson(
'isParamEnabledList': instance.isParamEnabledList, 'isParamEnabledList': instance.isParamEnabledList,
'bodyContentType': _$ContentTypeEnumMap[instance.bodyContentType]!, 'bodyContentType': _$ContentTypeEnumMap[instance.bodyContentType]!,
'body': instance.body, 'body': instance.body,
'query': instance.query,
'formData': instance.formData?.map((e) => e.toJson()).toList(), 'formData': instance.formData?.map((e) => e.toJson()).toList(),
}; };

View File

@ -10,7 +10,6 @@ import '../utils/utils.dart';
import '../consts.dart'; import '../consts.dart';
part 'http_response_model.freezed.dart'; part 'http_response_model.freezed.dart';
part 'http_response_model.g.dart'; part 'http_response_model.g.dart';
class Uint8ListConverter implements JsonConverter<Uint8List?, List<int>?> { class Uint8ListConverter implements JsonConverter<Uint8List?, List<int>?> {

View File

@ -12,6 +12,7 @@ typedef HttpResponse = http.Response;
Future<(HttpResponse?, Duration?, String?)> request( Future<(HttpResponse?, Duration?, String?)> request(
String requestId, String requestId,
APIType apiType,
HttpRequestModel requestModel, { HttpRequestModel requestModel, {
SupportedUriSchemes defaultUriScheme = kDefaultUriScheme, SupportedUriSchemes defaultUriScheme = kDefaultUriScheme,
bool noSSL = false, bool noSSL = false,
@ -28,10 +29,11 @@ Future<(HttpResponse?, Duration?, String?)> request(
if (uriRec.$1 != null) { if (uriRec.$1 != null) {
Uri requestUrl = uriRec.$1!; Uri requestUrl = uriRec.$1!;
Map<String, String> headers = requestModel.enabledHeadersMap; Map<String, String> headers = requestModel.enabledHeadersMap;
HttpResponse response; HttpResponse? response;
String? body; String? body;
try { try {
Stopwatch stopwatch = Stopwatch()..start(); Stopwatch stopwatch = Stopwatch()..start();
if (apiType == APIType.rest) {
var isMultiPartRequest = var isMultiPartRequest =
requestModel.bodyContentType == ContentType.formdata; requestModel.bodyContentType == ContentType.formdata;
@ -41,7 +43,8 @@ Future<(HttpResponse?, Duration?, String?)> request(
var contentLength = utf8.encode(requestBody).length; var contentLength = utf8.encode(requestBody).length;
if (contentLength > 0) { if (contentLength > 0) {
body = requestBody; body = requestBody;
headers[HttpHeaders.contentLengthHeader] = contentLength.toString(); headers[HttpHeaders.contentLengthHeader] =
contentLength.toString();
if (!requestModel.hasContentTypeHeader) { if (!requestModel.hasContentTypeHeader) {
headers[HttpHeaders.contentTypeHeader] = headers[HttpHeaders.contentTypeHeader] =
requestModel.bodyContentType.header; requestModel.bodyContentType.header;
@ -86,7 +89,8 @@ Future<(HttpResponse?, Duration?, String?)> request(
await client.post(requestUrl, headers: headers, body: body); await client.post(requestUrl, headers: headers, body: body);
break; break;
case HTTPVerb.put: case HTTPVerb.put:
response = await client.put(requestUrl, headers: headers, body: body); response =
await client.put(requestUrl, headers: headers, body: body);
break; break;
case HTTPVerb.patch: case HTTPVerb.patch:
response = response =
@ -97,6 +101,25 @@ Future<(HttpResponse?, Duration?, String?)> request(
await client.delete(requestUrl, headers: headers, body: body); await client.delete(requestUrl, headers: headers, body: body);
break; 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] = ContentType.json.header;
}
}
}
response = await client.post(
requestUrl,
headers: headers,
body: body,
);
}
stopwatch.stop(); stopwatch.stop();
return (response, stopwatch.elapsed, null); return (response, stopwatch.elapsed, null);
} catch (e) { } catch (e) {

View File

@ -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;
}

View File

@ -1,5 +1,8 @@
import 'package:apidash_core/consts.dart';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:seed/seed.dart'; import 'package:seed/seed.dart';
import '../models/models.dart';
import 'graphql_utils.dart';
Map<String, String>? rowsToMap( Map<String, String>? rowsToMap(
List<NameValueModel>? kvRows, { List<NameValueModel>? kvRows, {
@ -88,3 +91,13 @@ List<NameValueModel>? getEnabledRows(
rows.where((element) => isRowEnabledList[rows.indexOf(element)]).toList(); rows.where((element) => isRowEnabledList[rows.indexOf(element)]).toList();
return finalRows == [] ? null : finalRows; 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),
};
}

View File

@ -1,4 +1,5 @@
export 'content_type_utils.dart'; export 'content_type_utils.dart';
export 'graphql_utils.dart';
export 'http_request_utils.dart'; export 'http_request_utils.dart';
export 'http_response_utils.dart'; export 'http_response_utils.dart';
export 'string_utils.dart'; export 'string_utils.dart';

View File

@ -21,7 +21,7 @@ dependencies:
path: ../postman path: ../postman
insomnia_collection: insomnia_collection:
path: ../insomnia_collection path: ../insomnia_collection
seed: ^0.0.2 seed: ^0.0.3
xml: ^6.3.0 xml: ^6.3.0
dev_dependencies: dev_dependencies:
@ -30,4 +30,5 @@ dev_dependencies:
build_runner: ^2.4.12 build_runner: ^2.4.12
flutter_lints: ^4.0.0 flutter_lints: ^4.0.0
freezed: ^2.5.7 freezed: ^2.5.7
json_serializable: ^6.7.1
test: ^1.25.2 test: ^1.25.2

View File

@ -1,6 +1,8 @@
# melos_managed_dependency_overrides: seed,curl_parser # melos_managed_dependency_overrides: curl_parser,postman,seed
dependency_overrides: dependency_overrides:
curl_parser: curl_parser:
path: ../curl_parser path: ../curl_parser
postman:
path: ../postman
seed: seed:
path: ../seed path: ../seed

View File

@ -24,6 +24,8 @@ final kColorHttpMethodPut = Colors.amber.shade900;
final kColorHttpMethodPatch = kColorHttpMethodPut; final kColorHttpMethodPatch = kColorHttpMethodPut;
final kColorHttpMethodDelete = Colors.red.shade800; final kColorHttpMethodDelete = Colors.red.shade800;
final kColorGQL = Colors.pink.shade600;
const kHintOpacity = 0.6; const kHintOpacity = 0.6;
const kForegroundOpacity = 0.05; const kForegroundOpacity = 0.05;
const kOverlayBackgroundOpacity = 0.5; const kOverlayBackgroundOpacity = 0.5;

View File

@ -90,6 +90,7 @@ const kHSpacer10 = SizedBox(width: 10);
const kHSpacer12 = SizedBox(width: 12); const kHSpacer12 = SizedBox(width: 12);
const kHSpacer20 = SizedBox(width: 20); const kHSpacer20 = SizedBox(width: 20);
const kHSpacer40 = SizedBox(width: 40); const kHSpacer40 = SizedBox(width: 40);
const kVSpacer3 = SizedBox(height: 3);
const kVSpacer5 = SizedBox(height: 5); const kVSpacer5 = SizedBox(height: 5);
const kVSpacer8 = SizedBox(height: 8); const kVSpacer8 = SizedBox(height: 8);
const kVSpacer10 = SizedBox(height: 10); const kVSpacer10 = SizedBox(height: 10);

View File

@ -10,6 +10,7 @@ class ADPopupMenu<T> extends StatelessWidget {
this.tooltip, this.tooltip,
this.width, this.width,
this.isOutlined = false, this.isOutlined = false,
this.borderColor,
}); });
final String? value; final String? value;
@ -18,6 +19,7 @@ class ADPopupMenu<T> extends StatelessWidget {
final String? tooltip; final String? tooltip;
final double? width; final double? width;
final bool isOutlined; final bool isOutlined;
final Color? borderColor;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -62,7 +64,8 @@ class ADPopupMenu<T> extends StatelessWidget {
return Container( return Container(
decoration: BoxDecoration( decoration: BoxDecoration(
border: Border.all( border: Border.all(
color: Theme.of(context).colorScheme.onSurface, color: borderColor ??
Theme.of(context).colorScheme.surfaceContainerHighest,
), ),
borderRadius: kBorderRadius8, borderRadius: kBorderRadius8,
), ),

View File

@ -30,3 +30,4 @@ build/
.vscode/ .vscode/
coverage/ coverage/
pubspec_overrides.yaml

View File

@ -4,3 +4,4 @@ melos_curl_parser.iml
build/ build/
coverage/ coverage/
test/ test/
pubspec_overrides.yaml

View File

@ -1,3 +1,7 @@
## 0.1.2
- Bump dependencies.
## 0.1.1 ## 0.1.1
- Add formdata support and new test cases. - Add formdata support and new test cases.

View File

@ -1,6 +1,6 @@
name: curl_parser name: curl_parser
description: Parse cURL command to Dart object and convert Dart object to cURL command. 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 homepage: https://github.com/foss42/apidash/tree/main/packages/curl_parser
repository: 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 issue_tracker: https://github.com/foss42/apidash/issues
@ -19,7 +19,7 @@ environment:
dependencies: dependencies:
args: ^2.5.0 args: ^2.5.0
equatable: ^2.0.5 equatable: ^2.0.5
seed: ^0.0.1 seed: ^0.0.3
shlex: ^2.0.2 shlex: ^2.0.2
dev_dependencies: dev_dependencies:

View File

@ -1,4 +0,0 @@
# melos_managed_dependency_overrides: seed
dependency_overrides:
seed:
path: ../seed

View File

@ -1,3 +1,7 @@
## 0.0.3
- Fix: Add `json_serializable` under dev_dependencies.
## 0.0.2 ## 0.0.2
- Fix pubspec dependency for freezed_annotation. - Fix pubspec dependency for freezed_annotation.

View File

@ -1,6 +1,6 @@
name: seed name: seed
description: Seed is a foundational package designed to provide reusable building blocks for API Dash projects. 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 homepage: https://github.com/foss42/apidash/tree/main/packages/seed
repository: 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 issue_tracker: https://github.com/foss42/apidash/issues
@ -15,5 +15,6 @@ dependencies:
dev_dependencies: dev_dependencies:
build_runner: ^2.4.12 build_runner: ^2.4.12
freezed: ^2.5.7 freezed: ^2.5.7
json_serializable: ^6.7.1
lints: ^4.0.0 lints: ^4.0.0
test: ^1.24.0 test: ^1.24.0

View File

@ -306,7 +306,7 @@ packages:
path: "packages/curl_parser" path: "packages/curl_parser"
relative: true relative: true
source: path source: path
version: "0.1.1" version: "0.1.2"
dart_style: dart_style:
dependency: "direct main" dependency: "direct main"
description: description:
@ -1379,10 +1379,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: seed name: seed
sha256: "5c5ac5d73bf94e4b207d8c283903fc2b62ca8015e519b99949a8389605ee0eef" sha256: "0d74a46abd169c96a73d9dec4739e6623021915661beadf265e885bb1eafd214"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.0.2" version: "0.0.3"
shared_preferences: shared_preferences:
dependency: "direct main" dependency: "direct main"
description: description:

View File

@ -1,7 +1,7 @@
name: apidash 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. 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" publish_to: "none"
version: 0.4.0+4 version: 0.5.0+5
environment: environment:
sdk: ">=3.0.0 <4.0.0" sdk: ">=3.0.0 <4.0.0"

View File

@ -9,6 +9,7 @@ import 'http_response_models.dart';
final historyMetaModel1 = HistoryMetaModel( final historyMetaModel1 = HistoryMetaModel(
historyId: 'historyId1', historyId: 'historyId1',
requestId: 'requestId1', requestId: 'requestId1',
apiType: APIType.rest,
url: 'https://api.apidash.dev/humanize/social', url: 'https://api.apidash.dev/humanize/social',
method: HTTPVerb.get, method: HTTPVerb.get,
timeStamp: DateTime(2024, 1, 1), timeStamp: DateTime(2024, 1, 1),
@ -26,6 +27,7 @@ final historyRequestModel1 = HistoryRequestModel(
final historyMetaModel2 = HistoryMetaModel( final historyMetaModel2 = HistoryMetaModel(
historyId: 'historyId2', historyId: 'historyId2',
requestId: 'requestId2', requestId: 'requestId2',
apiType: APIType.rest,
url: 'https://api.apidash.dev/case/lower', url: 'https://api.apidash.dev/case/lower',
method: HTTPVerb.post, method: HTTPVerb.post,
timeStamp: DateTime(2024, 1, 1), timeStamp: DateTime(2024, 1, 1),
@ -43,6 +45,7 @@ final historyRequestModel2 = HistoryRequestModel(
final Map<String, dynamic> historyMetaModelJson1 = { final Map<String, dynamic> historyMetaModelJson1 = {
"historyId": "historyId1", "historyId": "historyId1",
"requestId": "requestId1", "requestId": "requestId1",
"apiType": "rest",
"name": "", "name": "",
"url": "https://api.apidash.dev/humanize/social", "url": "https://api.apidash.dev/humanize/social",
"method": "get", "method": "get",
@ -60,6 +63,7 @@ final Map<String, dynamic> historyRequestModelJson1 = {
final Map<String, dynamic> historyMetaModelJson2 = { final Map<String, dynamic> historyMetaModelJson2 = {
"historyId": "historyId2", "historyId": "historyId2",
"requestId": "requestId2", "requestId": "requestId2",
"apiType": "rest",
"name": "", "name": "",
"url": "https://api.apidash.dev/case/lower", "url": "https://api.apidash.dev/case/lower",
"method": "post", "method": "post",

View File

@ -388,6 +388,7 @@ const httpRequestModelGet4Json = <String, dynamic>{
"isParamEnabledList": null, "isParamEnabledList": null,
"bodyContentType": "json", "bodyContentType": "json",
"body": null, "body": null,
"query": null,
"formData": null "formData": null
}; };
@ -408,6 +409,7 @@ const httpRequestModelPost10Json = <String, dynamic>{
"body": '''{ "body": '''{
"text": "I LOVE Flutter" "text": "I LOVE Flutter"
}''', }''',
"query": null,
'formData': [ 'formData': [
{'name': 'token', 'value': 'xyz', 'type': 'text'}, {'name': 'token', 'value': 'xyz', 'type': 'text'},
{'name': 'imfile', 'value': '/Documents/up/1.png', 'type': 'file'} {'name': 'imfile', 'value': '/Documents/up/1.png', 'type': 'file'}

View File

@ -16,6 +16,7 @@ void main() {
test('Testing fromResponse', () async { test('Testing fromResponse', () async {
(HttpResponse?, Duration?, String?)? responseRec = await request( (HttpResponse?, Duration?, String?)? responseRec = await request(
requestModelGet1.id, requestModelGet1.id,
requestModelGet1.apiType,
requestModelGet1.httpRequestModel!, requestModelGet1.httpRequestModel!,
defaultUriScheme: kDefaultUriScheme, defaultUriScheme: kDefaultUriScheme,
noSSL: false, noSSL: false,
@ -33,6 +34,7 @@ void main() {
test('Testing fromResponse for contentType not Json', () async { test('Testing fromResponse for contentType not Json', () async {
(HttpResponse?, Duration?, String?)? responseRec = await request( (HttpResponse?, Duration?, String?)? responseRec = await request(
requestModelGet13.id, requestModelGet13.id,
requestModelGet1.apiType,
requestModelGet13.httpRequestModel!, requestModelGet13.httpRequestModel!,
defaultUriScheme: kDefaultUriScheme, defaultUriScheme: kDefaultUriScheme,
noSSL: false, noSSL: false,
@ -48,6 +50,7 @@ void main() {
test('Testing fromResponse for Bad SSL with certificate check', () async { test('Testing fromResponse for Bad SSL with certificate check', () async {
(HttpResponse?, Duration?, String?)? responseRec = await request( (HttpResponse?, Duration?, String?)? responseRec = await request(
requestModelGetBadSSL.id, requestModelGetBadSSL.id,
requestModelGet1.apiType,
requestModelGetBadSSL.httpRequestModel!, requestModelGetBadSSL.httpRequestModel!,
defaultUriScheme: kDefaultUriScheme, defaultUriScheme: kDefaultUriScheme,
noSSL: false, noSSL: false,
@ -59,6 +62,7 @@ void main() {
test('Testing fromResponse for Bad SSL with no certificate check', () async { test('Testing fromResponse for Bad SSL with no certificate check', () async {
(HttpResponse?, Duration?, String?)? responseRec = await request( (HttpResponse?, Duration?, String?)? responseRec = await request(
requestModelGetBadSSL.id, requestModelGetBadSSL.id,
requestModelGet1.apiType,
requestModelGetBadSSL.httpRequestModel!, requestModelGetBadSSL.httpRequestModel!,
defaultUriScheme: kDefaultUriScheme, defaultUriScheme: kDefaultUriScheme,
noSSL: true, noSSL: true,

View File

@ -56,20 +56,19 @@ const activeEnvVars = [
void main() { void main() {
group("Testing getEnvironmentTitle function", () { group("Testing getEnvironmentTitle function", () {
String titleUntitled = "untitled";
test("Testing getEnvironmentTitle with null", () { test("Testing getEnvironmentTitle with null", () {
String? envName1; String? envName1;
expect(getEnvironmentTitle(envName1), titleUntitled); expect(getEnvironmentTitle(envName1), kUntitled);
}); });
test("Testing getEnvironmentTitle with empty string", () { test("Testing getEnvironmentTitle with empty string", () {
String envName2 = ""; String envName2 = "";
expect(getEnvironmentTitle(envName2), titleUntitled); expect(getEnvironmentTitle(envName2), kUntitled);
}); });
test("Testing getEnvironmentTitle with trimmable string", () { test("Testing getEnvironmentTitle with trimmable string", () {
String envName3 = " "; String envName3 = " ";
expect(getEnvironmentTitle(envName3), titleUntitled); expect(getEnvironmentTitle(envName3), kUntitled);
}); });
test("Testing getEnvironmentTitle with non-empty string", () { test("Testing getEnvironmentTitle with non-empty string", () {

View File

@ -5,15 +5,14 @@ import 'package:apidash/consts.dart';
void main() { void main() {
group("Testing getRequestTitleFromUrl function", () { group("Testing getRequestTitleFromUrl function", () {
String titleUntitled = "untitled";
test('Testing getRequestTitleFromUrl using url1', () { test('Testing getRequestTitleFromUrl using url1', () {
String url1 = ""; String url1 = "";
expect(getRequestTitleFromUrl(url1), titleUntitled); expect(getRequestTitleFromUrl(url1), kUntitled);
}); });
test('Testing getRequestTitleFromUrl using url2', () { test('Testing getRequestTitleFromUrl using url2', () {
String url2 = " "; String url2 = " ";
expect(getRequestTitleFromUrl(url2), titleUntitled); expect(getRequestTitleFromUrl(url2), kUntitled);
}); });
test('Testing getRequestTitleFromUrl using url3', () { test('Testing getRequestTitleFromUrl using url3', () {
@ -30,11 +29,11 @@ void main() {
test('Testing getRequestTitleFromUrl using url5', () { test('Testing getRequestTitleFromUrl using url5', () {
String url5 = "http://"; String url5 = "http://";
expect(getRequestTitleFromUrl(url5), titleUntitled); expect(getRequestTitleFromUrl(url5), kUntitled);
}); });
test('Testing getRequestTitleFromUrl for null value', () { test('Testing getRequestTitleFromUrl for null value', () {
expect(getRequestTitleFromUrl(null), titleUntitled); expect(getRequestTitleFromUrl(null), kUntitled);
}); });
}); });

View File

@ -13,6 +13,7 @@ void main() {
final mockModel = HistoryMetaModel( final mockModel = HistoryMetaModel(
historyId: 'historyId', historyId: 'historyId',
requestId: 'requestId', requestId: 'requestId',
apiType: APIType.rest,
url: 'https://api.apidash.dev', url: 'https://api.apidash.dev',
method: HTTPVerb.get, method: HTTPVerb.get,
timeStamp: DateTime.now(), timeStamp: DateTime.now(),

View File

@ -11,6 +11,7 @@ void main() {
HistoryMetaModel( HistoryMetaModel(
historyId: 'historyId', historyId: 'historyId',
requestId: 'requestId', requestId: 'requestId',
apiType: APIType.rest,
url: 'https://api.apidash.dev', url: 'https://api.apidash.dev',
method: HTTPVerb.get, method: HTTPVerb.get,
timeStamp: DateTime.now(), timeStamp: DateTime.now(),
@ -30,6 +31,7 @@ void main() {
SidebarHistoryCard( SidebarHistoryCard(
id: '1', id: '1',
models: sampleModels, models: sampleModels,
apiType: APIType.rest,
method: HTTPVerb.get, method: HTTPVerb.get,
onTap: () { onTap: () {
changedValue = 'Tapped'; changedValue = 'Tapped';
@ -68,6 +70,7 @@ void main() {
children: [ children: [
SidebarHistoryCard( SidebarHistoryCard(
id: '1', id: '1',
apiType: APIType.rest,
models: sampleModels, models: sampleModels,
method: HTTPVerb.get, method: HTTPVerb.get,
onTap: () { onTap: () {

View File

@ -16,6 +16,7 @@ void main() {
children: [ children: [
SidebarRequestCard( SidebarRequestCard(
id: '23', id: '23',
apiType: APIType.rest,
selectedId: '2', selectedId: '2',
url: 'https://api.apidash.dev', url: 'https://api.apidash.dev',
method: HTTPVerb.get, method: HTTPVerb.get,
@ -61,6 +62,7 @@ void main() {
children: [ children: [
SidebarRequestCard( SidebarRequestCard(
id: '2', id: '2',
apiType: APIType.rest,
selectedId: '2', selectedId: '2',
editRequestId: '2', editRequestId: '2',
url: 'https://api.apidash.dev', url: 'https://api.apidash.dev',

View File

@ -1,3 +1,4 @@
import 'package:apidash/consts.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
@ -28,7 +29,7 @@ void main() {
expect(find.byType(TextFormField), findsOneWidget); expect(find.byType(TextFormField), findsOneWidget);
expect(find.byKey(const Key("2")), 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")); var txtForm = find.byKey(const Key("2"));
await tester.enterText(txtForm, 'entering 123 for testing content body'); await tester.enterText(txtForm, 'entering 123 for testing content body');
await tester.pump(); await tester.pump();
@ -66,7 +67,7 @@ void main() {
expect(find.text('initial'), findsOneWidget); expect(find.text('initial'), findsOneWidget);
expect(find.byType(TextFormField), findsOneWidget); expect(find.byType(TextFormField), findsOneWidget);
expect(find.byKey(const Key("2")), 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")); var txtForm = find.byKey(const Key("2"));
await tester.enterText(txtForm, 'entering 123 for testing content body'); await tester.enterText(txtForm, 'entering 123 for testing content body');
await tester.pump(); await tester.pump();

View File

@ -1,6 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.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 '../extensions/widget_tester_extensions.dart';
import '../test_consts.dart'; import '../test_consts.dart';
@ -15,8 +15,9 @@ void main() {
body: RequestPane( body: RequestPane(
selectedId: '1', selectedId: '1',
codePaneVisible: true, codePaneVisible: true,
children: const [Text('abc'), Text('xyz'), Text('mno')], tabLabels: const ['URL Params', 'Headers', 'Body'],
onPressedCodeButton: () {}, onPressedCodeButton: () {},
children: const [Text('abc'), Text('xyz'), Text('mno')],
), ),
), ),
), ),
@ -47,6 +48,7 @@ void main() {
codePaneVisible: true, codePaneVisible: true,
onPressedCodeButton: () {}, onPressedCodeButton: () {},
tabIndex: 1, tabIndex: 1,
tabLabels: const ['URL Params', 'Headers', 'Body'],
children: const [Text('abc'), Text('xyz'), Text('mno')], children: const [Text('abc'), Text('xyz'), Text('mno')],
), ),
), ),
@ -78,6 +80,7 @@ void main() {
codePaneVisible: false, codePaneVisible: false,
onPressedCodeButton: () {}, onPressedCodeButton: () {},
tabIndex: 2, tabIndex: 2,
tabLabels: const ['URL Params', 'Headers', 'Body'],
children: const [Text('abc'), Text('xyz'), Text('mno')], children: const [Text('abc'), Text('xyz'), Text('mno')],
), ),
), ),
@ -111,6 +114,7 @@ void main() {
onTapTabBar: (value) { onTapTabBar: (value) {
computedTabIndex = value; computedTabIndex = value;
}, },
tabLabels: const ['URL Params', 'Headers', 'Body'],
children: const [Text('abc'), Text('xyz'), Text('mno')], children: const [Text('abc'), Text('xyz'), Text('mno')],
), ),
), ),

View File

@ -8,12 +8,16 @@ import 'package:apidash/widgets/texts.dart';
void main() { void main() {
testWidgets('Testing when method is GET', (tester) async { testWidgets('Testing when method is GET', (tester) async {
var methodGet = HTTPVerb.get; var methodGet = HTTPVerb.get;
var apiType = APIType.rest;
await tester.pumpWidget( await tester.pumpWidget(
MaterialApp( MaterialApp(
title: 'Texts', title: 'Texts',
theme: ThemeData(brightness: Brightness.light), theme: ThemeData(brightness: Brightness.light),
home: Scaffold( 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 { testWidgets('Testing when method is DELETE', (tester) async {
var methodDel = HTTPVerb.delete; var methodDel = HTTPVerb.delete;
var apiType = APIType.rest;
await tester.pumpWidget( await tester.pumpWidget(
MaterialApp( MaterialApp(
title: 'Texts', title: 'Texts',
theme: ThemeData(brightness: Brightness.dark), theme: ThemeData(brightness: Brightness.dark),
home: Scaffold( home: Scaffold(
body: MethodBox(method: methodDel), body: SidebarRequestCardTextBox(
apiType: apiType,
method: methodDel,
),
), ),
), ),
); );