mirror of
https://github.com/foss42/apidash.git
synced 2025-06-17 11:54:51 +08:00
feat: Multi Part Request Feature Added
This commit is contained in:
lib
@ -301,12 +301,12 @@ const kContentTypeMap = {
|
|||||||
ContentType.formdata: "multipart/form-data",
|
ContentType.formdata: "multipart/form-data",
|
||||||
};
|
};
|
||||||
const kFormDataTypeMap = {
|
const kFormDataTypeMap = {
|
||||||
FormDataType.file: "File",
|
FormDataType.file: "file",
|
||||||
FormDataType.text: "Text",
|
FormDataType.text: "text",
|
||||||
};
|
};
|
||||||
const kMapFormDataType = {
|
const kMapFormDataType = {
|
||||||
"File": FormDataType.file,
|
"file": FormDataType.file,
|
||||||
"Text": FormDataType.text,
|
"text": FormDataType.text,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum ResponseBodyView { preview, code, raw, none }
|
enum ResponseBodyView { preview, code, raw, none }
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import 'package:apidash/models/form_data_model.dart';
|
import 'package:apidash/models/form_data_model.dart';
|
||||||
|
import 'package:apidash/services/http_service.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
|
||||||
import '../consts.dart';
|
import '../consts.dart';
|
||||||
import '../models/models.dart';
|
import '../models/models.dart';
|
||||||
@ -158,10 +160,20 @@ class CollectionStateNotifier
|
|||||||
ref.read(codePaneVisibleStateProvider.notifier).state = false;
|
ref.read(codePaneVisibleStateProvider.notifier).state = false;
|
||||||
final defaultUriScheme =
|
final defaultUriScheme =
|
||||||
ref.read(settingsProvider.select((value) => value.defaultUriScheme));
|
ref.read(settingsProvider.select((value) => value.defaultUriScheme));
|
||||||
|
(http.Response?, Duration?, String?)? responseRec;
|
||||||
RequestModel requestModel = state![id]!;
|
RequestModel requestModel = state![id]!;
|
||||||
var responseRec =
|
if (requestModel.formDataList != null &&
|
||||||
await request(requestModel, defaultUriScheme: defaultUriScheme);
|
requestModel.formDataList!.isNotEmpty) {
|
||||||
|
responseRec = await multiPartRequest(
|
||||||
|
requestModel,
|
||||||
|
defaultUriScheme: defaultUriScheme,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
responseRec = await request(
|
||||||
|
requestModel,
|
||||||
|
defaultUriScheme: defaultUriScheme,
|
||||||
|
);
|
||||||
|
}
|
||||||
late final RequestModel newRequestModel;
|
late final RequestModel newRequestModel;
|
||||||
if (responseRec.$1 == null) {
|
if (responseRec.$1 == null) {
|
||||||
newRequestModel = requestModel.copyWith(
|
newRequestModel = requestModel.copyWith(
|
||||||
@ -180,7 +192,6 @@ class CollectionStateNotifier
|
|||||||
responseModel: responseModel,
|
responseModel: responseModel,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
//print(newRequestModel);
|
|
||||||
ref.read(sentRequestIdStateProvider.notifier).state = null;
|
ref.read(sentRequestIdStateProvider.notifier).state = null;
|
||||||
var map = {...state!};
|
var map = {...state!};
|
||||||
map[id] = newRequestModel;
|
map[id] = newRequestModel;
|
||||||
|
@ -1,13 +1,9 @@
|
|||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:apidash/consts.dart';
|
import 'package:apidash/consts.dart';
|
||||||
import 'package:apidash/models/form_data_model.dart';
|
|
||||||
import 'package:apidash/providers/providers.dart';
|
import 'package:apidash/providers/providers.dart';
|
||||||
import 'package:apidash/utils/extensions/file_extension.dart';
|
import 'package:apidash/widgets/form_data_widget.dart';
|
||||||
import 'package:apidash/widgets/form_data_field.dart';
|
|
||||||
import 'package:apidash/widgets/widgets.dart';
|
import 'package:apidash/widgets/widgets.dart';
|
||||||
import 'package:davi/davi.dart';
|
|
||||||
import 'package:file_picker/file_picker.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
@ -19,15 +15,12 @@ class EditRequestBody extends ConsumerStatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _EditRequestBodyState extends ConsumerState<EditRequestBody> {
|
class _EditRequestBodyState extends ConsumerState<EditRequestBody> {
|
||||||
List<FormDataModel> rows = [];
|
|
||||||
final random = Random.secure();
|
final random = Random.secure();
|
||||||
late int seed;
|
late int seed;
|
||||||
late FilePicker filePicker;
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
seed = random.nextInt(kRandMax);
|
seed = random.nextInt(kRandMax);
|
||||||
filePicker = FilePicker.platform;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -40,138 +33,6 @@ class _EditRequestBodyState extends ConsumerState<EditRequestBody> {
|
|||||||
.watch(collectionStateNotifierProvider)![activeId]
|
.watch(collectionStateNotifierProvider)![activeId]
|
||||||
?.requestBodyContentType) ??
|
?.requestBodyContentType) ??
|
||||||
ContentType.values.first;
|
ContentType.values.first;
|
||||||
DaviModel<FormDataModel> model = DaviModel<FormDataModel>(
|
|
||||||
rows: rows,
|
|
||||||
columns: [
|
|
||||||
DaviColumn(
|
|
||||||
name: 'Key',
|
|
||||||
grow: 1,
|
|
||||||
cellBuilder: (_, row) {
|
|
||||||
int idx = row.index;
|
|
||||||
return SizedBox(
|
|
||||||
child: FormDataField(
|
|
||||||
keyId: "$activeId-$idx-form-v-$seed",
|
|
||||||
initialValue: rows[idx].value,
|
|
||||||
hintText: " Key",
|
|
||||||
onChanged: (value) {
|
|
||||||
rows[idx] = rows[idx].copyWith(
|
|
||||||
name: value,
|
|
||||||
);
|
|
||||||
_onFieldChange(activeId);
|
|
||||||
},
|
|
||||||
colorScheme: Theme.of(context).colorScheme,
|
|
||||||
formDataType: rows[idx].type,
|
|
||||||
onFormDataTypeChanged: (value) {
|
|
||||||
rows[idx] = rows[idx].copyWith(
|
|
||||||
type: value ?? FormDataType.text,
|
|
||||||
);
|
|
||||||
rows[idx] = rows[idx].copyWith(value: "");
|
|
||||||
_onFieldChange(activeId);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
sortable: false,
|
|
||||||
),
|
|
||||||
DaviColumn(
|
|
||||||
width: 10,
|
|
||||||
cellBuilder: (_, row) {
|
|
||||||
return Text(
|
|
||||||
"=",
|
|
||||||
style: kCodeStyle,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
DaviColumn(
|
|
||||||
name: 'Value',
|
|
||||||
grow: 4,
|
|
||||||
cellBuilder: (_, row) {
|
|
||||||
int idx = row.index;
|
|
||||||
return rows[idx].type == FormDataType.file
|
|
||||||
? Align(
|
|
||||||
alignment: Alignment.centerLeft,
|
|
||||||
child: Padding(
|
|
||||||
padding: kPs2,
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: ElevatedButton(
|
|
||||||
onPressed: () async {
|
|
||||||
FilePickerResult? pickedResult =
|
|
||||||
await filePicker.pickFiles();
|
|
||||||
if (pickedResult != null &&
|
|
||||||
pickedResult.files.isNotEmpty) {
|
|
||||||
rows[idx] = rows[idx].copyWith(
|
|
||||||
value: pickedResult.files.first.path,
|
|
||||||
);
|
|
||||||
_onFieldChange(activeId);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: Text(
|
|
||||||
"Select File",
|
|
||||||
style: kTextStyleButton.copyWith(
|
|
||||||
fontSize: 10,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: Padding(
|
|
||||||
padding: kPs2,
|
|
||||||
child: Text(
|
|
||||||
rows[idx].type == FormDataType.file
|
|
||||||
? (rows[idx].value != null
|
|
||||||
? rows[idx].value.toString().fileName
|
|
||||||
: "")
|
|
||||||
: "",
|
|
||||||
style: kTextStyleButton,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: CellField(
|
|
||||||
keyId: "$activeId-$idx-form-v-$seed",
|
|
||||||
initialValue: rows[idx].value,
|
|
||||||
hintText: " Value",
|
|
||||||
onChanged: (value) {
|
|
||||||
rows[idx] = rows[idx].copyWith(value: value);
|
|
||||||
_onFieldChange(activeId);
|
|
||||||
},
|
|
||||||
colorScheme: Theme.of(context).colorScheme,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
sortable: false,
|
|
||||||
),
|
|
||||||
DaviColumn(
|
|
||||||
pinStatus: PinStatus.none,
|
|
||||||
width: 30,
|
|
||||||
cellBuilder: (_, row) {
|
|
||||||
return InkWell(
|
|
||||||
child: Theme.of(context).brightness == Brightness.dark
|
|
||||||
? kIconRemoveDark
|
|
||||||
: kIconRemoveLight,
|
|
||||||
onTap: () {
|
|
||||||
seed = random.nextInt(kRandMax);
|
|
||||||
if (rows.length == 1) {
|
|
||||||
setState(() {
|
|
||||||
rows = [
|
|
||||||
kFormDataEmptyModel,
|
|
||||||
];
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
rows.removeAt(row.index);
|
|
||||||
}
|
|
||||||
_onFieldChange(activeId);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
return Container(
|
return Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Theme.of(context).colorScheme.background,
|
color: Theme.of(context).colorScheme.background,
|
||||||
@ -193,43 +54,11 @@ class _EditRequestBodyState extends ConsumerState<EditRequestBody> {
|
|||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: requestBodyStateWatcher == ContentType.formdata
|
child: requestBodyStateWatcher == ContentType.formdata
|
||||||
? Stack(
|
? FormDataWidget(
|
||||||
children: [
|
seed: seed,
|
||||||
Container(
|
onFormDataRemove: () {
|
||||||
decoration: BoxDecoration(
|
seed = random.nextInt(kRandMax);
|
||||||
color: Theme.of(context).colorScheme.background,
|
|
||||||
borderRadius: kBorderRadius12,
|
|
||||||
),
|
|
||||||
margin: kP10,
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: DaviTheme(
|
|
||||||
data: kTableThemeData,
|
|
||||||
child: Davi<FormDataModel>(model),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Align(
|
|
||||||
alignment: Alignment.bottomCenter,
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.only(bottom: 30),
|
|
||||||
child: ElevatedButton.icon(
|
|
||||||
onPressed: () {
|
|
||||||
rows.add(kFormDataEmptyModel);
|
|
||||||
_onFieldChange(activeId);
|
|
||||||
},
|
},
|
||||||
icon: const Icon(Icons.add),
|
|
||||||
label: const Text(
|
|
||||||
"Add Form Data",
|
|
||||||
style: kTextStyleButton,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
: TextFieldEditor(
|
: TextFieldEditor(
|
||||||
key: Key("$activeId-body"),
|
key: Key("$activeId-body"),
|
||||||
@ -246,13 +75,6 @@ class _EditRequestBodyState extends ConsumerState<EditRequestBody> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onFieldChange(String activeId) {
|
|
||||||
ref.read(collectionStateNotifierProvider.notifier).update(
|
|
||||||
activeId,
|
|
||||||
formDataList: rows,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class DropdownButtonBodyContentType extends ConsumerStatefulWidget {
|
class DropdownButtonBodyContentType extends ConsumerStatefulWidget {
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'package:http/http.dart' as http;
|
|
||||||
import 'package:apidash/utils/utils.dart';
|
|
||||||
import 'package:apidash/models/models.dart';
|
|
||||||
import 'package:apidash/consts.dart';
|
import 'package:apidash/consts.dart';
|
||||||
|
import 'package:apidash/models/form_data_model.dart';
|
||||||
|
import 'package:apidash/models/models.dart';
|
||||||
|
import 'package:apidash/utils/utils.dart';
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
|
||||||
Future<(http.Response?, Duration?, String?)> request(
|
Future<(http.Response?, Duration?, String?)> request(
|
||||||
RequestModel requestModel, {
|
RequestModel requestModel, {
|
||||||
@ -63,3 +65,59 @@ Future<(http.Response?, Duration?, String?)> request(
|
|||||||
return (null, null, uriRec.$2);
|
return (null, null, uriRec.$2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<(http.Response?, Duration?, String?)> multiPartRequest(
|
||||||
|
RequestModel requestModel, {
|
||||||
|
String defaultUriScheme = kDefaultUriScheme,
|
||||||
|
}) async {
|
||||||
|
(Uri?, String?) uriRec = getValidRequestUri(
|
||||||
|
requestModel.url,
|
||||||
|
requestModel.requestParams,
|
||||||
|
defaultUriScheme: defaultUriScheme,
|
||||||
|
);
|
||||||
|
if (uriRec.$1 != null) {
|
||||||
|
Uri requestUrl = uriRec.$1!;
|
||||||
|
Map<String, String> headers = requestModel.headersMap;
|
||||||
|
try {
|
||||||
|
var requestBody = requestModel.requestBody;
|
||||||
|
if (kMethodsWithBody.contains(requestModel.method) &&
|
||||||
|
requestBody != null) {
|
||||||
|
var contentLength = utf8.encode(requestBody).length;
|
||||||
|
if (contentLength > 0) {
|
||||||
|
headers[HttpHeaders.contentLengthHeader] = contentLength.toString();
|
||||||
|
headers[HttpHeaders.contentTypeHeader] =
|
||||||
|
kContentTypeMap[requestModel.requestBodyContentType] ?? "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Stopwatch stopwatch = Stopwatch()..start();
|
||||||
|
|
||||||
|
var request = http.MultipartRequest(
|
||||||
|
requestModel.method.name.toUpperCase(),
|
||||||
|
requestUrl,
|
||||||
|
);
|
||||||
|
for (FormDataModel formData in (requestModel.formDataList ?? [])) {
|
||||||
|
if (formData.type == FormDataType.text) {
|
||||||
|
request.fields.addAll({formData.name: formData.value});
|
||||||
|
} else {
|
||||||
|
request.files.add(
|
||||||
|
await http.MultipartFile.fromPath(
|
||||||
|
formData.name,
|
||||||
|
formData.value,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
http.StreamedResponse response = await request.send();
|
||||||
|
|
||||||
|
stopwatch.stop();
|
||||||
|
http.Response convertedHttpResponse =
|
||||||
|
await convertStreamedResponse(response);
|
||||||
|
return (convertedHttpResponse, stopwatch.elapsed, null);
|
||||||
|
} catch (e) {
|
||||||
|
return (null, null, e.toString());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return (null, null, uriRec.$2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -2,6 +2,7 @@ import 'dart:convert';
|
|||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
|
||||||
import 'package:apidash/models/form_data_model.dart';
|
import 'package:apidash/models/form_data_model.dart';
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
|
||||||
import '../consts.dart';
|
import '../consts.dart';
|
||||||
import '../models/models.dart';
|
import '../models/models.dart';
|
||||||
@ -142,3 +143,20 @@ Uint8List jsonMapToBytes(Map<String, dynamic>? map) {
|
|||||||
return bytes;
|
return bytes;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<http.Response> convertStreamedResponse(
|
||||||
|
http.StreamedResponse streamedResponse,
|
||||||
|
) async {
|
||||||
|
Uint8List bodyBytes = await streamedResponse.stream.toBytes();
|
||||||
|
|
||||||
|
http.Response response = http.Response.bytes(
|
||||||
|
bodyBytes,
|
||||||
|
streamedResponse.statusCode,
|
||||||
|
headers: streamedResponse.headers,
|
||||||
|
persistentConnection: streamedResponse.persistentConnection,
|
||||||
|
reasonPhrase: streamedResponse.reasonPhrase,
|
||||||
|
request: streamedResponse.request,
|
||||||
|
);
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
212
lib/widgets/form_data_widget.dart
Normal file
212
lib/widgets/form_data_widget.dart
Normal file
@ -0,0 +1,212 @@
|
|||||||
|
import 'package:apidash/consts.dart';
|
||||||
|
import 'package:apidash/models/form_data_model.dart';
|
||||||
|
import 'package:apidash/providers/collection_providers.dart';
|
||||||
|
import 'package:apidash/widgets/form_data_field.dart';
|
||||||
|
import 'package:apidash/widgets/textfields.dart';
|
||||||
|
import 'package:davi/davi.dart';
|
||||||
|
import 'package:file_picker/file_picker.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
|
class FormDataWidget extends ConsumerStatefulWidget {
|
||||||
|
const FormDataWidget({
|
||||||
|
super.key,
|
||||||
|
required this.seed,
|
||||||
|
required this.onFormDataRemove,
|
||||||
|
});
|
||||||
|
final int seed;
|
||||||
|
final Function onFormDataRemove;
|
||||||
|
@override
|
||||||
|
ConsumerState<FormDataWidget> createState() => _FormDataBodyState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _FormDataBodyState extends ConsumerState<FormDataWidget> {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final activeId = ref.watch(activeIdStateProvider);
|
||||||
|
final requestModel = ref
|
||||||
|
.read(collectionStateNotifierProvider.notifier)
|
||||||
|
.getRequestModel(activeId!);
|
||||||
|
List<FormDataModel> rows = requestModel?.formDataList ?? [];
|
||||||
|
DaviModel<FormDataModel> model = DaviModel<FormDataModel>(
|
||||||
|
rows: rows,
|
||||||
|
columns: [
|
||||||
|
DaviColumn(
|
||||||
|
name: 'Key',
|
||||||
|
grow: 1,
|
||||||
|
cellBuilder: (_, row) {
|
||||||
|
int idx = row.index;
|
||||||
|
return SizedBox(
|
||||||
|
child: FormDataField(
|
||||||
|
keyId: "$activeId-$idx-form-v-${widget.seed}",
|
||||||
|
initialValue: rows[idx].name,
|
||||||
|
hintText: " Key",
|
||||||
|
onChanged: (value) {
|
||||||
|
rows[idx] = rows[idx].copyWith(
|
||||||
|
name: value,
|
||||||
|
);
|
||||||
|
_onFieldChange(activeId);
|
||||||
|
},
|
||||||
|
colorScheme: Theme.of(context).colorScheme,
|
||||||
|
formDataType: rows[idx].type,
|
||||||
|
onFormDataTypeChanged: (value) {
|
||||||
|
rows[idx] = rows[idx].copyWith(
|
||||||
|
type: value ?? FormDataType.text,
|
||||||
|
);
|
||||||
|
rows[idx] = rows[idx].copyWith(value: "");
|
||||||
|
_onFieldChange(activeId);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
sortable: false,
|
||||||
|
),
|
||||||
|
DaviColumn(
|
||||||
|
width: 10,
|
||||||
|
cellBuilder: (_, row) {
|
||||||
|
return Text(
|
||||||
|
"=",
|
||||||
|
style: kCodeStyle,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
DaviColumn(
|
||||||
|
name: 'Value',
|
||||||
|
grow: 4,
|
||||||
|
cellBuilder: (_, row) {
|
||||||
|
int idx = row.index;
|
||||||
|
return rows[idx].type == FormDataType.file
|
||||||
|
? Align(
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
child: Padding(
|
||||||
|
padding: kPs8,
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: ElevatedButtonTheme(
|
||||||
|
data: const ElevatedButtonThemeData(),
|
||||||
|
child: ElevatedButton.icon(
|
||||||
|
onPressed: () async {
|
||||||
|
FilePickerResult? pickedResult =
|
||||||
|
await FilePicker.platform.pickFiles();
|
||||||
|
if (pickedResult != null &&
|
||||||
|
pickedResult.files.isNotEmpty) {
|
||||||
|
rows[idx] = rows[idx].copyWith(
|
||||||
|
value: pickedResult.files.first.path,
|
||||||
|
);
|
||||||
|
_onFieldChange(activeId);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.snippet_folder_rounded,
|
||||||
|
size: 18,
|
||||||
|
),
|
||||||
|
label: Text(
|
||||||
|
rows[idx].type == FormDataType.file
|
||||||
|
? (rows[idx].value != null
|
||||||
|
? rows[idx].value.toString()
|
||||||
|
: "Select File")
|
||||||
|
: "Select File",
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: kTextStyleButton.copyWith(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: CellField(
|
||||||
|
keyId: "$activeId-$idx-form-v-${widget.seed}",
|
||||||
|
initialValue: rows[idx].value,
|
||||||
|
hintText: " Value",
|
||||||
|
onChanged: (value) {
|
||||||
|
rows[idx] = rows[idx].copyWith(value: value);
|
||||||
|
_onFieldChange(activeId);
|
||||||
|
},
|
||||||
|
colorScheme: Theme.of(context).colorScheme,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
sortable: false,
|
||||||
|
),
|
||||||
|
DaviColumn(
|
||||||
|
pinStatus: PinStatus.none,
|
||||||
|
width: 30,
|
||||||
|
cellBuilder: (_, row) {
|
||||||
|
return InkWell(
|
||||||
|
child: Theme.of(context).brightness == Brightness.dark
|
||||||
|
? kIconRemoveDark
|
||||||
|
: kIconRemoveLight,
|
||||||
|
onTap: () {
|
||||||
|
widget.onFormDataRemove();
|
||||||
|
if (rows.length == 1) {
|
||||||
|
setState(() {
|
||||||
|
rows = [
|
||||||
|
kFormDataEmptyModel,
|
||||||
|
];
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
rows.removeAt(row.index);
|
||||||
|
}
|
||||||
|
_onFieldChange(activeId);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
return Stack(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Theme.of(context).colorScheme.background,
|
||||||
|
borderRadius: kBorderRadius12,
|
||||||
|
),
|
||||||
|
margin: kP10,
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: DaviTheme(
|
||||||
|
data: kTableThemeData,
|
||||||
|
child: Davi<FormDataModel>(model),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.bottomCenter,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(bottom: 30),
|
||||||
|
child: ElevatedButton.icon(
|
||||||
|
onPressed: () {
|
||||||
|
rows.add(kFormDataEmptyModel);
|
||||||
|
_onFieldChange(activeId);
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.add),
|
||||||
|
label: const Text(
|
||||||
|
"Add Form Data",
|
||||||
|
style: kTextStyleButton,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onFieldChange(String activeId) {
|
||||||
|
List<FormDataModel> formDataList =
|
||||||
|
ref.read(collectionStateNotifierProvider)?[activeId]?.formDataList ??
|
||||||
|
[];
|
||||||
|
ref.read(collectionStateNotifierProvider.notifier).update(
|
||||||
|
activeId,
|
||||||
|
formDataList: formDataList,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user