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
## v0.4.0 [WIP]
## v0.5.0 [WIP]
In this release, we have added the following features:
1. Create workspace (directory) to persist your entire data locally in the provided file-system location
2. Environment varibles
3. GraphQL support
4. Import requests from cURL & Postman Collection (v2.1)
5. History of your requests & response
6. Support for Request cancellation
7. Disable SSL verification
8. Codegen for Swift, hyper, etc.
A big shout-out to all the contributors 🎉
## v0.4.0 [iOS Release]
In this release, we have added the following features:
@ -15,7 +30,7 @@ In this release, we have added the following features:
9. Dart http codegen has been rewritten using dart code builder
10. Ability to override request contenttype
11. More header suggestions
12. Mobile platform support
## v0.3.0 [29-11-2023]

View File

@ -76,11 +76,31 @@ API Dash can be downloaded from the links below:
## List of Features
| API Type | Supported |
| --- | --- |
| HTTP | ✅ |
| GraphQL | ✅ |
| SSE | https://github.com/foss42/apidash/issues/116 |
| WebSocket | https://github.com/foss42/apidash/issues/15 |
| MQTT | https://github.com/foss42/apidash/issues/115 |
| gRPC | https://github.com/foss42/apidash/issues/14 |
| Import Collection From | Supported |
| --- | --- |
| Postman | ✅ |
| cURL | ✅ |
| Insomnia | https://github.com/foss42/apidash/issues/125 |
| OpenAPI | https://github.com/foss42/apidash/issues/121 |
| hurl | https://github.com/foss42/apidash/issues/123 |
| HAR | https://github.com/foss42/apidash/issues/122 |
**↗️ Create & Customize API Requests**
- Create different types of HTTP requests (`GET`, `HEAD`, `POST`, `PATCH`, `PUT` and `DELETE`).
- Easily manipulate and play around with request inputs like `headers`, `query parameters` and `body`.
- Full support to send text content with 🥳 Unicode/Emoji and preview any API response containing Unicode/Emoji.
- Create GraphQL requests with `headers` and `query`.
**💼 Organize Requests in Collections & Folders**
@ -222,7 +242,7 @@ Here is the complete list of MIME types that are syntax highlighted in API Dash:
| `text/javascript` | `.js` | |
| `text/markdown` | `.md` | |
## What's new in v0.3.0?
## What's new in v0.5.0?
Visit [CHANGELOG.md](CHANGELOG.md)

View File

