mirror of
https://github.com/foss42/apidash.git
synced 2025-06-29 12:26:41 +08:00
Merge branch 'foss42:main' into resolve-issue-missing-drag-scrolling
This commit is contained in:
1
.github/pull_request_template.md
vendored
1
.github/pull_request_template.md
vendored
@ -9,6 +9,7 @@ _Add your description_
|
|||||||
|
|
||||||
### Checklist
|
### Checklist
|
||||||
- [ ] I have gone through the [contributing guide](https://github.com/foss42/apidash/blob/main/CONTRIBUTING.md)
|
- [ ] I have gone through the [contributing guide](https://github.com/foss42/apidash/blob/main/CONTRIBUTING.md)
|
||||||
|
- [ ] I have updated my branch and synced it with project `main` branch before making this PR
|
||||||
- [ ] I have run the tests (`flutter test`) and all tests are passing
|
- [ ] I have run the tests (`flutter test`) and all tests are passing
|
||||||
|
|
||||||
## Added/updated tests?
|
## Added/updated tests?
|
||||||
|
@ -58,7 +58,7 @@ class cURLCodeGen {
|
|||||||
} else if (requestModel.hasFormData) {
|
} else if (requestModel.hasFormData) {
|
||||||
for (var formData in requestModel.formDataList) {
|
for (var formData in requestModel.formDataList) {
|
||||||
var templateFormData = jj.Template(kTemplateFormData);
|
var templateFormData = jj.Template(kTemplateFormData);
|
||||||
if (formData.name.isNotEmpty && formData.value.isNotEmpty) {
|
if (formData.name.isNotEmpty) {
|
||||||
result += templateFormData.render({
|
result += templateFormData.render({
|
||||||
"name": formData.name,
|
"name": formData.name,
|
||||||
"value":
|
"value":
|
||||||
|
@ -80,10 +80,10 @@ class RequestModel {
|
|||||||
bool get hasFormData =>
|
bool get hasFormData =>
|
||||||
kMethodsWithBody.contains(method) &&
|
kMethodsWithBody.contains(method) &&
|
||||||
hasFormDataContentType &&
|
hasFormDataContentType &&
|
||||||
(requestFormDataList ?? <FormDataModel>[]).isNotEmpty;
|
formDataMapList.isNotEmpty;
|
||||||
List<FormDataModel> get formDataList =>
|
List<FormDataModel> get formDataList =>
|
||||||
requestFormDataList ?? <FormDataModel>[];
|
requestFormDataList ?? <FormDataModel>[];
|
||||||
List<Map<String, dynamic>> get formDataMapList =>
|
List<Map<String, String>> get formDataMapList =>
|
||||||
rowsToFormDataMapList(requestFormDataList) ?? [];
|
rowsToFormDataMapList(requestFormDataList) ?? [];
|
||||||
bool get hasFileInFormData => formDataList
|
bool get hasFileInFormData => formDataList
|
||||||
.map((e) => e.type == FormDataType.file)
|
.map((e) => e.type == FormDataType.file)
|
||||||
@ -94,12 +94,13 @@ class RequestModel {
|
|||||||
|
|
||||||
RequestModel duplicate({
|
RequestModel duplicate({
|
||||||
required String id,
|
required String id,
|
||||||
|
String? name,
|
||||||
}) {
|
}) {
|
||||||
return RequestModel(
|
return RequestModel(
|
||||||
id: id,
|
id: id,
|
||||||
method: method,
|
method: method,
|
||||||
url: url,
|
url: url,
|
||||||
name: "$name (copy)",
|
name: name ?? "${this.name} (copy)",
|
||||||
description: description,
|
description: description,
|
||||||
requestHeaders: requestHeaders != null ? [...requestHeaders!] : null,
|
requestHeaders: requestHeaders != null ? [...requestHeaders!] : null,
|
||||||
requestParams: requestParams != null ? [...requestParams!] : null,
|
requestParams: requestParams != null ? [...requestParams!] : null,
|
||||||
@ -137,6 +138,7 @@ class RequestModel {
|
|||||||
var params = requestParams ?? this.requestParams;
|
var params = requestParams ?? this.requestParams;
|
||||||
var enabledHeaders = isHeaderEnabledList ?? this.isHeaderEnabledList;
|
var enabledHeaders = isHeaderEnabledList ?? this.isHeaderEnabledList;
|
||||||
var enabledParams = isParamEnabledList ?? this.isParamEnabledList;
|
var enabledParams = isParamEnabledList ?? this.isParamEnabledList;
|
||||||
|
var formDataList = requestFormDataList ?? this.requestFormDataList;
|
||||||
return RequestModel(
|
return RequestModel(
|
||||||
id: id ?? this.id,
|
id: id ?? this.id,
|
||||||
method: method ?? this.method,
|
method: method ?? this.method,
|
||||||
@ -151,7 +153,7 @@ class RequestModel {
|
|||||||
requestBodyContentType:
|
requestBodyContentType:
|
||||||
requestBodyContentType ?? this.requestBodyContentType,
|
requestBodyContentType ?? this.requestBodyContentType,
|
||||||
requestBody: requestBody ?? this.requestBody,
|
requestBody: requestBody ?? this.requestBody,
|
||||||
requestFormDataList: requestFormDataList ?? this.requestFormDataList,
|
requestFormDataList: formDataList != null ? [...formDataList] : null,
|
||||||
responseStatus: responseStatus ?? this.responseStatus,
|
responseStatus: responseStatus ?? this.responseStatus,
|
||||||
message: message ?? this.message,
|
message: message ?? this.message,
|
||||||
responseModel: responseModel ?? this.responseModel,
|
responseModel: responseModel ?? this.responseModel,
|
||||||
|
@ -96,6 +96,18 @@ class CollectionStateNotifier
|
|||||||
state = map;
|
state = map;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void clearResponse(String? id) {
|
||||||
|
if (id == null || state?[id] == null) return;
|
||||||
|
var currentModel = state![id]!;
|
||||||
|
final newModel = currentModel.duplicate(
|
||||||
|
id: id,
|
||||||
|
name: currentModel.name,
|
||||||
|
);
|
||||||
|
var map = {...state!};
|
||||||
|
map[id] = newModel;
|
||||||
|
state = map;
|
||||||
|
}
|
||||||
|
|
||||||
void duplicate(String id) {
|
void duplicate(String id) {
|
||||||
final newId = getNewUuid();
|
final newId = getNewUuid();
|
||||||
|
|
||||||
@ -134,21 +146,22 @@ class CollectionStateNotifier
|
|||||||
ResponseModel? responseModel,
|
ResponseModel? responseModel,
|
||||||
}) {
|
}) {
|
||||||
final newModel = state![id]!.copyWith(
|
final newModel = state![id]!.copyWith(
|
||||||
method: method,
|
method: method,
|
||||||
url: url,
|
url: url,
|
||||||
name: name,
|
name: name,
|
||||||
description: description,
|
description: description,
|
||||||
requestTabIndex: requestTabIndex,
|
requestTabIndex: requestTabIndex,
|
||||||
requestHeaders: requestHeaders,
|
requestHeaders: requestHeaders,
|
||||||
requestParams: requestParams,
|
requestParams: requestParams,
|
||||||
isHeaderEnabledList: isHeaderEnabledList,
|
isHeaderEnabledList: isHeaderEnabledList,
|
||||||
isParamEnabledList: isParamEnabledList,
|
isParamEnabledList: isParamEnabledList,
|
||||||
requestBodyContentType: requestBodyContentType,
|
requestBodyContentType: requestBodyContentType,
|
||||||
requestBody: requestBody,
|
requestBody: requestBody,
|
||||||
requestFormDataList: requestFormDataList,
|
requestFormDataList: requestFormDataList,
|
||||||
responseStatus: responseStatus,
|
responseStatus: responseStatus,
|
||||||
message: message,
|
message: message,
|
||||||
responseModel: responseModel);
|
responseModel: responseModel,
|
||||||
|
);
|
||||||
//print(newModel);
|
//print(newModel);
|
||||||
var map = {...state!};
|
var map = {...state!};
|
||||||
map[id] = newModel;
|
map[id] = newModel;
|
||||||
|
@ -34,6 +34,7 @@ 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
|
||||||
@ -46,6 +47,14 @@ class ResponseDetails extends ConsumerWidget {
|
|||||||
responseStatus: responseStatus,
|
responseStatus: responseStatus,
|
||||||
message: message,
|
message: message,
|
||||||
time: responseModel?.time,
|
time: responseModel?.time,
|
||||||
|
onClearResponse: () {
|
||||||
|
final selectedRequest = ref.read(selectedRequestModelProvider);
|
||||||
|
ref
|
||||||
|
.read(collectionStateNotifierProvider.notifier)
|
||||||
|
.clearResponse(selectedRequest?.id);
|
||||||
|
sm.hideCurrentSnackBar();
|
||||||
|
sm.showSnackBar(getSnackBar('Response cleared'));
|
||||||
|
},
|
||||||
),
|
),
|
||||||
const Expanded(
|
const Expanded(
|
||||||
child: ResponseTabs(),
|
child: ResponseTabs(),
|
||||||
|
@ -66,39 +66,68 @@ class SettingsPage extends ConsumerWidget {
|
|||||||
title: const Text('Default URI Scheme'),
|
title: const Text('Default URI Scheme'),
|
||||||
subtitle: Text(
|
subtitle: Text(
|
||||||
'$kDefaultUri → ${settings.defaultUriScheme}://$kDefaultUri'),
|
'$kDefaultUri → ${settings.defaultUriScheme}://$kDefaultUri'),
|
||||||
trailing: DropdownMenu(
|
trailing: Container(
|
||||||
onSelected: (value) {
|
decoration: BoxDecoration(
|
||||||
ref
|
border: Border.all(
|
||||||
.read(settingsProvider.notifier)
|
color: Theme.of(context).colorScheme.onSurface,
|
||||||
.update(defaultUriScheme: value);
|
),
|
||||||
},
|
borderRadius: kBorderRadius8,
|
||||||
initialSelection: settings.defaultUriScheme,
|
),
|
||||||
dropdownMenuEntries: kSupportedUriSchemes
|
child: DropdownButtonHideUnderline(
|
||||||
.map<DropdownMenuEntry<String>>((value) {
|
child: DropdownButton<String>(
|
||||||
return DropdownMenuEntry<String>(
|
borderRadius: kBorderRadius8,
|
||||||
value: value,
|
onChanged: (value) {
|
||||||
label: value,
|
ref
|
||||||
);
|
.read(settingsProvider.notifier)
|
||||||
}).toList()),
|
.update(defaultUriScheme: value);
|
||||||
|
},
|
||||||
|
value: settings.defaultUriScheme,
|
||||||
|
items: kSupportedUriSchemes
|
||||||
|
.map<DropdownMenuItem<String>>((String value) {
|
||||||
|
return DropdownMenuItem<String>(
|
||||||
|
value: value,
|
||||||
|
child: Padding(
|
||||||
|
padding: kP10,
|
||||||
|
child: Text(value),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
contentPadding: kPb10,
|
contentPadding: kPb10,
|
||||||
hoverColor: kColorTransparent,
|
hoverColor: kColorTransparent,
|
||||||
title: const Text('Default Code Generator'),
|
title: const Text('Default Code Generator'),
|
||||||
trailing: DropdownMenu(
|
trailing: Container(
|
||||||
onSelected: (value) {
|
decoration: BoxDecoration(
|
||||||
ref
|
border: Border.all(
|
||||||
.read(settingsProvider.notifier)
|
color: Theme.of(context).colorScheme.onSurface,
|
||||||
.update(defaultCodeGenLang: value);
|
),
|
||||||
},
|
borderRadius: kBorderRadius8,
|
||||||
initialSelection: settings.defaultCodeGenLang,
|
),
|
||||||
dropdownMenuEntries: CodegenLanguage.values
|
child: DropdownButtonHideUnderline(
|
||||||
.map<DropdownMenuEntry<CodegenLanguage>>((value) {
|
child: DropdownButton<CodegenLanguage>(
|
||||||
return DropdownMenuEntry<CodegenLanguage>(
|
borderRadius: kBorderRadius8,
|
||||||
value: value,
|
value: settings.defaultCodeGenLang,
|
||||||
label: value.label,
|
onChanged: (value) {
|
||||||
);
|
ref
|
||||||
}).toList()),
|
.read(settingsProvider.notifier)
|
||||||
|
.update(defaultCodeGenLang: value);
|
||||||
|
},
|
||||||
|
items: CodegenLanguage.values.map((value) {
|
||||||
|
return DropdownMenuItem<CodegenLanguage>(
|
||||||
|
value: value,
|
||||||
|
child: Padding(
|
||||||
|
padding: kP10,
|
||||||
|
child: Text(value.label),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
CheckboxListTile(
|
CheckboxListTile(
|
||||||
contentPadding: EdgeInsets.zero,
|
contentPadding: EdgeInsets.zero,
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
import 'package:collection/collection.dart';
|
||||||
import '../models/models.dart';
|
import '../models/models.dart';
|
||||||
import '../consts.dart';
|
import '../consts.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
@ -90,18 +91,22 @@ List<NameValueModel>? mapToRows(Map<String, String>? kvMap) {
|
|||||||
return finalRows;
|
return finalRows;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Map<String, dynamic>>? rowsToFormDataMapList(
|
List<Map<String, String>>? rowsToFormDataMapList(
|
||||||
List<FormDataModel>? kvRows,
|
List<FormDataModel>? kvRows,
|
||||||
) {
|
) {
|
||||||
if (kvRows == null) {
|
if (kvRows == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
List<Map<String, dynamic>> finalMap = kvRows
|
List<Map<String, String>> finalMap = kvRows
|
||||||
.map((FormDataModel formData) => {
|
.map((FormDataModel formData) =>
|
||||||
"name": formData.name,
|
(formData.name.trim().isEmpty && formData.value.trim().isEmpty)
|
||||||
"value": formData.value,
|
? null
|
||||||
"type": formData.type.name,
|
: {
|
||||||
})
|
"name": formData.name,
|
||||||
|
"value": formData.value,
|
||||||
|
"type": formData.type.name,
|
||||||
|
})
|
||||||
|
.whereNotNull()
|
||||||
.toList();
|
.toList();
|
||||||
return finalMap;
|
return finalMap;
|
||||||
}
|
}
|
||||||
|
@ -50,7 +50,7 @@ String getShortPath(String path) {
|
|||||||
|
|
||||||
String getFilenameFromPath(String path) {
|
String getFilenameFromPath(String path) {
|
||||||
var f = p.split(path);
|
var f = p.split(path);
|
||||||
return f.last;
|
return f.lastOrNull ?? "";
|
||||||
}
|
}
|
||||||
|
|
||||||
String getTempFileName() {
|
String getTempFileName() {
|
||||||
|
@ -235,3 +235,27 @@ class SaveButton extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class ClearResponseButton extends StatelessWidget {
|
||||||
|
const ClearResponseButton({
|
||||||
|
super.key,
|
||||||
|
this.onPressed,
|
||||||
|
});
|
||||||
|
|
||||||
|
final VoidCallback? onPressed;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Tooltip(
|
||||||
|
message: 'Clear response',
|
||||||
|
child: TextButton(
|
||||||
|
style: TextButton.styleFrom(minimumSize: const Size(40, 40)),
|
||||||
|
onPressed: onPressed,
|
||||||
|
child: const Icon(
|
||||||
|
Icons.delete,
|
||||||
|
size: 20,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -55,11 +55,13 @@ class ResponsePaneHeader extends StatelessWidget {
|
|||||||
this.responseStatus,
|
this.responseStatus,
|
||||||
this.message,
|
this.message,
|
||||||
this.time,
|
this.time,
|
||||||
|
this.onClearResponse,
|
||||||
});
|
});
|
||||||
|
|
||||||
final int? responseStatus;
|
final int? responseStatus;
|
||||||
final String? message;
|
final String? message;
|
||||||
final Duration? time;
|
final Duration? time;
|
||||||
|
final VoidCallback? onClearResponse;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -116,6 +118,10 @@ class ResponsePaneHeader extends StatelessWidget {
|
|||||||
color: Theme.of(context).colorScheme.secondary,
|
color: Theme.of(context).colorScheme.secondary,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
kHSpacer10,
|
||||||
|
ClearResponseButton(
|
||||||
|
onPressed: onClearResponse,
|
||||||
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -186,4 +186,18 @@ void main() {
|
|||||||
expect(find.byIcon(Icons.save), findsOneWidget);
|
expect(find.byIcon(Icons.save), findsOneWidget);
|
||||||
expect(find.text("Save"), findsOneWidget);
|
expect(find.text("Save"), findsOneWidget);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('Testing for ClearResponseButton', (tester) async {
|
||||||
|
await tester.pumpWidget(
|
||||||
|
MaterialApp(
|
||||||
|
title: 'ClearResponseButton',
|
||||||
|
theme: kThemeDataLight,
|
||||||
|
home: const Scaffold(
|
||||||
|
body: ClearResponseButton(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(find.byIcon(Icons.delete), findsOneWidget);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user