Merge branch 'main' into elapsed-time-by-request

This commit is contained in:
Ankit Mahato
2024-03-24 05:12:50 +05:30
committed by GitHub
24 changed files with 1915 additions and 276 deletions

View File

@ -154,4 +154,16 @@ ClientException with SocketException: Connection failed (OS Error: Operation not
You can read more [here](https://docs.flutter.dev/platform-integration/macos/building#setting-up-entitlements) You can read more [here](https://docs.flutter.dev/platform-integration/macos/building#setting-up-entitlements)
### Android (Work in Progress)
Add the `multiDexEnabled true` line to the `defaultConfig` section at `android/app/build.gradle file`
```
android {
...
defaultConfig {
...
multiDexEnabled true
}
}
```

View File

@ -9,6 +9,7 @@ import 'php/guzzle.dart';
import 'python/http_client.dart'; import 'python/http_client.dart';
import 'python/requests.dart'; import 'python/requests.dart';
import 'rust/actix.dart'; import 'rust/actix.dart';
import 'rust/curl_rust.dart';
import 'rust/reqwest.dart'; import 'rust/reqwest.dart';
import 'rust/ureq.dart'; import 'rust/ureq.dart';
import 'js/axios.dart'; import 'js/axios.dart';
@ -73,6 +74,8 @@ class Codegen {
return PythonRequestsCodeGen().getCode(rM, boundary: boundary); return PythonRequestsCodeGen().getCode(rM, boundary: boundary);
case CodegenLanguage.rustActix: case CodegenLanguage.rustActix:
return RustActixCodeGen().getCode(rM, boundary: boundary); return RustActixCodeGen().getCode(rM, boundary: boundary);
case CodegenLanguage.rustCurl:
return RustCurlCodeGen().getCode(rM);
case CodegenLanguage.rustReqwest: case CodegenLanguage.rustReqwest:
return RustReqwestCodeGen().getCode(rM); return RustReqwestCodeGen().getCode(rM);
case CodegenLanguage.rustUreq: case CodegenLanguage.rustUreq:

View File

@ -0,0 +1,147 @@
import 'package:jinja/jinja.dart' as jj;
import 'package:apidash/utils/utils.dart'
show getValidRequestUri, requestModelToHARJsonRequest;
import 'package:apidash/models/models.dart' show RequestModel;
import 'package:apidash/consts.dart';
class RustCurlCodeGen {
final String kTemplateStart = """use curl::easy::Easy;
{% if hasJsonBody %}use serde_json::json;
{% endif %}{% if hasHeaders %}use curl::easy::List;
{% endif %}
fn main() {
let mut easy = Easy::new();
let mut data = Vec::new();
""";
String kTemplateUrl = """
easy.url("{{url}}").unwrap();
""";
String kTemplateMethod = """
{% if method == 'get' or method == 'post' or method == 'put' %}
easy.{{ method }}(true).unwrap();
{% elif method == 'delete' %}
easy.custom_request("DELETE").unwrap();
{% elif method == 'patch' %}
easy.custom_request("PATCH").unwrap();
{% elif method == 'head' %}
easy.nobody(true).unwrap();
{% endif %}
""";
String kTemplateRawBody = """
easy.post_fields_copy(r#"{{body}}"#.as_bytes()).unwrap();
""";
String kTemplateJsonBody = """
easy.post_fields_copy(json!({{body}}).to_string().as_bytes()).unwrap();
""";
String kTemplateFormData = """
let mut form = curl::easy::Form::new();
{% for field in fields %}
form.part("{{field.name}}")
{% if field.type == "file" %}.file("{{field.value}}"){% else %}.contents(b"{{field.value}}"){% endif %}
.add().unwrap();
{% endfor %}
easy.httppost(form).unwrap();
""";
String kTemplateHeader = """
{% if headers %}let mut list = List::new();{% for header, value in headers %}
list.append("{{header}}: {{value}}").unwrap();{% endfor %}
easy.http_headers(list).unwrap();
{% endif %}
""";
final String kTemplateEnd = """
{
let mut transfer = easy.transfer();
transfer.write_function(|new_data| {
data.extend_from_slice(new_data);
Ok(new_data.len())
}).unwrap();
transfer.perform().unwrap();
}
let response_body = String::from_utf8_lossy(&data);
println!("Response body: {}", response_body);
println!("Response code: {}", easy.response_code().unwrap());
}""";
String? getCode(RequestModel requestModel) {
try {
String result = "";
var requestBody = requestModel.requestBody;
String url = requestModel.url;
result += jj.Template(kTemplateStart).render({
"hasJsonBody": requestModel.hasJsonData,
"hasHeaders": (requestModel.enabledRequestHeaders != null &&
requestModel.enabledRequestHeaders!.isNotEmpty) ||
(requestModel.hasJsonData || requestModel.hasTextData)
});
var rec = getValidRequestUri(
url,
requestModel.enabledRequestParams,
);
Uri? uri = rec.$1;
var harJson =
requestModelToHARJsonRequest(requestModel, useEnabled: true);
var templateUrl = jj.Template(kTemplateUrl);
result += templateUrl.render({"url": harJson["url"]});
var methodTemplate = jj.Template(kTemplateMethod);
result += methodTemplate.render({"method": requestModel.method.name});
if (uri != null) {
if (requestModel.hasTextData) {
var templateBody = jj.Template(kTemplateRawBody);
result += templateBody.render({"body": requestBody});
} else if (requestModel.hasJsonData) {
var templateBody = jj.Template(kTemplateJsonBody);
result += templateBody.render({"body": requestBody});
} else if (requestModel.hasFormData) {
var templateFormData = jj.Template(kTemplateFormData);
result += templateFormData.render({
"fields": requestModel.formDataMapList,
});
}
var headersList = requestModel.enabledRequestHeaders;
if (headersList != null || requestModel.hasBody) {
var headers = requestModel.enabledHeadersMap;
if (requestModel.hasJsonData || requestModel.hasTextData) {
headers.putIfAbsent(kHeaderContentType,
() => requestModel.requestBodyContentType.header);
}
if (headers.isNotEmpty) {
var templateHeader = jj.Template(kTemplateHeader);
result += templateHeader.render({
"headers": headers,
});
}
}
result += kTemplateEnd;
}
return result;
} catch (e) {
return null;
}
}
}

View File

@ -56,6 +56,7 @@ const kForegroundOpacity = 0.05;
const kOverlayBackgroundOpacity = 0.5; const kOverlayBackgroundOpacity = 0.5;
const kTextStyleButton = TextStyle(fontWeight: FontWeight.bold); const kTextStyleButton = TextStyle(fontWeight: FontWeight.bold);
const kTextStyleTab = TextStyle(fontSize: 14);
const kTextStyleButtonSmall = TextStyle(fontSize: 12); const kTextStyleButtonSmall = TextStyle(fontSize: 12);
const kFormDataButtonLabelTextStyle = TextStyle( const kFormDataButtonLabelTextStyle = TextStyle(
fontSize: 12, fontSize: 12,
@ -71,9 +72,13 @@ const kP5 = EdgeInsets.all(5);
const kP8 = EdgeInsets.all(8); const kP8 = EdgeInsets.all(8);
const kPs8 = EdgeInsets.only(left: 8); const kPs8 = EdgeInsets.only(left: 8);
const kPs2 = EdgeInsets.only(left: 2); const kPs2 = EdgeInsets.only(left: 2);
const kPe8 = EdgeInsets.only(right: 8.0);
const kPh20v5 = EdgeInsets.symmetric(horizontal: 20, vertical: 5); const kPh20v5 = EdgeInsets.symmetric(horizontal: 20, vertical: 5);
const kPh20v10 = EdgeInsets.symmetric(horizontal: 20, vertical: 10); const kPh20v10 = EdgeInsets.symmetric(horizontal: 20, vertical: 10);
const kP10 = EdgeInsets.all(10); const kP10 = EdgeInsets.all(10);
const kPv8 = EdgeInsets.symmetric(vertical: 8);
const kPv2 = EdgeInsets.symmetric(vertical: 2);
const kPh2 = EdgeInsets.symmetric(horizontal: 2);
const kPt24o8 = EdgeInsets.only(top: 24, left: 8.0, right: 8.0, bottom: 8.0); const kPt24o8 = EdgeInsets.only(top: 24, left: 8.0, right: 8.0, bottom: 8.0);
const kPt5o10 = const kPt5o10 =
EdgeInsets.only(left: 10.0, right: 10.0, top: 5.0, bottom: 10.0); EdgeInsets.only(left: 10.0, right: 10.0, top: 5.0, bottom: 10.0);
@ -102,8 +107,9 @@ const kP8CollectionPane = EdgeInsets.only(
const kPb10 = EdgeInsets.only( const kPb10 = EdgeInsets.only(
bottom: 10, bottom: 10,
); );
const kPr8CollectionPane = EdgeInsets.only(right: 8.0); const kPb15 = EdgeInsets.only(
const kpsV5 = EdgeInsets.symmetric(vertical: 2); bottom: 15,
);
const kHSpacer4 = SizedBox(width: 4); const kHSpacer4 = SizedBox(width: 4);
const kHSpacer5 = SizedBox(width: 5); const kHSpacer5 = SizedBox(width: 5);
const kHSpacer10 = SizedBox(width: 10); const kHSpacer10 = SizedBox(width: 10);
@ -112,9 +118,10 @@ 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);
const kVSpacer20 = SizedBox(height: 20); const kVSpacer20 = SizedBox(height: 20);
const kVSpacer40 = SizedBox(height: 40);
const kTabAnimationDuration = Duration(milliseconds: 200); const kTabAnimationDuration = Duration(milliseconds: 200);
const kTabHeight = 45.0; const kTabHeight = 32.0;
const kHeaderHeight = 32.0; const kHeaderHeight = 32.0;
const kSegmentHeight = 24.0; const kSegmentHeight = 24.0;
const kTextButtonMinWidth = 44.0; const kTextButtonMinWidth = 44.0;
@ -284,6 +291,7 @@ enum CodegenLanguage {
pythonHttpClient("Python (http.client)", "python", "py"), pythonHttpClient("Python (http.client)", "python", "py"),
rustActix("Rust (Actix Client)", "rust", "rs"), rustActix("Rust (Actix Client)", "rust", "rs"),
rustReqwest("Rust (reqwest)", "rust", "rs"), rustReqwest("Rust (reqwest)", "rust", "rs"),
rustCurl("Rust (curl-rust)", "rust", "rs"),
rustUreq("Rust (ureq)", "rust", "rs"), rustUreq("Rust (ureq)", "rust", "rs"),
javaOkHttp("Java (okhttp3)", "java", 'java'), javaOkHttp("Java (okhttp3)", "java", 'java'),
javaAsyncHttpClient("Java (asynchttpclient)", "java", "java"), javaAsyncHttpClient("Java (asynchttpclient)", "java", "java"),
@ -532,3 +540,36 @@ const kLabelSave = "Save";
const kLabelDownload = "Download"; const kLabelDownload = "Download";
const kLabelSaving = "Saving"; const kLabelSaving = "Saving";
const kLabelSaved = "Saved"; const kLabelSaved = "Saved";
// Request Pane
const kLabelRequest = "Request";
const kLabelHideCode = "Hide Code";
const kLabelViewCode = "View Code";
const kLabelURLParams = "URL Params";
const kLabelHeaders = "Headers";
const kLabelBody = "Body";
const kNameCheckbox = "Checkbox";
const kNameURLParam = "URL Parameter";
const kNameHeader = "Header Name";
const kNameValue = "Value";
const kNameField = "Field";
const kHintAddURLParam = "Add URL Parameter";
const kHintAddValue = "Add Value";
const kHintAddName = "Add Name";
const kHintAddFieldName = "Add Field Name";
const kLabelAddParam = "Add Param";
const kLabelAddHeader = "Add Header";
const kLabelSelectFile = "Select File";
const kLabelAddFormField = "Add Form Field";
// Response Pane
const kLabelNotSent = "Not Sent";
const kLabelResponse = "Response";
const kLabelResponseBody = "Response Body";
const kTooltipClearResponse = "Clear Response";
const kHeaderRow = ["Header Name", "Header Value"];
const kLabelRequestHeaders = "Request Headers";
const kLabelResponseHeaders = "Response Headers";
const kLabelItems = "items";
const kMsgError = "Error: Response data does not exist.";
const kMsgNullBody = "Response body is missing (null).";
const kMsgNoContent = "No content";
const kMsgUnknowContentType = "Unknown Response Content-Type";

View File

@ -97,6 +97,7 @@ class RequestModel {
RequestModel duplicate({ RequestModel duplicate({
required String id, required String id,
String? name, String? name,
int? requestTabIndex,
}) { }) {
return RequestModel( return RequestModel(
id: id, id: id,
@ -104,6 +105,7 @@ class RequestModel {
url: url, url: url,
name: name ?? "${this.name} (copy)", name: name ?? "${this.name} (copy)",
description: description, description: description,
requestTabIndex: requestTabIndex ?? 0,
requestHeaders: requestHeaders != null ? [...requestHeaders!] : null, requestHeaders: requestHeaders != null ? [...requestHeaders!] : null,
requestParams: requestParams != null ? [...requestParams!] : null, requestParams: requestParams != null ? [...requestParams!] : null,
isHeaderEnabledList: isHeaderEnabledList:

View File

@ -102,6 +102,7 @@ class CollectionStateNotifier
final newModel = currentModel.duplicate( final newModel = currentModel.duplicate(
id: id, id: id,
name: currentModel.name, name: currentModel.name,
requestTabIndex: currentModel.requestTabIndex,
); );
var map = {...state!}; var map = {...state!};
map[id] = newModel; map[id] = newModel;

View File

@ -23,3 +23,5 @@ final nameTextFieldFocusNodeProvider =
}); });
return focusNode; return focusNode;
}); });
final searchQueryProvider = StateProvider<String>((ref) => '');

View File

@ -26,7 +26,7 @@ class CollectionPane extends ConsumerWidget {
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ children: [
Padding( Padding(
padding: kPr8CollectionPane, padding: kPe8,
child: Wrap( child: Wrap(
alignment: WrapAlignment.spaceBetween, alignment: WrapAlignment.spaceBetween,
children: [ children: [
@ -69,7 +69,39 @@ class CollectionPane extends ConsumerWidget {
], ],
), ),
), ),
kVSpacer8, kVSpacer10,
Container(
height: 30,
margin: const EdgeInsets.only(right: 8),
decoration: BoxDecoration(
borderRadius: kBorderRadius8,
border: Border.all(
color: Theme.of(context).colorScheme.surfaceVariant,
),
),
child: Row(
children: [
kHSpacer5,
Icon(
Icons.filter_alt,
size: 18,
color: Theme.of(context).colorScheme.secondary,
),
kHSpacer5,
Expanded(
child: RawTextField(
style: Theme.of(context).textTheme.bodyMedium,
hintText: "Filter by name or URL",
onChanged: (value) {
ref.read(searchQueryProvider.notifier).state =
value.toLowerCase();
},
),
),
],
),
),
kVSpacer10,
const Expanded( const Expanded(
child: RequestList(), child: RequestList(),
), ),
@ -109,13 +141,15 @@ class _RequestListState extends ConsumerState<RequestList> {
final requestItems = ref.watch(collectionStateNotifierProvider)!; final requestItems = ref.watch(collectionStateNotifierProvider)!;
final alwaysShowCollectionPaneScrollbar = ref.watch(settingsProvider final alwaysShowCollectionPaneScrollbar = ref.watch(settingsProvider
.select((value) => value.alwaysShowCollectionPaneScrollbar)); .select((value) => value.alwaysShowCollectionPaneScrollbar));
final filterQuery = ref.watch(searchQueryProvider).trim();
return Scrollbar( return Scrollbar(
controller: controller, controller: controller,
thumbVisibility: alwaysShowCollectionPaneScrollbar ? true : null, thumbVisibility: alwaysShowCollectionPaneScrollbar ? true : null,
radius: const Radius.circular(12), radius: const Radius.circular(12),
child: ReorderableListView.builder( child: filterQuery.isEmpty
padding: kPr8CollectionPane, ? ReorderableListView.builder(
padding: kPe8,
scrollController: controller, scrollController: controller,
buildDefaultDragHandles: false, buildDefaultDragHandles: false,
itemCount: requestSequence.length, itemCount: requestSequence.length,
@ -143,6 +177,24 @@ class _RequestListState extends ConsumerState<RequestList> {
), ),
); );
}, },
)
: ListView(
padding: kPe8,
controller: controller,
children: requestSequence.map((id) {
var item = requestItems[id]!;
if (item.url.toLowerCase().contains(filterQuery) ||
item.name.toLowerCase().contains(filterQuery)) {
return Padding(
padding: kP1,
child: RequestItem(
id: id,
requestModel: item,
),
);
}
return const SizedBox();
}).toList(),
), ),
); );
} }

View File

@ -17,48 +17,72 @@ class FormDataWidget extends ConsumerStatefulWidget {
class _FormDataBodyState extends ConsumerState<FormDataWidget> { class _FormDataBodyState extends ConsumerState<FormDataWidget> {
late int seed; late int seed;
final random = Random.secure(); final random = Random.secure();
late List<FormDataModel> rows; late List<FormDataModel> formRows;
bool isAddingRow = false;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
seed = random.nextInt(kRandMax); seed = random.nextInt(kRandMax);
} }
void _onFieldChange(String selectedId) {
ref.read(collectionStateNotifierProvider.notifier).update(
selectedId,
requestFormDataList: formRows.sublist(0, formRows.length - 1),
);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final selectedId = ref.watch(selectedIdStateProvider); final selectedId = ref.watch(selectedIdStateProvider);
var formRows = ref.read(selectedRequestModelProvider)?.requestFormDataList; ref.watch(selectedRequestModelProvider
rows = .select((value) => value?.requestFormDataList?.length));
formRows == null || formRows.isEmpty ? [kFormDataEmptyModel] : formRows; var rF = ref.read(selectedRequestModelProvider)?.requestFormDataList;
bool isFormDataEmpty = rF == null || rF.isEmpty;
List<FormDataModel> rows = (isFormDataEmpty)
? [
kFormDataEmptyModel,
]
: rF;
formRows = isFormDataEmpty ? rows : rows + [kFormDataEmptyModel];
isAddingRow = false;
DaviModel<FormDataModel> daviModelRows = DaviModel<FormDataModel>( DaviModel<FormDataModel> daviModelRows = DaviModel<FormDataModel>(
rows: rows, rows: formRows,
columns: [ columns: [
DaviColumn( DaviColumn(
cellPadding: kpsV5, cellPadding: kPv2,
name: 'Key', name: kNameField,
grow: 4, grow: 4,
cellBuilder: (_, row) { cellBuilder: (_, row) {
int idx = row.index; int idx = row.index;
bool isLast = idx + 1 == formRows.length;
return Theme( return Theme(
data: Theme.of(context), data: Theme.of(context),
child: FormDataField( child: FormDataField(
keyId: "$selectedId-$idx-form-v-$seed", keyId: "$selectedId-$idx-form-v-$seed",
initialValue: rows[idx].name, initialValue: formRows[idx].name,
hintText: " Add Key", hintText: kHintAddFieldName,
onChanged: (value) { onChanged: (value) {
rows[idx] = rows[idx].copyWith( formRows[idx] = formRows[idx].copyWith(name: value);
name: value, if (isLast && !isAddingRow) {
); isAddingRow = true;
formRows.add(kFormDataEmptyModel);
}
_onFieldChange(selectedId!); _onFieldChange(selectedId!);
}, },
colorScheme: Theme.of(context).colorScheme, colorScheme: Theme.of(context).colorScheme,
formDataType: rows[idx].type, formDataType: formRows[idx].type,
onFormDataTypeChanged: (value) { onFormDataTypeChanged: (value) {
rows[idx] = rows[idx].copyWith( bool hasChanged = formRows[idx].type != value;
formRows[idx] = formRows[idx].copyWith(
type: value ?? FormDataType.text, type: value ?? FormDataType.text,
); );
rows[idx] = rows[idx].copyWith(value: ""); formRows[idx] = formRows[idx].copyWith(value: "");
if (isLast && hasChanged) {
formRows.add(kFormDataEmptyModel);
}
setState(() {}); setState(() {});
_onFieldChange(selectedId!); _onFieldChange(selectedId!);
}, },
@ -69,7 +93,7 @@ class _FormDataBodyState extends ConsumerState<FormDataWidget> {
), ),
DaviColumn( DaviColumn(
width: 40, width: 40,
cellPadding: kpsV5, cellPadding: kPv2,
cellAlignment: Alignment.center, cellAlignment: Alignment.center,
cellBuilder: (_, row) { cellBuilder: (_, row) {
return Text( return Text(
@ -79,12 +103,13 @@ class _FormDataBodyState extends ConsumerState<FormDataWidget> {
}, },
), ),
DaviColumn( DaviColumn(
name: 'Value', name: kNameValue,
grow: 4, grow: 4,
cellPadding: kpsV5, cellPadding: kPv2,
cellBuilder: (_, row) { cellBuilder: (_, row) {
int idx = row.index; int idx = row.index;
return rows[idx].type == FormDataType.file bool isLast = idx + 1 == formRows.length;
return formRows[idx].type == FormDataType.file
? Align( ? Align(
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: Row( child: Row(
@ -109,7 +134,7 @@ class _FormDataBodyState extends ConsumerState<FormDataWidget> {
if (pickedResult != null && if (pickedResult != null &&
pickedResult.files.isNotEmpty && pickedResult.files.isNotEmpty &&
pickedResult.files.first.path != null) { pickedResult.files.first.path != null) {
rows[idx] = rows[idx].copyWith( formRows[idx] = formRows[idx].copyWith(
value: pickedResult.files.first.path!, value: pickedResult.files.first.path!,
); );
setState(() {}); setState(() {});
@ -117,10 +142,10 @@ class _FormDataBodyState extends ConsumerState<FormDataWidget> {
} }
}, },
label: Text( label: Text(
(rows[idx].type == FormDataType.file && (formRows[idx].type == FormDataType.file &&
rows[idx].value.isNotEmpty) formRows[idx].value.isNotEmpty)
? rows[idx].value.toString() ? formRows[idx].value.toString()
: "Select File", : kLabelSelectFile,
textAlign: TextAlign.center, textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: kFormDataButtonLabelTextStyle, style: kFormDataButtonLabelTextStyle,
@ -133,10 +158,14 @@ class _FormDataBodyState extends ConsumerState<FormDataWidget> {
) )
: CellField( : CellField(
keyId: "$selectedId-$idx-form-v-$seed", keyId: "$selectedId-$idx-form-v-$seed",
initialValue: rows[idx].value, initialValue: formRows[idx].value,
hintText: " Add Value", hintText: kHintAddValue,
onChanged: (value) { onChanged: (value) {
rows[idx] = rows[idx].copyWith(value: value); formRows[idx] = formRows[idx].copyWith(value: value);
if (isLast && !isAddingRow) {
isAddingRow = true;
formRows.add(kFormDataEmptyModel);
}
_onFieldChange(selectedId!); _onFieldChange(selectedId!);
}, },
colorScheme: Theme.of(context).colorScheme, colorScheme: Theme.of(context).colorScheme,
@ -148,22 +177,24 @@ class _FormDataBodyState extends ConsumerState<FormDataWidget> {
pinStatus: PinStatus.none, pinStatus: PinStatus.none,
width: 30, width: 30,
cellBuilder: (_, row) { cellBuilder: (_, row) {
bool isLast = row.index + 1 == formRows.length;
return InkWell( return InkWell(
onTap: isLast
? null
: () {
seed = random.nextInt(kRandMax);
if (formRows.length == 2) {
setState(() {
formRows = [kFormDataEmptyModel];
});
} else {
formRows.removeAt(row.index);
}
_onFieldChange(selectedId!);
},
child: Theme.of(context).brightness == Brightness.dark child: Theme.of(context).brightness == Brightness.dark
? kIconRemoveDark ? kIconRemoveDark
: kIconRemoveLight, : kIconRemoveLight,
onTap: () {
seed = random.nextInt(kRandMax);
if (rows.length == 1) {
setState(() {
rows = [kFormDataEmptyModel];
});
} else {
rows.removeAt(row.index);
}
_onFieldChange(selectedId!);
setState(() {});
},
); );
}, },
), ),
@ -185,23 +216,22 @@ class _FormDataBodyState extends ConsumerState<FormDataWidget> {
child: Davi<FormDataModel>(daviModelRows), child: Davi<FormDataModel>(daviModelRows),
), ),
), ),
kVSpacer20,
], ],
), ),
), ),
Align( Align(
alignment: Alignment.bottomCenter, alignment: Alignment.bottomCenter,
child: Padding( child: Padding(
padding: const EdgeInsets.only(bottom: 30), padding: const EdgeInsets.only(bottom: 5),
child: ElevatedButton.icon( child: ElevatedButton.icon(
onPressed: () { onPressed: () {
setState(() { formRows.add(kFormDataEmptyModel);
rows.add(kFormDataEmptyModel);
});
_onFieldChange(selectedId!); _onFieldChange(selectedId!);
}, },
icon: const Icon(Icons.add), icon: const Icon(Icons.add),
label: const Text( label: const Text(
"Add Form Data", kLabelAddFormField,
style: kTextStyleButton, style: kTextStyleButton,
), ),
), ),
@ -210,11 +240,4 @@ class _FormDataBodyState extends ConsumerState<FormDataWidget> {
], ],
); );
} }
void _onFieldChange(String selectedId) {
ref.read(collectionStateNotifierProvider.notifier).update(
selectedId,
requestFormDataList: rows,
);
}
} }

View File

@ -15,10 +15,11 @@ class EditRequestHeaders extends ConsumerStatefulWidget {
} }
class EditRequestHeadersState extends ConsumerState<EditRequestHeaders> { class EditRequestHeadersState extends ConsumerState<EditRequestHeaders> {
final random = Random.secure();
late List<NameValueModel> rows;
late List<bool> isRowEnabledList;
late int seed; late int seed;
final random = Random.secure();
late List<NameValueModel> headerRows;
late List<bool> isRowEnabledList;
bool isAddingRow = false;
@override @override
void initState() { void initState() {
@ -29,8 +30,9 @@ class EditRequestHeadersState extends ConsumerState<EditRequestHeaders> {
void _onFieldChange(String selectedId) { void _onFieldChange(String selectedId) {
ref.read(collectionStateNotifierProvider.notifier).update( ref.read(collectionStateNotifierProvider.notifier).update(
selectedId, selectedId,
requestHeaders: rows, requestHeaders: headerRows.sublist(0, headerRows.length - 1),
isHeaderEnabledList: isRowEnabledList, isHeaderEnabledList:
isRowEnabledList.sublist(0, headerRows.length - 1),
); );
} }
@ -40,27 +42,34 @@ class EditRequestHeadersState extends ConsumerState<EditRequestHeaders> {
ref.watch(selectedRequestModelProvider ref.watch(selectedRequestModelProvider
.select((value) => value?.requestHeaders?.length)); .select((value) => value?.requestHeaders?.length));
var rH = ref.read(selectedRequestModelProvider)?.requestHeaders; var rH = ref.read(selectedRequestModelProvider)?.requestHeaders;
rows = (rH == null || rH.isEmpty) bool isHeadersEmpty = rH == null || rH.isEmpty;
List<NameValueModel> rows = (isHeadersEmpty)
? [ ? [
kNameValueEmptyModel, kNameValueEmptyModel,
] ]
: rH; : rH;
headerRows = isHeadersEmpty ? rows : rows + [kNameValueEmptyModel];
isRowEnabledList = isRowEnabledList =
ref.read(selectedRequestModelProvider)?.isHeaderEnabledList ?? ref.read(selectedRequestModelProvider)?.isHeaderEnabledList ??
List.filled(rows.length, true, growable: true); List.filled(rH?.length ?? 0, true, growable: true);
isRowEnabledList.add(false);
isAddingRow = false;
DaviModel<NameValueModel> model = DaviModel<NameValueModel>( DaviModel<NameValueModel> model = DaviModel<NameValueModel>(
rows: rows, rows: headerRows,
columns: [ columns: [
DaviColumn( DaviColumn(
name: 'Checkbox', name: kNameCheckbox,
width: 30, width: 30,
cellBuilder: (_, row) { cellBuilder: (_, row) {
int idx = row.index; int idx = row.index;
bool isLast = idx + 1 == headerRows.length;
return CheckBox( return CheckBox(
keyId: "$selectedId-$idx-headers-c-$seed", keyId: "$selectedId-$idx-headers-c-$seed",
value: isRowEnabledList[idx], value: isRowEnabledList[idx],
onChanged: (value) { onChanged: isLast
? null
: (value) {
setState(() { setState(() {
isRowEnabledList[idx] = value!; isRowEnabledList[idx] = value!;
}); });
@ -71,17 +80,24 @@ class EditRequestHeadersState extends ConsumerState<EditRequestHeaders> {
}, },
), ),
DaviColumn( DaviColumn(
name: 'Header Name', name: kNameHeader,
width: 70, width: 70,
grow: 1, grow: 1,
cellBuilder: (_, row) { cellBuilder: (_, row) {
int idx = row.index; int idx = row.index;
bool isLast = idx + 1 == headerRows.length;
return HeaderField( return HeaderField(
keyId: "$selectedId-$idx-headers-k-$seed", keyId: "$selectedId-$idx-headers-k-$seed",
initialValue: rows[idx].name, initialValue: headerRows[idx].name,
hintText: "Add Header Name", hintText: kHintAddName,
onChanged: (value) { onChanged: (value) {
rows[idx] = rows[idx].copyWith(name: value); headerRows[idx] = headerRows[idx].copyWith(name: value);
if (isLast && !isAddingRow) {
isAddingRow = true;
isRowEnabledList[idx] = true;
headerRows.add(kNameValueEmptyModel);
isRowEnabledList.add(false);
}
_onFieldChange(selectedId!); _onFieldChange(selectedId!);
}, },
colorScheme: Theme.of(context).colorScheme, colorScheme: Theme.of(context).colorScheme,
@ -99,16 +115,23 @@ class EditRequestHeadersState extends ConsumerState<EditRequestHeaders> {
}, },
), ),
DaviColumn( DaviColumn(
name: 'Header Value', name: kNameValue,
grow: 1, grow: 1,
cellBuilder: (_, row) { cellBuilder: (_, row) {
int idx = row.index; int idx = row.index;
bool isLast = idx + 1 == headerRows.length;
return CellField( return CellField(
keyId: "$selectedId-$idx-headers-v-$seed", keyId: "$selectedId-$idx-headers-v-$seed",
initialValue: rows[idx].value, initialValue: headerRows[idx].value,
hintText: " Add Header Value", hintText: kHintAddValue,
onChanged: (value) { onChanged: (value) {
rows[idx] = rows[idx].copyWith(value: value); headerRows[idx] = headerRows[idx].copyWith(value: value);
if (isLast && !isAddingRow) {
isAddingRow = true;
isRowEnabledList[idx] = true;
headerRows.add(kNameValueEmptyModel);
isRowEnabledList.add(false);
}
_onFieldChange(selectedId!); _onFieldChange(selectedId!);
}, },
colorScheme: Theme.of(context).colorScheme, colorScheme: Theme.of(context).colorScheme,
@ -120,25 +143,28 @@ class EditRequestHeadersState extends ConsumerState<EditRequestHeaders> {
pinStatus: PinStatus.none, pinStatus: PinStatus.none,
width: 30, width: 30,
cellBuilder: (_, row) { cellBuilder: (_, row) {
bool isLast = row.index + 1 == headerRows.length;
return InkWell( return InkWell(
child: Theme.of(context).brightness == Brightness.dark onTap: isLast
? kIconRemoveDark ? null
: kIconRemoveLight, : () {
onTap: () {
seed = random.nextInt(kRandMax); seed = random.nextInt(kRandMax);
if (rows.length == 1) { if (headerRows.length == 2) {
setState(() { setState(() {
rows = [ headerRows = [
kNameValueEmptyModel, kNameValueEmptyModel,
]; ];
isRowEnabledList = [true]; isRowEnabledList = [false];
}); });
} else { } else {
rows.removeAt(row.index); headerRows.removeAt(row.index);
isRowEnabledList.removeAt(row.index); isRowEnabledList.removeAt(row.index);
} }
_onFieldChange(selectedId!); _onFieldChange(selectedId!);
}, },
child: Theme.of(context).brightness == Brightness.dark
? kIconRemoveDark
: kIconRemoveLight,
); );
}, },
), ),
@ -160,22 +186,23 @@ class EditRequestHeadersState extends ConsumerState<EditRequestHeaders> {
child: Davi<NameValueModel>(model), child: Davi<NameValueModel>(model),
), ),
), ),
kVSpacer40,
], ],
), ),
), ),
Align( Align(
alignment: Alignment.bottomCenter, alignment: Alignment.bottomCenter,
child: Padding( child: Padding(
padding: const EdgeInsets.only(bottom: 30), padding: kPb15,
child: ElevatedButton.icon( child: ElevatedButton.icon(
onPressed: () { onPressed: () {
rows.add(kNameValueEmptyModel); headerRows.add(kNameValueEmptyModel);
isRowEnabledList.add(true); isRowEnabledList.add(false);
_onFieldChange(selectedId!); _onFieldChange(selectedId!);
}, },
icon: const Icon(Icons.add), icon: const Icon(Icons.add),
label: const Text( label: const Text(
"Add Header", kLabelAddHeader,
style: kTextStyleButton, style: kTextStyleButton,
), ),
), ),

View File

@ -16,10 +16,11 @@ class EditRequestURLParams extends ConsumerStatefulWidget {
} }
class EditRequestURLParamsState extends ConsumerState<EditRequestURLParams> { class EditRequestURLParamsState extends ConsumerState<EditRequestURLParams> {
final random = Random.secure();
late List<NameValueModel> rows;
late List<bool> isRowEnabledList;
late int seed; late int seed;
final random = Random.secure();
late List<NameValueModel> paramRows;
late List<bool> isRowEnabledList;
bool isAddingRow = false;
@override @override
void initState() { void initState() {
@ -30,8 +31,8 @@ class EditRequestURLParamsState extends ConsumerState<EditRequestURLParams> {
void _onFieldChange(String selectedId) { void _onFieldChange(String selectedId) {
ref.read(collectionStateNotifierProvider.notifier).update( ref.read(collectionStateNotifierProvider.notifier).update(
selectedId, selectedId,
requestParams: rows, requestParams: paramRows.sublist(0, paramRows.length - 1),
isParamEnabledList: isRowEnabledList, isParamEnabledList: isRowEnabledList.sublist(0, paramRows.length - 1),
); );
} }
@ -41,27 +42,34 @@ class EditRequestURLParamsState extends ConsumerState<EditRequestURLParams> {
ref.watch(selectedRequestModelProvider ref.watch(selectedRequestModelProvider
.select((value) => value?.requestParams?.length)); .select((value) => value?.requestParams?.length));
var rP = ref.read(selectedRequestModelProvider)?.requestParams; var rP = ref.read(selectedRequestModelProvider)?.requestParams;
rows = (rP == null || rP.isEmpty) bool isParamsEmpty = rP == null || rP.isEmpty;
List<NameValueModel> rows = (isParamsEmpty)
? [ ? [
kNameValueEmptyModel, kNameValueEmptyModel,
] ]
: rP; : rP;
paramRows = isParamsEmpty ? rows : rows + [kNameValueEmptyModel];
isRowEnabledList = isRowEnabledList =
ref.read(selectedRequestModelProvider)?.isParamEnabledList ?? ref.read(selectedRequestModelProvider)?.isParamEnabledList ??
List.filled(rows.length, true, growable: true); List.filled(rP?.length ?? 0, true, growable: true);
isRowEnabledList.add(false);
isAddingRow = false;
DaviModel<NameValueModel> model = DaviModel<NameValueModel>( DaviModel<NameValueModel> model = DaviModel<NameValueModel>(
rows: rows, rows: paramRows,
columns: [ columns: [
DaviColumn( DaviColumn(
name: 'Checkbox', name: kNameCheckbox,
width: 30, width: 30,
cellBuilder: (_, row) { cellBuilder: (_, row) {
int idx = row.index; int idx = row.index;
bool isLast = idx + 1 == paramRows.length;
return CheckBox( return CheckBox(
keyId: "$selectedId-$idx-params-c-$seed", keyId: "$selectedId-$idx-params-c-$seed",
value: isRowEnabledList[idx], value: isRowEnabledList[idx],
onChanged: (value) { onChanged: isLast
? null
: (value) {
setState(() { setState(() {
isRowEnabledList[idx] = value!; isRowEnabledList[idx] = value!;
}); });
@ -72,17 +80,24 @@ class EditRequestURLParamsState extends ConsumerState<EditRequestURLParams> {
}, },
), ),
DaviColumn( DaviColumn(
name: 'URL Parameter', name: kNameURLParam,
width: 70, width: 70,
grow: 1, grow: 1,
cellBuilder: (_, row) { cellBuilder: (_, row) {
int idx = row.index; int idx = row.index;
bool isLast = idx + 1 == paramRows.length;
return CellField( return CellField(
keyId: "$selectedId-$idx-params-k-$seed", keyId: "$selectedId-$idx-params-k-$seed",
initialValue: rows[idx].name, initialValue: paramRows[idx].name,
hintText: "Add URL Parameter", hintText: kHintAddURLParam,
onChanged: (value) { onChanged: (value) {
rows[idx] = rows[idx].copyWith(name: value); paramRows[idx] = paramRows[idx].copyWith(name: value);
if (isLast && !isAddingRow) {
isAddingRow = true;
isRowEnabledList[idx] = true;
paramRows.add(kNameValueEmptyModel);
isRowEnabledList.add(false);
}
_onFieldChange(selectedId!); _onFieldChange(selectedId!);
}, },
colorScheme: Theme.of(context).colorScheme, colorScheme: Theme.of(context).colorScheme,
@ -100,16 +115,23 @@ class EditRequestURLParamsState extends ConsumerState<EditRequestURLParams> {
}, },
), ),
DaviColumn( DaviColumn(
name: 'Value', name: kNameValue,
grow: 1, grow: 1,
cellBuilder: (_, row) { cellBuilder: (_, row) {
int idx = row.index; int idx = row.index;
bool isLast = idx + 1 == paramRows.length;
return CellField( return CellField(
keyId: "$selectedId-$idx-params-v-$seed", keyId: "$selectedId-$idx-params-v-$seed",
initialValue: rows[idx].value, initialValue: paramRows[idx].value,
hintText: "Add Value", hintText: kHintAddValue,
onChanged: (value) { onChanged: (value) {
rows[idx] = rows[idx].copyWith(value: value); paramRows[idx] = paramRows[idx].copyWith(value: value);
if (isLast && !isAddingRow) {
isAddingRow = true;
isRowEnabledList[idx] = true;
paramRows.add(kNameValueEmptyModel);
isRowEnabledList.add(false);
}
_onFieldChange(selectedId!); _onFieldChange(selectedId!);
}, },
colorScheme: Theme.of(context).colorScheme, colorScheme: Theme.of(context).colorScheme,
@ -121,25 +143,28 @@ class EditRequestURLParamsState extends ConsumerState<EditRequestURLParams> {
pinStatus: PinStatus.none, pinStatus: PinStatus.none,
width: 30, width: 30,
cellBuilder: (_, row) { cellBuilder: (_, row) {
bool isLast = row.index + 1 == paramRows.length;
return InkWell( return InkWell(
child: Theme.of(context).brightness == Brightness.dark onTap: isLast
? kIconRemoveDark ? null
: kIconRemoveLight, : () {
onTap: () {
seed = random.nextInt(kRandMax); seed = random.nextInt(kRandMax);
if (rows.length == 1) { if (paramRows.length == 2) {
setState(() { setState(() {
rows = [ paramRows = [
kNameValueEmptyModel, kNameValueEmptyModel,
]; ];
isRowEnabledList = [true]; isRowEnabledList = [false];
}); });
} else { } else {
rows.removeAt(row.index); paramRows.removeAt(row.index);
isRowEnabledList.removeAt(row.index); isRowEnabledList.removeAt(row.index);
} }
_onFieldChange(selectedId!); _onFieldChange(selectedId!);
}, },
child: Theme.of(context).brightness == Brightness.dark
? kIconRemoveDark
: kIconRemoveLight,
); );
}, },
), ),
@ -161,22 +186,23 @@ class EditRequestURLParamsState extends ConsumerState<EditRequestURLParams> {
child: Davi<NameValueModel>(model), child: Davi<NameValueModel>(model),
), ),
), ),
kVSpacer40,
], ],
), ),
), ),
Align( Align(
alignment: Alignment.bottomCenter, alignment: Alignment.bottomCenter,
child: Padding( child: Padding(
padding: const EdgeInsets.only(bottom: 30), padding: kPb15,
child: ElevatedButton.icon( child: ElevatedButton.icon(
onPressed: () { onPressed: () {
rows.add(kNameValueEmptyModel); paramRows.add(kNameValueEmptyModel);
isRowEnabledList.add(true); isRowEnabledList.add(false);
_onFieldChange(selectedId!); _onFieldChange(selectedId!);
}, },
icon: const Icon(Icons.add), icon: const Icon(Icons.add),
label: const Text( label: const Text(
"Add Param", kLabelAddParam,
style: kTextStyleButton, style: kTextStyleButton,
), ),
), ),

View File

@ -39,7 +39,6 @@ class ResponseDetails extends ConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
var sm = ScaffoldMessenger.of(context);
final responseStatus = ref.watch( final responseStatus = ref.watch(
selectedRequestModelProvider.select((value) => value?.responseStatus)); selectedRequestModelProvider.select((value) => value?.responseStatus));
final message = ref final message = ref
@ -57,8 +56,6 @@ class ResponseDetails extends ConsumerWidget {
ref ref
.read(collectionStateNotifierProvider.notifier) .read(collectionStateNotifierProvider.notifier)
.clearResponse(selectedRequest?.id); .clearResponse(selectedRequest?.id);
sm.hideCurrentSnackBar();
sm.showSnackBar(getSnackBar('Response cleared'));
}, },
), ),
const Expanded( const Expanded(

View File

@ -246,15 +246,12 @@ class ClearResponseButton extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Tooltip( return IconButton(
message: 'Clear response', tooltip: kTooltipClearResponse,
child: TextButton(
style: TextButton.styleFrom(minimumSize: const Size(40, 40)),
onPressed: onPressed, onPressed: onPressed,
child: const Icon( icon: const Icon(
Icons.delete, Icons.delete,
size: 20, size: 16,
),
), ),
); );
} }

View File

@ -3,7 +3,7 @@ import 'package:flutter/material.dart';
class CheckBox extends StatelessWidget { class CheckBox extends StatelessWidget {
final String keyId; final String keyId;
final bool value; final bool value;
final ValueChanged<bool?> onChanged; final ValueChanged<bool?>? onChanged;
final ColorScheme? colorScheme; final ColorScheme? colorScheme;
const CheckBox({ const CheckBox({
super.key, super.key,

View File

@ -157,6 +157,7 @@ class DropdownButtonCodegenLanguage extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final surfaceColor = Theme.of(context).colorScheme.surface; final surfaceColor = Theme.of(context).colorScheme.surface;
return DropdownButton<CodegenLanguage>( return DropdownButton<CodegenLanguage>(
isExpanded: true,
focusColor: surfaceColor, focusColor: surfaceColor,
value: codegenLanguage, value: codegenLanguage,
icon: const Icon( icon: const Icon(
@ -181,6 +182,8 @@ class DropdownButtonCodegenLanguage extends StatelessWidget {
child: Text( child: Text(
value.label, value.label,
style: kTextStyleButton, style: kTextStyleButton,
overflow: TextOverflow.ellipsis,
maxLines: 1,
), ),
), ),
); );

View File

@ -48,16 +48,12 @@ class _RequestPaneState extends State<RequestPane>
return Column( return Column(
children: [ children: [
Padding( Padding(
padding: kPh20v10, padding: kP8,
child: SizedBox( child: SizedBox(
height: kHeaderHeight, height: kHeaderHeight,
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.end,
children: [ children: [
Text(
"Request",
style: Theme.of(context).textTheme.titleMedium,
),
FilledButton.tonalIcon( FilledButton.tonalIcon(
onPressed: widget.onPressedCodeButton, onPressed: widget.onPressedCodeButton,
icon: Icon( icon: Icon(
@ -68,7 +64,8 @@ class _RequestPaneState extends State<RequestPane>
label: SizedBox( label: SizedBox(
width: 75, width: 75,
child: Text( child: Text(
widget.codePaneVisible ? "Hide Code" : "View Code"), widget.codePaneVisible ? kLabelHideCode : kLabelViewCode,
),
), ),
), ),
], ],
@ -79,18 +76,19 @@ class _RequestPaneState extends State<RequestPane>
key: Key(widget.selectedId!), key: Key(widget.selectedId!),
controller: _controller, controller: _controller,
overlayColor: kColorTransparentState, overlayColor: kColorTransparentState,
labelPadding: kPh2,
onTap: widget.onTapTabBar, onTap: widget.onTapTabBar,
tabs: [ tabs: [
TabLabel( TabLabel(
text: 'URL Params', text: kLabelURLParams,
showIndicator: widget.showIndicators[0], showIndicator: widget.showIndicators[0],
), ),
TabLabel( TabLabel(
text: 'Headers', text: kLabelHeaders,
showIndicator: widget.showIndicators[1], showIndicator: widget.showIndicators[1],
), ),
TabLabel( TabLabel(
text: 'Body', text: kLabelBody,
showIndicator: widget.showIndicators[2], showIndicator: widget.showIndicators[2],
), ),
], ],

View File

@ -25,11 +25,9 @@ class NotSentWidget extends StatelessWidget {
color: color, color: color,
), ),
Text( Text(
'Not Sent', kLabelNotSent,
style: Theme.of(context) style:
.textTheme Theme.of(context).textTheme.titleMedium?.copyWith(color: color),
.titleMedium
?.copyWith(color: color),
), ),
], ],
), ),
@ -123,42 +121,19 @@ class ResponsePaneHeader extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Padding( return Padding(
padding: kPh20v10, padding: kPv8,
child: SizedBox( child: SizedBox(
height: kHeaderHeight, height: kHeaderHeight,
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text.rich( kHSpacer10,
TextSpan(
children: [
const TextSpan(
text: "Response (",
),
TextSpan(
text: "$responseStatus",
style: TextStyle(
color: getResponseStatusCodeColor(
responseStatus,
brightness: Theme.of(context).brightness,
),
fontFamily: kCodeStyle.fontFamily,
),
),
const TextSpan(
text: ")",
),
],
style: Theme.of(context).textTheme.titleMedium,
),
),
kHSpacer20,
Expanded( Expanded(
child: Text( child: Text(
message ?? "", "$responseStatus: ${message ?? '-'}",
softWrap: false, softWrap: false,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.titleMedium!.copyWith( style: Theme.of(context).textTheme.bodyMedium?.copyWith(
fontFamily: kCodeStyle.fontFamily, fontFamily: kCodeStyle.fontFamily,
color: getResponseStatusCodeColor( color: getResponseStatusCodeColor(
responseStatus, responseStatus,
@ -167,10 +142,10 @@ class ResponsePaneHeader extends StatelessWidget {
), ),
), ),
), ),
kHSpacer20, kHSpacer10,
Text( Text(
humanizeDuration(time), humanizeDuration(time),
style: Theme.of(context).textTheme.titleMedium!.copyWith( style: Theme.of(context).textTheme.bodyMedium?.copyWith(
fontFamily: kCodeStyle.fontFamily, fontFamily: kCodeStyle.fontFamily,
color: Theme.of(context).colorScheme.secondary, color: Theme.of(context).colorScheme.secondary,
), ),
@ -220,31 +195,15 @@ class _ResponseTabViewState extends State<ResponseTabView>
TabBar( TabBar(
key: Key(widget.selectedId!), key: Key(widget.selectedId!),
controller: _controller, controller: _controller,
labelPadding: kPh2,
overlayColor: kColorTransparentState, overlayColor: kColorTransparentState,
onTap: (index) {}, onTap: (index) {},
tabs: const [ tabs: const [
SizedBox( TabLabel(
height: kTabHeight, text: kLabelResponseBody,
child: Center(
child: Text(
'Body',
textAlign: TextAlign.center,
overflow: TextOverflow.fade,
softWrap: false,
style: kTextStyleButton,
),
),
),
SizedBox(
height: kTabHeight,
child: Center(
child: Text(
'Headers',
textAlign: TextAlign.center,
overflow: TextOverflow.fade,
style: kTextStyleButton,
),
), ),
TabLabel(
text: kLabelHeaders,
), ),
], ],
), ),
@ -284,8 +243,8 @@ class ResponseHeadersHeader extends StatelessWidget {
children: [ children: [
Expanded( Expanded(
child: Text( child: Text(
"$name (${map.length} items)", "$name (${map.length} $kLabelItems)",
style: Theme.of(context).textTheme.labelLarge!.copyWith( style: Theme.of(context).textTheme.labelMedium?.copyWith(
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
), ),
@ -300,8 +259,6 @@ class ResponseHeadersHeader extends StatelessWidget {
} }
} }
const kHeaderRow = ["Header Name", "Header Value"];
class ResponseHeaders extends StatelessWidget { class ResponseHeaders extends StatelessWidget {
const ResponseHeaders({ const ResponseHeaders({
super.key, super.key,
@ -320,7 +277,7 @@ class ResponseHeaders extends StatelessWidget {
children: [ children: [
ResponseHeadersHeader( ResponseHeadersHeader(
map: responseHeaders, map: responseHeaders,
name: "Response Headers", name: kLabelResponseHeaders,
), ),
if (responseHeaders.isNotEmpty) kVSpacer5, if (responseHeaders.isNotEmpty) kVSpacer5,
if (responseHeaders.isNotEmpty) if (responseHeaders.isNotEmpty)
@ -332,7 +289,7 @@ class ResponseHeaders extends StatelessWidget {
kVSpacer10, kVSpacer10,
ResponseHeadersHeader( ResponseHeadersHeader(
map: requestHeaders, map: requestHeaders,
name: "Request Headers", name: kLabelRequestHeaders,
), ),
if (requestHeaders.isNotEmpty) kVSpacer5, if (requestHeaders.isNotEmpty) kVSpacer5,
if (requestHeaders.isNotEmpty) if (requestHeaders.isNotEmpty)
@ -359,20 +316,18 @@ class ResponseBody extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final responseModel = selectedRequestModel?.responseModel; final responseModel = selectedRequestModel?.responseModel;
if (responseModel == null) { if (responseModel == null) {
return const ErrorMessage( return const ErrorMessage(message: '$kMsgError $kUnexpectedRaiseIssue');
message:
'Error: Response data does not exist. $kUnexpectedRaiseIssue');
} }
var body = responseModel.body; var body = responseModel.body;
var formattedBody = responseModel.formattedBody; var formattedBody = responseModel.formattedBody;
if (body == null) { if (body == null) {
return const ErrorMessage( return const ErrorMessage(
message: 'Response body is missing (null). $kUnexpectedRaiseIssue'); message: '$kMsgNullBody $kUnexpectedRaiseIssue');
} }
if (body.isEmpty) { if (body.isEmpty) {
return const ErrorMessage( return const ErrorMessage(
message: 'No content', message: kMsgNoContent,
showIcon: false, showIcon: false,
showIssueButton: false, showIssueButton: false,
); );
@ -382,7 +337,7 @@ class ResponseBody extends StatelessWidget {
if (mediaType == null) { if (mediaType == null) {
return ErrorMessage( return ErrorMessage(
message: message:
'Unknown Response Content-Type - ${responseModel.contentType}. $kUnexpectedRaiseIssue'); '$kMsgUnknowContentType - ${responseModel.contentType}. $kUnexpectedRaiseIssue');
} }
var responseBodyView = getResponseBodyViewOptions(mediaType); var responseBodyView = getResponseBodyViewOptions(mediaType);

View File

@ -2,7 +2,11 @@ import 'package:flutter/material.dart';
import 'package:apidash/consts.dart'; import 'package:apidash/consts.dart';
class TabLabel extends StatelessWidget { class TabLabel extends StatelessWidget {
const TabLabel({super.key, required this.text, this.showIndicator = false}); const TabLabel({
super.key,
required this.text,
this.showIndicator = false,
});
final String text; final String text;
final bool showIndicator; final bool showIndicator;
@ -18,14 +22,14 @@ class TabLabel extends StatelessWidget {
textAlign: TextAlign.center, textAlign: TextAlign.center,
overflow: TextOverflow.fade, overflow: TextOverflow.fade,
softWrap: false, softWrap: false,
style: kTextStyleButton, style: kTextStyleTab,
), ),
), ),
if (showIndicator) if (showIndicator)
const Align( const Align(
alignment: Alignment.topCenter, alignment: Alignment.topCenter,
child: Padding( child: Padding(
padding: EdgeInsets.only(top: 6), padding: EdgeInsets.only(top: 1),
child: Icon( child: Icon(
Icons.circle, Icons.circle,
size: 6, size: 6,

View File

@ -94,14 +94,39 @@ class JsonSearchField extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return TextField( return RawTextField(
controller: controller, controller: controller,
onChanged: onChanged, onChanged: onChanged,
style: kCodeStyle, style: kCodeStyle,
decoration: const InputDecoration( hintText: 'Search..',
);
}
}
class RawTextField extends StatelessWidget {
const RawTextField({
super.key,
this.onChanged,
this.controller,
this.hintText,
this.style,
});
final void Function(String)? onChanged;
final TextEditingController? controller;
final String? hintText;
final TextStyle? style;
@override
Widget build(BuildContext context) {
return TextField(
controller: controller,
onChanged: onChanged,
style: style,
decoration: InputDecoration(
isDense: true, isDense: true,
border: InputBorder.none, border: InputBorder.none,
hintText: 'Search..', hintText: hintText,
), ),
); );
} }

View File

@ -0,0 +1,992 @@
import 'package:apidash/codegen/codegen.dart';
import 'package:apidash/consts.dart';
import 'package:test/test.dart';
import '../request_models.dart';
void main() {
final codeGen = Codegen();
group('GET Request', () {
test('GET1', () {
const expectedCode = r"""
use curl::easy::Easy;
fn main() {
let mut easy = Easy::new();
let mut data = Vec::new();
easy.url("https://api.apidash.dev").unwrap();
easy.get(true).unwrap();
{
let mut transfer = easy.transfer();
transfer.write_function(|new_data| {
data.extend_from_slice(new_data);
Ok(new_data.len())
}).unwrap();
transfer.perform().unwrap();
}
let response_body = String::from_utf8_lossy(&data);
println!("Response body: {}", response_body);
println!("Response code: {}", easy.response_code().unwrap());
}""";
expect(
codeGen.getCode(
CodegenLanguage.rustCurl, requestModelGet1, "https"),
expectedCode);
});
test('GET2', () {
const expectedCode = r"""
use curl::easy::Easy;
fn main() {
let mut easy = Easy::new();
let mut data = Vec::new();
easy.url("https://api.apidash.dev/country/data?code=US").unwrap();
easy.get(true).unwrap();
{
let mut transfer = easy.transfer();
transfer.write_function(|new_data| {
data.extend_from_slice(new_data);
Ok(new_data.len())
}).unwrap();
transfer.perform().unwrap();
}
let response_body = String::from_utf8_lossy(&data);
println!("Response body: {}", response_body);
println!("Response code: {}", easy.response_code().unwrap());
}""";
expect(
codeGen.getCode(
CodegenLanguage.rustCurl, requestModelGet2, "https"),
expectedCode);
});
test('GET3', () {
const expectedCode = r"""
use curl::easy::Easy;
fn main() {
let mut easy = Easy::new();
let mut data = Vec::new();
easy.url("https://api.apidash.dev/country/data?code=IND").unwrap();
easy.get(true).unwrap();
{
let mut transfer = easy.transfer();
transfer.write_function(|new_data| {
data.extend_from_slice(new_data);
Ok(new_data.len())
}).unwrap();
transfer.perform().unwrap();
}
let response_body = String::from_utf8_lossy(&data);
println!("Response body: {}", response_body);
println!("Response code: {}", easy.response_code().unwrap());
}""";
expect(
codeGen.getCode(
CodegenLanguage.rustCurl, requestModelGet3, "https"),
expectedCode);
});
test('GET4', () {
const expectedCode = r"""
use curl::easy::Easy;
fn main() {
let mut easy = Easy::new();
let mut data = Vec::new();
easy.url("https://api.apidash.dev/humanize/social?num=8700000&digits=3&system=SS&add_space=true&trailing_zeros=true").unwrap();
easy.get(true).unwrap();
{
let mut transfer = easy.transfer();
transfer.write_function(|new_data| {
data.extend_from_slice(new_data);
Ok(new_data.len())
}).unwrap();
transfer.perform().unwrap();
}
let response_body = String::from_utf8_lossy(&data);
println!("Response body: {}", response_body);
println!("Response code: {}", easy.response_code().unwrap());
}""";
expect(
codeGen.getCode(
CodegenLanguage.rustCurl, requestModelGet4, "https"),
expectedCode);
});
test('GET5', () {
const expectedCode = r"""
use curl::easy::Easy;
use curl::easy::List;
fn main() {
let mut easy = Easy::new();
let mut data = Vec::new();
easy.url("https://api.github.com/repos/foss42/apidash").unwrap();
easy.get(true).unwrap();
let mut list = List::new();
list.append("User-Agent: Test Agent").unwrap();
easy.http_headers(list).unwrap();
{
let mut transfer = easy.transfer();
transfer.write_function(|new_data| {
data.extend_from_slice(new_data);
Ok(new_data.len())
}).unwrap();
transfer.perform().unwrap();
}
let response_body = String::from_utf8_lossy(&data);
println!("Response body: {}", response_body);
println!("Response code: {}", easy.response_code().unwrap());
}""";
expect(
codeGen.getCode(
CodegenLanguage.rustCurl, requestModelGet5, "https"),
expectedCode);
});
test('GET6', () {
const expectedCode = r"""
use curl::easy::Easy;
use curl::easy::List;
fn main() {
let mut easy = Easy::new();
let mut data = Vec::new();
easy.url("https://api.github.com/repos/foss42/apidash?raw=true").unwrap();
easy.get(true).unwrap();
let mut list = List::new();
list.append("User-Agent: Test Agent").unwrap();
easy.http_headers(list).unwrap();
{
let mut transfer = easy.transfer();
transfer.write_function(|new_data| {
data.extend_from_slice(new_data);
Ok(new_data.len())
}).unwrap();
transfer.perform().unwrap();
}
let response_body = String::from_utf8_lossy(&data);
println!("Response body: {}", response_body);
println!("Response code: {}", easy.response_code().unwrap());
}""";
expect(
codeGen.getCode(
CodegenLanguage.rustCurl, requestModelGet6, "https"),
expectedCode);
});
test('GET7', () {
const expectedCode = r"""
use curl::easy::Easy;
fn main() {
let mut easy = Easy::new();
let mut data = Vec::new();
easy.url("https://api.apidash.dev").unwrap();
easy.get(true).unwrap();
{
let mut transfer = easy.transfer();
transfer.write_function(|new_data| {
data.extend_from_slice(new_data);
Ok(new_data.len())
}).unwrap();
transfer.perform().unwrap();
}
let response_body = String::from_utf8_lossy(&data);
println!("Response body: {}", response_body);
println!("Response code: {}", easy.response_code().unwrap());
}""";
expect(
codeGen.getCode(
CodegenLanguage.rustCurl, requestModelGet7, "https"),
expectedCode);
});
test('GET8', () {
const expectedCode = r"""
use curl::easy::Easy;
use curl::easy::List;
fn main() {
let mut easy = Easy::new();
let mut data = Vec::new();
easy.url("https://api.github.com/repos/foss42/apidash?raw=true").unwrap();
easy.get(true).unwrap();
let mut list = List::new();
list.append("User-Agent: Test Agent").unwrap();
easy.http_headers(list).unwrap();
{
let mut transfer = easy.transfer();
transfer.write_function(|new_data| {
data.extend_from_slice(new_data);
Ok(new_data.len())
}).unwrap();
transfer.perform().unwrap();
}
let response_body = String::from_utf8_lossy(&data);
println!("Response body: {}", response_body);
println!("Response code: {}", easy.response_code().unwrap());
}""";
expect(
codeGen.getCode(
CodegenLanguage.rustCurl, requestModelGet8, "https"),
expectedCode);
});
test('GET9', () {
const expectedCode = r"""
use curl::easy::Easy;
fn main() {
let mut easy = Easy::new();
let mut data = Vec::new();
easy.url("https://api.apidash.dev/humanize/social?num=8700000&add_space=true").unwrap();
easy.get(true).unwrap();
{
let mut transfer = easy.transfer();
transfer.write_function(|new_data| {
data.extend_from_slice(new_data);
Ok(new_data.len())
}).unwrap();
transfer.perform().unwrap();
}
let response_body = String::from_utf8_lossy(&data);
println!("Response body: {}", response_body);
println!("Response code: {}", easy.response_code().unwrap());
}""";
expect(
codeGen.getCode(
CodegenLanguage.rustCurl, requestModelGet9, "https"),
expectedCode);
});
test('GET10', () {
const expectedCode = r"""
use curl::easy::Easy;
use curl::easy::List;
fn main() {
let mut easy = Easy::new();
let mut data = Vec::new();
easy.url("https://api.apidash.dev/humanize/social").unwrap();
easy.get(true).unwrap();
let mut list = List::new();
list.append("User-Agent: Test Agent").unwrap();
easy.http_headers(list).unwrap();
{
let mut transfer = easy.transfer();
transfer.write_function(|new_data| {
data.extend_from_slice(new_data);
Ok(new_data.len())
}).unwrap();
transfer.perform().unwrap();
}
let response_body = String::from_utf8_lossy(&data);
println!("Response body: {}", response_body);
println!("Response code: {}", easy.response_code().unwrap());
}""";
expect(
codeGen.getCode(
CodegenLanguage.rustCurl, requestModelGet10, "https"),
expectedCode);
});
test('GET11', () {
const expectedCode = r"""
use curl::easy::Easy;
use curl::easy::List;
fn main() {
let mut easy = Easy::new();
let mut data = Vec::new();
easy.url("https://api.apidash.dev/humanize/social?num=8700000&digits=3").unwrap();
easy.get(true).unwrap();
let mut list = List::new();
list.append("User-Agent: Test Agent").unwrap();
easy.http_headers(list).unwrap();
{
let mut transfer = easy.transfer();
transfer.write_function(|new_data| {
data.extend_from_slice(new_data);
Ok(new_data.len())
}).unwrap();
transfer.perform().unwrap();
}
let response_body = String::from_utf8_lossy(&data);
println!("Response body: {}", response_body);
println!("Response code: {}", easy.response_code().unwrap());
}""";
expect(
codeGen.getCode(
CodegenLanguage.rustCurl, requestModelGet11, "https"),
expectedCode);
});
test('GET12', () {
const expectedCode = r"""
use curl::easy::Easy;
fn main() {
let mut easy = Easy::new();
let mut data = Vec::new();
easy.url("https://api.apidash.dev/humanize/social").unwrap();
easy.get(true).unwrap();
{
let mut transfer = easy.transfer();
transfer.write_function(|new_data| {
data.extend_from_slice(new_data);
Ok(new_data.len())
}).unwrap();
transfer.perform().unwrap();
}
let response_body = String::from_utf8_lossy(&data);
println!("Response body: {}", response_body);
println!("Response code: {}", easy.response_code().unwrap());
}""";
expect(
codeGen.getCode(
CodegenLanguage.rustCurl, requestModelGet12, "https"),
expectedCode);
});
});
group('HEAD Request', () {
test('HEAD1', () {
const expectedCode = r"""
use curl::easy::Easy;
fn main() {
let mut easy = Easy::new();
let mut data = Vec::new();
easy.url("https://api.apidash.dev").unwrap();
easy.nobody(true).unwrap();
{
let mut transfer = easy.transfer();
transfer.write_function(|new_data| {
data.extend_from_slice(new_data);
Ok(new_data.len())
}).unwrap();
transfer.perform().unwrap();
}
let response_body = String::from_utf8_lossy(&data);
println!("Response body: {}", response_body);
println!("Response code: {}", easy.response_code().unwrap());
}""";
expect(
codeGen.getCode(
CodegenLanguage.rustCurl, requestModelHead1, "https"),
expectedCode);
});
test('HEAD2', () {
const expectedCode = r"""
use curl::easy::Easy;
fn main() {
let mut easy = Easy::new();
let mut data = Vec::new();
easy.url("https://api.apidash.dev").unwrap();
easy.nobody(true).unwrap();
{
let mut transfer = easy.transfer();
transfer.write_function(|new_data| {
data.extend_from_slice(new_data);
Ok(new_data.len())
}).unwrap();
transfer.perform().unwrap();
}
let response_body = String::from_utf8_lossy(&data);
println!("Response body: {}", response_body);
println!("Response code: {}", easy.response_code().unwrap());
}""";
expect(
codeGen.getCode(
CodegenLanguage.rustCurl, requestModelHead2, "https"),
expectedCode);
});
});
group('POST Request', () {
test('POST1', () {
const expectedCode = r"""
use curl::easy::Easy;
use curl::easy::List;
fn main() {
let mut easy = Easy::new();
let mut data = Vec::new();
easy.url("https://api.apidash.dev/case/lower").unwrap();
easy.post(true).unwrap();
easy.post_fields_copy(r#"{
"text": "I LOVE Flutter"
}"#.as_bytes()).unwrap();
let mut list = List::new();
list.append("Content-Type: text/plain").unwrap();
easy.http_headers(list).unwrap();
{
let mut transfer = easy.transfer();
transfer.write_function(|new_data| {
data.extend_from_slice(new_data);
Ok(new_data.len())
}).unwrap();
transfer.perform().unwrap();
}
let response_body = String::from_utf8_lossy(&data);
println!("Response body: {}", response_body);
println!("Response code: {}", easy.response_code().unwrap());
}""";
expect(
codeGen.getCode(
CodegenLanguage.rustCurl, requestModelPost1, "https"),
expectedCode);
});
test('POST2', () {
const expectedCode = r"""
use curl::easy::Easy;
use serde_json::json;
use curl::easy::List;
fn main() {
let mut easy = Easy::new();
let mut data = Vec::new();
easy.url("https://api.apidash.dev/case/lower").unwrap();
easy.post(true).unwrap();
easy.post_fields_copy(json!({
"text": "I LOVE Flutter",
"flag": null,
"male": true,
"female": false,
"no": 1.2,
"arr": ["null", "true", "false", null]
}).to_string().as_bytes()).unwrap();
let mut list = List::new();
list.append("Content-Type: application/json").unwrap();
easy.http_headers(list).unwrap();
{
let mut transfer = easy.transfer();
transfer.write_function(|new_data| {
data.extend_from_slice(new_data);
Ok(new_data.len())
}).unwrap();
transfer.perform().unwrap();
}
let response_body = String::from_utf8_lossy(&data);
println!("Response body: {}", response_body);
println!("Response code: {}", easy.response_code().unwrap());
}""";
expect(
codeGen.getCode(
CodegenLanguage.rustCurl, requestModelPost2, "https"),
expectedCode);
});
test('POST3', () {
const expectedCode = r"""
use curl::easy::Easy;
use serde_json::json;
use curl::easy::List;
fn main() {
let mut easy = Easy::new();
let mut data = Vec::new();
easy.url("https://api.apidash.dev/case/lower").unwrap();
easy.post(true).unwrap();
easy.post_fields_copy(json!({
"text": "I LOVE Flutter"
}).to_string().as_bytes()).unwrap();
let mut list = List::new();
list.append("User-Agent: Test Agent").unwrap();
list.append("Content-Type: application/json").unwrap();
easy.http_headers(list).unwrap();
{
let mut transfer = easy.transfer();
transfer.write_function(|new_data| {
data.extend_from_slice(new_data);
Ok(new_data.len())
}).unwrap();
transfer.perform().unwrap();
}
let response_body = String::from_utf8_lossy(&data);
println!("Response body: {}", response_body);
println!("Response code: {}", easy.response_code().unwrap());
}""";
expect(
codeGen.getCode(
CodegenLanguage.rustCurl, requestModelPost3, "https"),
expectedCode);
});
test('POST4', () {
const expectedCode = r"""
use curl::easy::Easy;
fn main() {
let mut easy = Easy::new();
let mut data = Vec::new();
easy.url("https://api.apidash.dev/io/form").unwrap();
easy.post(true).unwrap();
let mut form = curl::easy::Form::new();
form.part("text")
.contents(b"API")
.add().unwrap();
form.part("sep")
.contents(b"|")
.add().unwrap();
form.part("times")
.contents(b"3")
.add().unwrap();
easy.httppost(form).unwrap();
{
let mut transfer = easy.transfer();
transfer.write_function(|new_data| {
data.extend_from_slice(new_data);
Ok(new_data.len())
}).unwrap();
transfer.perform().unwrap();
}
let response_body = String::from_utf8_lossy(&data);
println!("Response body: {}", response_body);
println!("Response code: {}", easy.response_code().unwrap());
}""";
expect(
codeGen.getCode(
CodegenLanguage.rustCurl, requestModelPost4, "https"),
expectedCode);
});
test('POST5', () {
const expectedCode = r"""
use curl::easy::Easy;
use curl::easy::List;
fn main() {
let mut easy = Easy::new();
let mut data = Vec::new();
easy.url("https://api.apidash.dev/io/form").unwrap();
easy.post(true).unwrap();
let mut form = curl::easy::Form::new();
form.part("text")
.contents(b"API")
.add().unwrap();
form.part("sep")
.contents(b"|")
.add().unwrap();
form.part("times")
.contents(b"3")
.add().unwrap();
easy.httppost(form).unwrap();
let mut list = List::new();
list.append("User-Agent: Test Agent").unwrap();
easy.http_headers(list).unwrap();
{
let mut transfer = easy.transfer();
transfer.write_function(|new_data| {
data.extend_from_slice(new_data);
Ok(new_data.len())
}).unwrap();
transfer.perform().unwrap();
}
let response_body = String::from_utf8_lossy(&data);
println!("Response body: {}", response_body);
println!("Response code: {}", easy.response_code().unwrap());
}""";
expect(
codeGen.getCode(
CodegenLanguage.rustCurl, requestModelPost5, "https"),
expectedCode);
});
test('POST6', () {
const expectedCode = r"""
use curl::easy::Easy;
fn main() {
let mut easy = Easy::new();
let mut data = Vec::new();
easy.url("https://api.apidash.dev/io/img").unwrap();
easy.post(true).unwrap();
let mut form = curl::easy::Form::new();
form.part("token")
.contents(b"xyz")
.add().unwrap();
form.part("imfile")
.file("/Documents/up/1.png")
.add().unwrap();
easy.httppost(form).unwrap();
{
let mut transfer = easy.transfer();
transfer.write_function(|new_data| {
data.extend_from_slice(new_data);
Ok(new_data.len())
}).unwrap();
transfer.perform().unwrap();
}
let response_body = String::from_utf8_lossy(&data);
println!("Response body: {}", response_body);
println!("Response code: {}", easy.response_code().unwrap());
}""";
expect(
codeGen.getCode(
CodegenLanguage.rustCurl, requestModelPost6, "https"),
expectedCode);
});
test('POST7', () {
const expectedCode = r"""
use curl::easy::Easy;
fn main() {
let mut easy = Easy::new();
let mut data = Vec::new();
easy.url("https://api.apidash.dev/io/img").unwrap();
easy.post(true).unwrap();
let mut form = curl::easy::Form::new();
form.part("token")
.contents(b"xyz")
.add().unwrap();
form.part("imfile")
.file("/Documents/up/1.png")
.add().unwrap();
easy.httppost(form).unwrap();
{
let mut transfer = easy.transfer();
transfer.write_function(|new_data| {
data.extend_from_slice(new_data);
Ok(new_data.len())
}).unwrap();
transfer.perform().unwrap();
}
let response_body = String::from_utf8_lossy(&data);
println!("Response body: {}", response_body);
println!("Response code: {}", easy.response_code().unwrap());
}""";
expect(
codeGen.getCode(
CodegenLanguage.rustCurl, requestModelPost7, "https"),
expectedCode);
});
test('POST8', () {
const expectedCode = r"""
use curl::easy::Easy;
fn main() {
let mut easy = Easy::new();
let mut data = Vec::new();
easy.url("https://api.apidash.dev/io/form?size=2&len=3").unwrap();
easy.post(true).unwrap();
let mut form = curl::easy::Form::new();
form.part("text")
.contents(b"API")
.add().unwrap();
form.part("sep")
.contents(b"|")
.add().unwrap();
form.part("times")
.contents(b"3")
.add().unwrap();
easy.httppost(form).unwrap();
{
let mut transfer = easy.transfer();
transfer.write_function(|new_data| {
data.extend_from_slice(new_data);
Ok(new_data.len())
}).unwrap();
transfer.perform().unwrap();
}
let response_body = String::from_utf8_lossy(&data);
println!("Response body: {}", response_body);
println!("Response code: {}", easy.response_code().unwrap());
}""";
expect(
codeGen.getCode(
CodegenLanguage.rustCurl, requestModelPost8, "https"),
expectedCode);
});
test('POST9', () {
const expectedCode = r"""
use curl::easy::Easy;
use curl::easy::List;
fn main() {
let mut easy = Easy::new();
let mut data = Vec::new();
easy.url("https://api.apidash.dev/io/img?size=2&len=3").unwrap();
easy.post(true).unwrap();
let mut form = curl::easy::Form::new();
form.part("token")
.contents(b"xyz")
.add().unwrap();
form.part("imfile")
.file("/Documents/up/1.png")
.add().unwrap();
easy.httppost(form).unwrap();
let mut list = List::new();
list.append("User-Agent: Test Agent").unwrap();
list.append("Keep-Alive: true").unwrap();
easy.http_headers(list).unwrap();
{
let mut transfer = easy.transfer();
transfer.write_function(|new_data| {
data.extend_from_slice(new_data);
Ok(new_data.len())
}).unwrap();
transfer.perform().unwrap();
}
let response_body = String::from_utf8_lossy(&data);
println!("Response body: {}", response_body);
println!("Response code: {}", easy.response_code().unwrap());
}""";
expect(
codeGen.getCode(
CodegenLanguage.rustCurl, requestModelPost9, "https"),
expectedCode);
});
});
group('PUT Request', () {
test('PUT1', () {
const expectedCode = r"""
use curl::easy::Easy;
use serde_json::json;
use curl::easy::List;
fn main() {
let mut easy = Easy::new();
let mut data = Vec::new();
easy.url("https://reqres.in/api/users/2").unwrap();
easy.put(true).unwrap();
easy.post_fields_copy(json!({
"name": "morpheus",
"job": "zion resident"
}).to_string().as_bytes()).unwrap();
let mut list = List::new();
list.append("Content-Type: application/json").unwrap();
easy.http_headers(list).unwrap();
{
let mut transfer = easy.transfer();
transfer.write_function(|new_data| {
data.extend_from_slice(new_data);
Ok(new_data.len())
}).unwrap();
transfer.perform().unwrap();
}
let response_body = String::from_utf8_lossy(&data);
println!("Response body: {}", response_body);
println!("Response code: {}", easy.response_code().unwrap());
}""";
expect(
codeGen.getCode(
CodegenLanguage.rustCurl, requestModelPut1, "https"),
expectedCode);
});
});
group('PATCH Request', () {
test('PATCH1', () {
const expectedCode = r"""
use curl::easy::Easy;
use serde_json::json;
use curl::easy::List;
fn main() {
let mut easy = Easy::new();
let mut data = Vec::new();
easy.url("https://reqres.in/api/users/2").unwrap();
easy.custom_request("PATCH").unwrap();
easy.post_fields_copy(json!({
"name": "marfeus",
"job": "accountant"
}).to_string().as_bytes()).unwrap();
let mut list = List::new();
list.append("Content-Type: application/json").unwrap();
easy.http_headers(list).unwrap();
{
let mut transfer = easy.transfer();
transfer.write_function(|new_data| {
data.extend_from_slice(new_data);
Ok(new_data.len())
}).unwrap();
transfer.perform().unwrap();
}
let response_body = String::from_utf8_lossy(&data);
println!("Response body: {}", response_body);
println!("Response code: {}", easy.response_code().unwrap());
}""";
expect(
codeGen.getCode(
CodegenLanguage.rustCurl, requestModelPatch1, "https"),
expectedCode);
});
});
group('DELETE Request', () {
test('DELETE1', () {
const expectedCode = r"""
use curl::easy::Easy;
fn main() {
let mut easy = Easy::new();
let mut data = Vec::new();
easy.url("https://reqres.in/api/users/2").unwrap();
easy.custom_request("DELETE").unwrap();
{
let mut transfer = easy.transfer();
transfer.write_function(|new_data| {
data.extend_from_slice(new_data);
Ok(new_data.len())
}).unwrap();
transfer.perform().unwrap();
}
let response_body = String::from_utf8_lossy(&data);
println!("Response body: {}", response_body);
println!("Response code: {}", easy.response_code().unwrap());
}""";
expect(
codeGen.getCode(
CodegenLanguage.rustCurl, requestModelDelete1, "https"),
expectedCode);
});
test('DELETE2', () {
const expectedCode = r"""
use curl::easy::Easy;
use serde_json::json;
use curl::easy::List;
fn main() {
let mut easy = Easy::new();
let mut data = Vec::new();
easy.url("https://reqres.in/api/users/2").unwrap();
easy.custom_request("DELETE").unwrap();
easy.post_fields_copy(json!({
"name": "marfeus",
"job": "accountant"
}).to_string().as_bytes()).unwrap();
let mut list = List::new();
list.append("Content-Type: application/json").unwrap();
easy.http_headers(list).unwrap();
{
let mut transfer = easy.transfer();
transfer.write_function(|new_data| {
data.extend_from_slice(new_data);
Ok(new_data.len())
}).unwrap();
transfer.perform().unwrap();
}
let response_body = String::from_utf8_lossy(&data);
println!("Response body: {}", response_body);
println!("Response code: {}", easy.response_code().unwrap());
}""";
expect(
codeGen.getCode(
CodegenLanguage.rustCurl, requestModelDelete2, "https"),
expectedCode);
});
});
}

View File

@ -0,0 +1,339 @@
import 'dart:io';
import 'package:apidash/providers/providers.dart';
import 'package:apidash/screens/dashboard.dart';
import 'package:apidash/screens/home_page/collection_pane.dart';
import 'package:apidash/screens/home_page/home_page.dart';
import 'package:apidash/screens/intro_page.dart';
import 'package:apidash/screens/settings_page.dart';
import 'package:apidash/services/hive_services.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
setUp(() async {
const MethodChannel channel =
MethodChannel('plugins.flutter.io/path_provider');
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(channel, (MethodCall methodCall) async {
if (methodCall.method == 'getApplicationDocumentsDirectory') {
// Create a mock app doc directory for testing
Directory tempDir =
await Directory.systemTemp.createTemp('mock_app_doc_dir');
return tempDir.path; // Return the path to the mock directory
}
return null;
});
await openBoxes();
});
group('Testing navRailIndexStateProvider', () {
testWidgets('Dashboard should display correct initial page',
(tester) async {
await tester.pumpWidget(
const ProviderScope(
child: MaterialApp(
home: Dashboard(),
),
),
);
// Verify that the HomePage is displayed initially
expect(find.byType(HomePage), findsOneWidget);
expect(find.byType(IntroPage), findsNothing);
expect(find.byType(SettingsPage), findsNothing);
});
testWidgets(
"Dashboard should display IntroPage when navRailIndexStateProvider is 1",
(WidgetTester tester) async {
await tester.pumpWidget(
ProviderScope(
overrides: [
navRailIndexStateProvider.overrideWith((ref) => 1),
],
child: const MaterialApp(
home: Dashboard(),
),
),
);
// Verify that the IntroPage is displayed
expect(find.byType(IntroPage), findsOneWidget);
expect(find.byType(HomePage), findsNothing);
expect(find.byType(SettingsPage), findsNothing);
});
testWidgets(
"Dashboard should display SettingsPage when navRailIndexStateProvider is 2",
(WidgetTester tester) async {
await tester.pumpWidget(
ProviderScope(
overrides: [
navRailIndexStateProvider.overrideWith((ref) => 2),
],
child: const MaterialApp(
home: Dashboard(),
),
),
);
// Verify that the SettingsPage is displayed
expect(find.byType(SettingsPage), findsOneWidget);
expect(find.byType(IntroPage), findsNothing);
expect(find.byType(HomePage), findsNothing);
});
testWidgets(
'navRailIndexStateProvider should update when icon button is pressed',
(tester) async {
await tester.pumpWidget(
const ProviderScope(
child: MaterialApp(
home: Dashboard(),
),
),
);
// Tap on the Intro icon
await tester.tap(find.byIcon(Icons.help_outline));
await tester.pump();
// Verify that the navRailIndexStateProvider is updated
final dashboard = tester.element(find.byType(Dashboard));
final container = ProviderScope.containerOf(dashboard);
expect(container.read(navRailIndexStateProvider), 1);
});
testWidgets(
'navRailIndexStateProvider should persist across widget rebuilds',
(tester) async {
// Pump the initial widget tree
await tester.pumpWidget(
const ProviderScope(
child: MaterialApp(
home: Dashboard(),
),
),
);
// Tap on the Settings icon to change the index to 2
await tester.tap(find.byIcon(Icons.settings_outlined));
await tester.pump();
// Rebuild the widget tree with the same ProviderScope
await tester.pumpWidget(
const ProviderScope(
child: MaterialApp(
home: Dashboard(),
),
),
);
// Verify that the navRailIndexStateProvider still has the updated value
final dashboard = tester.element(find.byType(Dashboard));
final container = ProviderScope.containerOf(dashboard);
expect(container.read(navRailIndexStateProvider), 2);
// Verify that the SettingsPage is still displayed after the rebuild
expect(find.byType(SettingsPage), findsOneWidget);
expect(find.byType(IntroPage), findsNothing);
expect(find.byType(HomePage), findsNothing);
});
testWidgets(
'UI should update correctly when navRailIndexStateProvider changes',
(tester) async {
await tester.pumpWidget(
const ProviderScope(
child: MaterialApp(
home: Dashboard(),
),
),
);
// Grab the Dashboard widget and its ProviderContainer
final dashboard = tester.element(find.byType(Dashboard));
final container = ProviderScope.containerOf(dashboard);
// Go to IntroPage
container.read(navRailIndexStateProvider.notifier).state = 1;
await tester.pump();
// Verify that the IntroPage is displayed
expect(find.byType(IntroPage), findsOneWidget);
// Verify that the selected icon is the filled version (selectedIcon)
expect(find.byIcon(Icons.help), findsOneWidget);
// Go to SettingsPage
container.read(navRailIndexStateProvider.notifier).state = 2;
await tester.pump();
// Verify that the SettingsPage is displayed
expect(find.byType(SettingsPage), findsOneWidget);
// Verify that the selected icon is the filled version (selectedIcon)
expect(find.byIcon(Icons.settings), findsOneWidget);
});
testWidgets(
'navRailIndexStateProvider should be disposed when Dashboard is removed',
(tester) async {
await tester.pumpWidget(
const ProviderScope(
child: MaterialApp(
home: Dashboard(),
),
),
);
// Grab the Dashboard widget and its ProviderContainer
final dashboard = tester.element(find.byType(Dashboard));
final container = ProviderScope.containerOf(dashboard);
// Pumping a different widget to remove the Dashboard from the widget tree
await tester.pumpWidget(
const MaterialApp(
home: Scaffold(body: Text('Different Widget')),
),
);
// Verify that the ProviderContainer has been disposed
// by trying to read from disposed container
bool isDisposed = false;
try {
container.read(navRailIndexStateProvider);
} catch (e) {
isDisposed = true;
}
expect(isDisposed, true);
});
});
group("Testing selectedIdEditStateProvider", () {
testWidgets('It should have an initial value of null', (tester) async {
await tester.pumpWidget(
const ProviderScope(
child: MaterialApp(
home: Scaffold(
body: CollectionPane(),
),
),
),
);
// Verify that the initial value is null
final collectionPane = tester.element(find.byType(CollectionPane));
final container = ProviderScope.containerOf(collectionPane);
expect(container.read(selectedIdEditStateProvider), null);
});
testWidgets(
'selectedIdEditStateProvider should not be null after rename button has been tapped',
(tester) async {
await tester.pumpWidget(
const ProviderScope(
child: MaterialApp(
home: Scaffold(
body: CollectionPane(),
),
),
),
);
// Tap on the three dots to open the request card menu
await tester.tap(find.byType(RequestList));
await tester.pump();
await tester.tap(find.byType(RequestItem));
await tester.pump();
await tester.tap(find.byIcon(Icons.more_vert).first);
await tester.pumpAndSettle();
// Tap on the "Rename" option in the menu
await tester.tap(find.text('Rename'));
await tester.pumpAndSettle();
// Verify that the selectedIdEditStateProvider is not null
final collectionPane = tester.element(find.byType(CollectionPane));
final container = ProviderScope.containerOf(collectionPane);
expect(container.read(selectedIdEditStateProvider), isNotNull);
expect((container.read(selectedIdEditStateProvider)).runtimeType, String);
});
testWidgets(
'It should be set back to null when user taps outside name editor',
(tester) async {
await tester.pumpWidget(
const ProviderScope(
child: MaterialApp(
home: Scaffold(
body: CollectionPane(),
),
),
),
);
// Grab the CollectionPane widget and its ProviderContainer
final collectionPane = tester.element(find.byType(CollectionPane));
final container = ProviderScope.containerOf(collectionPane);
// Tap on the three dots to open the request card menu
await tester.tap(find.byType(RequestList));
await tester.pump();
await tester.tap(find.byType(RequestItem));
await tester.pump();
await tester.tap(find.byIcon(Icons.more_vert).first);
await tester.pumpAndSettle();
// Tap on the "Rename" option in the menu
await tester.tap(find.text('Rename'));
await tester.pumpAndSettle();
// Verify that the selectedIdEditStateProvider is not null
expect(container.read(selectedIdEditStateProvider), isNotNull);
expect((container.read(selectedIdEditStateProvider)).runtimeType, String);
// Tap on the screen to simulate tapping outside the name editor
await tester.tap(find.byType(CollectionPane));
await tester.pumpAndSettle();
// Verify that the selectedIdEditStateProvider is null
expect(container.read(selectedIdEditStateProvider), null);
});
testWidgets("It should be properly disposed", (tester) async {
await tester.pumpWidget(
const ProviderScope(
child: MaterialApp(
home: Scaffold(
body: CollectionPane(),
),
),
),
);
// Grab the Dashboard widget and its ProviderContainer
final collectionPane = tester.element(find.byType(CollectionPane));
final container = ProviderScope.containerOf(collectionPane);
// Pumping a different widget to remove the CollectionPane from the widget tree
await tester.pumpWidget(
const MaterialApp(
home: Scaffold(body: Text('Foo')),
),
);
// Verify that the ProviderContainer has been disposed
// by trying to read from disposed container
bool isDisposed = false;
try {
container.read(selectedIdEditStateProvider);
} catch (e) {
isDisposed = true;
}
expect(isDisposed, true);
});
});
}

View File

@ -21,7 +21,6 @@ void main() {
); );
expect(find.byType(Center), findsAtLeastNWidgets(1)); expect(find.byType(Center), findsAtLeastNWidgets(1));
expect(find.text('Request'), findsOneWidget);
expect(find.text('Hide Code'), findsOneWidget); expect(find.text('Hide Code'), findsOneWidget);
expect(find.text('View Code'), findsNothing); expect(find.text('View Code'), findsNothing);
expect(find.text('URL Params'), findsOneWidget); expect(find.text('URL Params'), findsOneWidget);
@ -52,7 +51,6 @@ void main() {
); );
expect(find.byType(Center), findsAtLeastNWidgets(1)); expect(find.byType(Center), findsAtLeastNWidgets(1));
expect(find.text('Request'), findsOneWidget);
expect(find.text('Hide Code'), findsOneWidget); expect(find.text('Hide Code'), findsOneWidget);
expect(find.text('View Code'), findsNothing); expect(find.text('View Code'), findsNothing);
expect(find.text('URL Params'), findsOneWidget); expect(find.text('URL Params'), findsOneWidget);
@ -83,7 +81,6 @@ void main() {
); );
expect(find.byType(Center), findsAtLeastNWidgets(1)); expect(find.byType(Center), findsAtLeastNWidgets(1));
expect(find.text('Request'), findsOneWidget);
expect(find.text('Hide Code'), findsNothing); expect(find.text('Hide Code'), findsNothing);
expect(find.text('View Code'), findsOneWidget); expect(find.text('View Code'), findsOneWidget);
expect(find.text('URL Params'), findsOneWidget); expect(find.text('URL Params'), findsOneWidget);
@ -117,7 +114,6 @@ void main() {
); );
expect(find.byType(Center), findsAtLeastNWidgets(1)); expect(find.byType(Center), findsAtLeastNWidgets(1));
expect(find.text('Request'), findsOneWidget);
expect(find.text('URL Params'), findsOneWidget); expect(find.text('URL Params'), findsOneWidget);
expect(find.text('Headers'), findsOneWidget); expect(find.text('Headers'), findsOneWidget);
expect(find.text('Body'), findsOneWidget); expect(find.text('Body'), findsOneWidget);

View File

@ -75,11 +75,8 @@ void main() {
), ),
); );
expect(find.byType(RichText), findsAtLeastNWidgets(1));
expect(
find.textContaining("Response (", findRichText: true), findsOneWidget);
expect(find.text('Hi'), findsOneWidget);
expect(find.textContaining("200", findRichText: true), findsOneWidget); expect(find.textContaining("200", findRichText: true), findsOneWidget);
expect(find.textContaining("Hi", findRichText: true), findsOneWidget);
expect(find.text(humanizeDuration(const Duration(microseconds: 23))), expect(find.text(humanizeDuration(const Duration(microseconds: 23))),
findsOneWidget); findsOneWidget);
}); });
@ -96,7 +93,7 @@ void main() {
), ),
); );
expect(find.text('Body'), findsOneWidget); expect(find.text('Response Body'), findsOneWidget);
expect(find.text('Headers'), findsOneWidget); expect(find.text('Headers'), findsOneWidget);
await tester.tap(find.text('Headers')); await tester.tap(find.text('Headers'));
@ -104,7 +101,7 @@ void main() {
expect(find.text('first'), findsNothing); expect(find.text('first'), findsNothing);
expect(find.text('second'), findsOneWidget); expect(find.text('second'), findsOneWidget);
await tester.tap(find.text('Body')); await tester.tap(find.text('Response Body'));
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect(find.text('first'), findsOneWidget); expect(find.text('first'), findsOneWidget);