@ -5,14 +5,14 @@
- [x] Remaining Code Generators (https://github.com/foss42/apidash/discussions/80)
- [x] Environment Variables (https://github.com/foss42/apidash/issues/25)
- [x] Integration Testing (https://github.com/foss42/apidash/issues/119)
- [ ] Paste not working on iOS and desktop (Right Click -> Paste) (https://github.com/foss42/apidash/issues/490)
- [x] Paste not working on iOS and desktop (Right Click -> Paste) (https://github.com/foss42/apidash/issues/490)
- [ ] WebSocket support (https://github.com/foss42/apidash/issues/15)
- [ ] SSE support (https://github.com/foss42/apidash/issues/116)
- [ ] MQTT support (https://github.com/foss42/apidash/issues/115)
- [ ] GraphQL support (https://github.com/foss42/apidash/issues/117)
- [x] GraphQL support (https://github.com/foss42/apidash/issues/117)
- [ ] gRPC support (https://github.com/foss42/apidash/issues/14)
- [ ] Figuring out how to build for various Linux packaging formats (https://github.com/foss42/apidash/discussions/240) [Docs]
- [ ] Save items in Response headers/body in an Environment variable to be used by other requests (https://github.com/foss42/apidash/issues/465)
- [ ] Save items in Response headers/body in an Environment variable to be used by other requests. Something like a post-processing script. (https://github.com/foss42/apidash/issues/465)
- [ ] Importers
- [ ] OpenAPI (https://github.com/foss42/apidash/issues/121)
- [ ] Insomnia (https://github.com/foss42/apidash/issues/125)

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)
- [Environment Variables Manager](https://github.com/foss42/apidash/blob/main/doc/user_guide/env_user_guide.md)
- [How to Disable SSL for Requests](https://github.com/foss42/apidash/blob/main/doc/user_guide/disable_ssl.md)
- Import Request for cURL (Doc TODO)
- How to use Code Generator (Doc TODO)
- Import Request for cURL (Contributions Welcome!)
- [How to Run Generated Code for a Programming Language](https://github.com/foss42/apidash/blob/main/doc/user_guide/instructions_to_run_generated_code.md) (Contributions Welcome!)
- [API Dash on Mobile](https://github.com/foss42/apidash/blob/main/doc/user_guide/req_user_guide.md)

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 {
Finder envItems = find.byType(EnvironmentItem);
Finder newEnvItem = envItems.at(1);
expect(find.descendant(of: newEnvItem, matching: find.text("untitled")),
expect(find.descendant(of: newEnvItem, matching: find.text(kUntitled)),
findsOneWidget);
Finder itemCardMenu =
find.descendant(of: newEnvItem, matching: find.byType(ItemCardMenu));

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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_riverpod/flutter_riverpod.dart';
import 'package:apidash/providers/providers.dart';
@ -39,6 +40,13 @@ class CodePane extends ConsumerWidget {
final code = codegen.getCode(
codegenLanguage, substitutedRequestModel!, defaultUriScheme);
// TODO: Add GraphQL Codegen
if (substitutedRequestModel.apiType == APIType.graphql) {
return const ErrorMessage(
message: "Code generation for GraphQL is currently not available.",
);
}
if (code == null) {
return const ErrorMessage(
message: "An error was encountered while generating code. $kRaiseIssue",

View File

@ -1,3 +1,4 @@
export 'api_type_dropdown.dart';
export 'button_navbar.dart';
export 'code_pane.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/utils/utils.dart';
import 'package:apidash/consts.dart';
import 'history_pane.dart';
import 'history_sidebar.dart';
import 'history_viewer.dart';
class HistoryPage extends ConsumerWidget {

View File

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

View File

@ -18,7 +18,8 @@ class HistoryRequestPane extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
final selectedId = ref.watch(selectedHistoryIdStateProvider);
final codePaneVisible = ref.watch(historyCodePaneVisibleStateProvider);
final apiType = ref.watch(selectedHistoryRequestModelProvider
.select((value) => value?.metaData.apiType));
final headersMap = ref.watch(selectedHistoryRequestModelProvider
.select((value) => value?.httpRequestModel.headersMap)) ??
{};
@ -33,7 +34,13 @@ class HistoryRequestPane extends ConsumerWidget {
.select((value) => value?.httpRequestModel.hasBody)) ??
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,
codePaneVisible: codePaneVisible,
onPressedCodeButton: () {
@ -46,6 +53,11 @@ class HistoryRequestPane extends ConsumerWidget {
headerLength > 0,
hasBody,
],
tabLabels: const [
kLabelURLParams,
kLabelHeaders,
kLabelBody,
],
children: [
RequestDataTable(
rows: paramsMap,
@ -57,7 +69,34 @@ class HistoryRequestPane extends ConsumerWidget {
),
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
Widget build(BuildContext context, WidgetRef ref) {
final selectedHistoryModel = ref.watch(selectedHistoryRequestModelProvider);
final apiType = selectedHistoryModel?.metaData.apiType;
final requestModel = selectedHistoryModel?.httpRequestModel;
final contentType = requestModel?.bodyContentType;
return Column(
return switch (apiType) {
APIType.rest => Column(
children: [
kVSpacer5,
RichText(
@ -94,8 +135,8 @@ class HisRequestBody extends ConsumerWidget {
child: switch (contentType) {
ContentType.formdata => Padding(
padding: kPh4,
child:
RequestFormDataTable(rows: requestModel?.formData ?? [])),
child: RequestFormDataTable(
rows: requestModel?.formData ?? [])),
// TODO: Fix JsonTextFieldEditor & plug it here
ContentType.json => Padding(
padding: kPt5o10,
@ -111,7 +152,8 @@ class HisRequestBody extends ConsumerWidget {
padding: kPt5o10,
child: TextFieldEditor(
key: Key("${selectedHistoryModel?.historyId}-body"),
fieldKey: "${selectedHistoryModel?.historyId}-body-viewer",
fieldKey:
"${selectedHistoryModel?.historyId}-body-viewer",
initialValue: requestModel?.body,
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:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
@ -21,11 +22,19 @@ class HistorySidebarHeader extends ConsumerWidget {
style: Theme.of(context).textTheme.titleMedium,
),
const Spacer(),
IconButton(
tooltip: "Manage History",
style: IconButton.styleFrom(
foregroundColor: Theme.of(context).colorScheme.primary,
ADIconButton(
icon: Icons.delete_forever,
iconSize: kButtonIconSizeLarge,
tooltip: "Clear History",
color: Theme.of(context).brightness == Brightness.dark
? kColorDarkDanger
: kColorLightDanger,
onPressed: () => hiveHandler.clearAllHistory(),
),
ADIconButton(
icon: Icons.manage_history_rounded,
iconSize: kButtonIconSizeLarge,
tooltip: "Manage History",
onPressed: () {
showHistoryRetentionDialog(
context,
@ -36,10 +45,6 @@ class HistorySidebarHeader extends ConsumerWidget {
);
});
},
icon: const Icon(
Icons.manage_history_rounded,
size: 20,
),
),
context.width <= kMinWindowSize.width
? IconButton(

View File

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

View File

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

View File

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

View File

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

View File

@ -29,9 +29,8 @@ class EditRequestHeadersState extends ConsumerState<EditRequestHeaders> {
seed = random.nextInt(kRandMax);
}
void _onFieldChange(String selectedId) {
void _onFieldChange() {
ref.read(collectionStateNotifierProvider.notifier).update(
selectedId,
headers: headerRows.sublist(0, headerRows.length - 1),
isHeaderEnabledList:
isRowEnabledList.sublist(0, headerRows.length - 1),
@ -99,7 +98,7 @@ class EditRequestHeadersState extends ConsumerState<EditRequestHeaders> {
setState(() {
isRowEnabledList[index] = value!;
});
_onFieldChange(selectedId!);
_onFieldChange();
},
colorScheme: Theme.of(context).colorScheme,
),
@ -117,7 +116,7 @@ class EditRequestHeadersState extends ConsumerState<EditRequestHeaders> {
headerRows.add(kNameValueEmptyModel);
isRowEnabledList.add(false);
}
_onFieldChange(selectedId!);
_onFieldChange();
},
colorScheme: Theme.of(context).colorScheme,
),
@ -143,7 +142,7 @@ class EditRequestHeadersState extends ConsumerState<EditRequestHeaders> {
headerRows.add(kNameValueEmptyModel);
isRowEnabledList.add(false);
}
_onFieldChange(selectedId!);
_onFieldChange();
},
colorScheme: Theme.of(context).colorScheme,
),
@ -165,7 +164,7 @@ class EditRequestHeadersState extends ConsumerState<EditRequestHeaders> {
headerRows.removeAt(index);
isRowEnabledList.removeAt(index);
}
_onFieldChange(selectedId!);
_onFieldChange();
},
child: Theme.of(context).brightness == Brightness.dark
? kIconRemoveDark
@ -212,7 +211,7 @@ class EditRequestHeadersState extends ConsumerState<EditRequestHeaders> {
onPressed: () {
headerRows.add(kNameValueEmptyModel);
isRowEnabledList.add(false);
_onFieldChange(selectedId!);
_onFieldChange();
},
icon: const Icon(Icons.add),
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_riverpod/flutter_riverpod.dart';
import 'package:apidash/providers/providers.dart';
import 'package:apidash/widgets/widgets.dart';
import 'request_headers.dart';
import 'request_params.dart';
import 'request_body.dart';
import 'request_pane_graphql.dart';
import 'request_pane_rest.dart';
class EditRequestPane extends ConsumerWidget {
const EditRequestPane({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final selectedId = ref.watch(selectedIdStateProvider);
final codePaneVisible = ref.watch(codePaneVisibleStateProvider);
final tabIndex = ref.watch(
selectedRequestModelProvider.select((value) => value?.requestTabIndex));
final headerLength = ref.watch(selectedRequestModelProvider
.select((value) => value?.httpRequestModel?.headersMap.length)) ??
0;
final paramLength = ref.watch(selectedRequestModelProvider
.select((value) => value?.httpRequestModel?.paramsMap.length)) ??
0;
final hasBody = ref.watch(selectedRequestModelProvider
.select((value) => value?.httpRequestModel?.hasBody)) ??
false;
return RequestPane(
selectedId: selectedId,
codePaneVisible: codePaneVisible,
tabIndex: tabIndex,
onPressedCodeButton: () {
ref.read(codePaneVisibleStateProvider.notifier).state =
!codePaneVisible;
},
onTapTabBar: (index) {
ref
.read(collectionStateNotifierProvider.notifier)
.update(selectedId!, requestTabIndex: index);
},
showIndicators: [
paramLength > 0,
headerLength > 0,
hasBody,
],
children: const [
EditRequestURLParams(),
EditRequestHeaders(),
EditRequestBody(),
],
);
ref.watch(selectedIdStateProvider);
final apiType = ref
.watch(selectedRequestModelProvider.select((value) => value?.apiType));
return switch (apiType) {
APIType.rest => const EditRestRequestPane(),
APIType.graphql => const EditGraphQLRequestPane(),
_ => kSizedBoxEmpty,
};
}
}

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +1,5 @@
import 'dart:math' as math;
import 'package:apidash/consts.dart';
import 'package:apidash_design_system/apidash_design_system.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
@ -93,7 +94,7 @@ class _JsonTextFieldEditorState extends State<JsonTextFieldEditor> {
widget.onChanged?.call(value);
},
decoration: InputDecoration(
hintText: "Enter content (body)",
hintText: kHintJson,
hintStyle: TextStyle(
color: Theme.of(context).colorScheme.outline.withOpacity(
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:flutter/material.dart';
class DropdownButtonAPIType extends StatelessWidget {
const DropdownButtonAPIType({
class APITypePopupMenu extends StatelessWidget {
const APITypePopupMenu({
super.key,
this.apiType,
required this.apiType,
this.onChanged,
});
@ -14,11 +14,13 @@ class DropdownButtonAPIType extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ADDropdownButton<APIType>(
value: apiType,
return ADPopupMenu<APIType>(
tooltip: "Select API Type",
width: 100,
value: apiType?.label,
values: APIType.values.map((e) => (e, e.label)),
onChanged: onChanged,
isDense: true,
isOutlined: true,
);
}
}

View File

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

View File

@ -3,11 +3,13 @@ import 'package:apidash_design_system/apidash_design_system.dart';
import 'package:flutter/material.dart';
import 'package:apidash/utils/utils.dart';
class MethodBox extends StatelessWidget {
const MethodBox({
class SidebarRequestCardTextBox extends StatelessWidget {
const SidebarRequestCardTextBox({
super.key,
required this.apiType,
required this.method,
});
final APIType apiType;
final HTTPVerb method;
@override
@ -15,15 +17,21 @@ class MethodBox extends StatelessWidget {
return SizedBox(
width: 24,
child: Text(
method.abbr,
switch (apiType) {
APIType.rest => method.abbr,
APIType.graphql => apiType.abbr,
},
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 8,
fontWeight: FontWeight.bold,
color: getHTTPMethodColor(
color: switch (apiType) {
APIType.rest => getHTTPMethodColor(
method,
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_http_method.dart';
export 'dropdown_import_format.dart';
export 'dropdown_api_type.dart';
export 'editor_json.dart';
export 'editor.dart';
export 'error_message.dart';
@ -40,12 +39,13 @@ export 'markdown.dart';
export 'menu_item_card.dart';
export 'menu_sidebar_top.dart';
export 'overlay_widget.dart';
export 'popup_menu_api_type.dart';
export 'popup_menu_codegen.dart';
export 'popup_menu_env.dart';
export 'popup_menu_history.dart';
export 'popup_menu_uri.dart';
export 'previewer.dart';
export 'request_widgets.dart';
export 'request_pane.dart';
export 'response_widgets.dart';
export 'splitview_drawer.dart';
export 'splitview_dashboard.dart';

View File

@ -8,6 +8,14 @@ scripts:
run: melos exec -- "flutter analyze"
description: Analyze all packages
clean:
run: melos exec -- "flutter clean"
description: Clean all packages
build-gen:
run: melos exec -- "dart run build_runner build --delete-conflicting-outputs"
description: Run build generator for all packages
test:
run: melos exec --dir-exists=test -- "flutter test --coverage"
description: Run tests for all packages

View File

@ -1,10 +1,12 @@
import 'dart:convert';
enum APIType {
rest("HTTP");
rest("HTTP", "HTTP"),
graphql("GraphQL", "GQL");
const APIType(this.label);
const APIType(this.label, this.abbr);
final String label;
final String abbr;
}
enum HTTPVerb {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -12,6 +12,7 @@ typedef HttpResponse = http.Response;
Future<(HttpResponse?, Duration?, String?)> request(
String requestId,
APIType apiType,
HttpRequestModel requestModel, {
SupportedUriSchemes defaultUriScheme = kDefaultUriScheme,
bool noSSL = false,
@ -28,10 +29,11 @@ Future<(HttpResponse?, Duration?, String?)> request(
if (uriRec.$1 != null) {
Uri requestUrl = uriRec.$1!;
Map<String, String> headers = requestModel.enabledHeadersMap;
HttpResponse response;
HttpResponse? response;
String? body;
try {
Stopwatch stopwatch = Stopwatch()..start();
if (apiType == APIType.rest) {
var isMultiPartRequest =
requestModel.bodyContentType == ContentType.formdata;
@ -41,7 +43,8 @@ Future<(HttpResponse?, Duration?, String?)> request(
var contentLength = utf8.encode(requestBody).length;
if (contentLength > 0) {
body = requestBody;
headers[HttpHeaders.contentLengthHeader] = contentLength.toString();
headers[HttpHeaders.contentLengthHeader] =
contentLength.toString();
if (!requestModel.hasContentTypeHeader) {
headers[HttpHeaders.contentTypeHeader] =
requestModel.bodyContentType.header;
@ -86,7 +89,8 @@ Future<(HttpResponse?, Duration?, String?)> request(
await client.post(requestUrl, headers: headers, body: body);
break;
case HTTPVerb.put:
response = await client.put(requestUrl, headers: headers, body: body);
response =
await client.put(requestUrl, headers: headers, body: body);
break;
case HTTPVerb.patch:
response =
@ -97,6 +101,25 @@ Future<(HttpResponse?, Duration?, String?)> request(
await client.delete(requestUrl, headers: headers, body: body);
break;
}
}
if (apiType == APIType.graphql) {
var requestBody = getGraphQLBody(requestModel);
if (requestBody != null) {
var contentLength = utf8.encode(requestBody).length;
if (contentLength > 0) {
body = requestBody;
headers[HttpHeaders.contentLengthHeader] = contentLength.toString();
if (!requestModel.hasContentTypeHeader) {
headers[HttpHeaders.contentTypeHeader] = ContentType.json.header;
}
}
}
response = await client.post(
requestUrl,
headers: headers,
body: body,
);
}
stopwatch.stop();
return (response, stopwatch.elapsed, null);
} 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:seed/seed.dart';
import '../models/models.dart';
import 'graphql_utils.dart';
Map<String, String>? rowsToMap(
List<NameValueModel>? kvRows, {
@ -88,3 +91,13 @@ List<NameValueModel>? getEnabledRows(
rows.where((element) => isRowEnabledList[rows.indexOf(element)]).toList();
return finalRows == [] ? null : finalRows;
}
String? getRequestBody(APIType type, HttpRequestModel httpRequestModel) {
return switch (type) {
APIType.rest =>
(httpRequestModel.hasJsonData || httpRequestModel.hasTextData)
? httpRequestModel.body
: null,
APIType.graphql => getGraphQLBody(httpRequestModel),
};
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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
- Fix pubspec dependency for freezed_annotation.

View File

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

View File

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

View File

@ -1,7 +1,7 @@
name: apidash
description: API Dash is a beautiful open-source cross-platform API Client built using Flutter which can help you easily create & customize your API requests, visually inspect responses and generate Dart code on the go.
publish_to: "none"
version: 0.4.0+4
version: 0.5.0+5
environment:
sdk: ">=3.0.0 <4.0.0"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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