mirror of
https://github.com/foss42/apidash.git
synced 2025-06-20 14:09:28 +08:00
refactor
This commit is contained in:
@ -2,3 +2,9 @@ include: package:flutter_lints/flutter.yaml
|
||||
|
||||
# Additional information about this file can be found at
|
||||
# https://dart.dev/guides/language/analysis-options
|
||||
analyzer:
|
||||
errors:
|
||||
invalid_annotation_target: ignore
|
||||
exclude:
|
||||
- "**/*.freezed.dart"
|
||||
- "**/*.g.dart"
|
||||
|
@ -1,7 +1,13 @@
|
||||
library apidash_core;
|
||||
|
||||
/// A Calculator.
|
||||
class Calculator {
|
||||
/// Returns [value] plus 1.
|
||||
int addOne(int value) => value + 1;
|
||||
}
|
||||
export 'consts.dart';
|
||||
export 'extensions/extensions.dart';
|
||||
export 'models/models.dart';
|
||||
export 'utils/utils.dart';
|
||||
export 'services/services.dart';
|
||||
|
||||
// Export 3rd party packages
|
||||
export 'package:collection/collection.dart';
|
||||
export 'package:freezed_annotation/freezed_annotation.dart';
|
||||
export 'package:http/http.dart';
|
||||
export 'package:http_parser/http_parser.dart';
|
||||
|
70
packages/apidash_core/lib/consts.dart
Normal file
70
packages/apidash_core/lib/consts.dart
Normal file
@ -0,0 +1,70 @@
|
||||
import 'dart:convert';
|
||||
|
||||
enum HTTPVerb { get, head, post, put, patch, delete }
|
||||
|
||||
enum FormDataType { text, file }
|
||||
|
||||
const kSupportedUriSchemes = ["https", "http"];
|
||||
const kDefaultUriScheme = "https";
|
||||
const kMethodsWithBody = [
|
||||
HTTPVerb.post,
|
||||
HTTPVerb.put,
|
||||
HTTPVerb.patch,
|
||||
HTTPVerb.delete,
|
||||
];
|
||||
|
||||
const kDefaultHttpMethod = HTTPVerb.get;
|
||||
const kDefaultContentType = ContentType.json;
|
||||
|
||||
const kTypeApplication = 'application';
|
||||
// application
|
||||
const kSubTypeJson = 'json';
|
||||
const kSubTypeOctetStream = 'octet-stream';
|
||||
const kSubTypePdf = 'pdf';
|
||||
const kSubTypeSql = 'sql';
|
||||
const kSubTypeXml = 'xml';
|
||||
const kSubTypeYaml = 'yaml';
|
||||
const kSubTypeXYaml = 'x-yaml';
|
||||
const kSubTypeYml = 'x-yml';
|
||||
const kSubTypeXWwwFormUrlencoded = 'x-www-form-urlencoded';
|
||||
|
||||
const kTypeText = 'text';
|
||||
// text
|
||||
const kSubTypeCss = 'css';
|
||||
const kSubTypeCsv = 'csv';
|
||||
const kSubTypeHtml = 'html';
|
||||
const kSubTypeJavascript = 'javascript';
|
||||
const kSubTypeMarkdown = 'markdown';
|
||||
const kSubTypePlain = 'plain';
|
||||
const kSubTypeTextXml = 'xml';
|
||||
const kSubTypeTextYaml = 'yaml';
|
||||
const kSubTypeTextYml = 'yml';
|
||||
|
||||
const kTypeImage = 'image';
|
||||
//image
|
||||
const kSubTypeSvg = 'svg+xml';
|
||||
|
||||
const kTypeAudio = 'audio';
|
||||
const kTypeVideo = 'video';
|
||||
|
||||
const kTypeMultipart = "multipart";
|
||||
const kSubTypeFormData = "form-data";
|
||||
|
||||
const kSubTypeDefaultViewOptions = 'all';
|
||||
|
||||
enum ContentType {
|
||||
json("$kTypeApplication/$kSubTypeJson"),
|
||||
text("$kTypeText/$kSubTypePlain"),
|
||||
formdata("$kTypeMultipart/$kSubTypeFormData");
|
||||
|
||||
const ContentType(this.header);
|
||||
final String header;
|
||||
}
|
||||
|
||||
const JsonEncoder kJsonEncoder = JsonEncoder.withIndent(' ');
|
||||
const JsonDecoder kJsonDecoder = JsonDecoder();
|
||||
const LineSplitter kSplitter = LineSplitter();
|
||||
|
||||
const kCodeCharsPerLineLimit = 200;
|
||||
|
||||
const kHeaderContentType = "Content-Type";
|
1
packages/apidash_core/lib/extensions/extensions.dart
Normal file
1
packages/apidash_core/lib/extensions/extensions.dart
Normal file
@ -0,0 +1 @@
|
||||
export 'string_extensions.dart';
|
21
packages/apidash_core/lib/extensions/string_extensions.dart
Normal file
21
packages/apidash_core/lib/extensions/string_extensions.dart
Normal file
@ -0,0 +1,21 @@
|
||||
extension StringExtension on String {
|
||||
String capitalize() {
|
||||
if (isEmpty) {
|
||||
return this;
|
||||
}
|
||||
if (length == 1) {
|
||||
return toUpperCase();
|
||||
}
|
||||
return "${this[0].toUpperCase()}${substring(1).toLowerCase()}";
|
||||
}
|
||||
|
||||
String clip(int limit) {
|
||||
if (limit < 0) {
|
||||
return '...';
|
||||
}
|
||||
if (length <= limit) {
|
||||
return this;
|
||||
}
|
||||
return "${substring(0, limit)}...";
|
||||
}
|
||||
}
|
23
packages/apidash_core/lib/models/form_data_model.dart
Normal file
23
packages/apidash_core/lib/models/form_data_model.dart
Normal file
@ -0,0 +1,23 @@
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import '../consts.dart';
|
||||
|
||||
part 'form_data_model.freezed.dart';
|
||||
part 'form_data_model.g.dart';
|
||||
|
||||
@freezed
|
||||
class FormDataModel with _$FormDataModel {
|
||||
const factory FormDataModel({
|
||||
required String name,
|
||||
required String value,
|
||||
required FormDataType type,
|
||||
}) = _FormDataModel;
|
||||
|
||||
factory FormDataModel.fromJson(Map<String, Object?> json) =>
|
||||
_$FormDataModelFromJson(json);
|
||||
}
|
||||
|
||||
const kFormDataEmptyModel = FormDataModel(
|
||||
name: "",
|
||||
value: "",
|
||||
type: FormDataType.text,
|
||||
);
|
187
packages/apidash_core/lib/models/form_data_model.freezed.dart
Normal file
187
packages/apidash_core/lib/models/form_data_model.freezed.dart
Normal file
@ -0,0 +1,187 @@
|
||||
// coverage:ignore-file
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||
|
||||
part of 'form_data_model.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
T _$identity<T>(T value) => value;
|
||||
|
||||
final _privateConstructorUsedError = UnsupportedError(
|
||||
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
|
||||
|
||||
FormDataModel _$FormDataModelFromJson(Map<String, dynamic> json) {
|
||||
return _FormDataModel.fromJson(json);
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$FormDataModel {
|
||||
String get name => throw _privateConstructorUsedError;
|
||||
String get value => throw _privateConstructorUsedError;
|
||||
FormDataType get type => throw _privateConstructorUsedError;
|
||||
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
@JsonKey(ignore: true)
|
||||
$FormDataModelCopyWith<FormDataModel> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $FormDataModelCopyWith<$Res> {
|
||||
factory $FormDataModelCopyWith(
|
||||
FormDataModel value, $Res Function(FormDataModel) then) =
|
||||
_$FormDataModelCopyWithImpl<$Res, FormDataModel>;
|
||||
@useResult
|
||||
$Res call({String name, String value, FormDataType type});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$FormDataModelCopyWithImpl<$Res, $Val extends FormDataModel>
|
||||
implements $FormDataModelCopyWith<$Res> {
|
||||
_$FormDataModelCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? name = null,
|
||||
Object? value = null,
|
||||
Object? type = null,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
name: null == name
|
||||
? _value.name
|
||||
: name // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
value: null == value
|
||||
? _value.value
|
||||
: value // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
type: null == type
|
||||
? _value.type
|
||||
: type // ignore: cast_nullable_to_non_nullable
|
||||
as FormDataType,
|
||||
) as $Val);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$FormDataModelImplCopyWith<$Res>
|
||||
implements $FormDataModelCopyWith<$Res> {
|
||||
factory _$$FormDataModelImplCopyWith(
|
||||
_$FormDataModelImpl value, $Res Function(_$FormDataModelImpl) then) =
|
||||
__$$FormDataModelImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call({String name, String value, FormDataType type});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$FormDataModelImplCopyWithImpl<$Res>
|
||||
extends _$FormDataModelCopyWithImpl<$Res, _$FormDataModelImpl>
|
||||
implements _$$FormDataModelImplCopyWith<$Res> {
|
||||
__$$FormDataModelImplCopyWithImpl(
|
||||
_$FormDataModelImpl _value, $Res Function(_$FormDataModelImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? name = null,
|
||||
Object? value = null,
|
||||
Object? type = null,
|
||||
}) {
|
||||
return _then(_$FormDataModelImpl(
|
||||
name: null == name
|
||||
? _value.name
|
||||
: name // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
value: null == value
|
||||
? _value.value
|
||||
: value // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
type: null == type
|
||||
? _value.type
|
||||
: type // ignore: cast_nullable_to_non_nullable
|
||||
as FormDataType,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
class _$FormDataModelImpl implements _FormDataModel {
|
||||
const _$FormDataModelImpl(
|
||||
{required this.name, required this.value, required this.type});
|
||||
|
||||
factory _$FormDataModelImpl.fromJson(Map<String, dynamic> json) =>
|
||||
_$$FormDataModelImplFromJson(json);
|
||||
|
||||
@override
|
||||
final String name;
|
||||
@override
|
||||
final String value;
|
||||
@override
|
||||
final FormDataType type;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'FormDataModel(name: $name, value: $value, type: $type)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$FormDataModelImpl &&
|
||||
(identical(other.name, name) || other.name == name) &&
|
||||
(identical(other.value, value) || other.value == value) &&
|
||||
(identical(other.type, type) || other.type == type));
|
||||
}
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType, name, value, type);
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$FormDataModelImplCopyWith<_$FormDataModelImpl> get copyWith =>
|
||||
__$$FormDataModelImplCopyWithImpl<_$FormDataModelImpl>(this, _$identity);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$$FormDataModelImplToJson(
|
||||
this,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _FormDataModel implements FormDataModel {
|
||||
const factory _FormDataModel(
|
||||
{required final String name,
|
||||
required final String value,
|
||||
required final FormDataType type}) = _$FormDataModelImpl;
|
||||
|
||||
factory _FormDataModel.fromJson(Map<String, dynamic> json) =
|
||||
_$FormDataModelImpl.fromJson;
|
||||
|
||||
@override
|
||||
String get name;
|
||||
@override
|
||||
String get value;
|
||||
@override
|
||||
FormDataType get type;
|
||||
@override
|
||||
@JsonKey(ignore: true)
|
||||
_$$FormDataModelImplCopyWith<_$FormDataModelImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
26
packages/apidash_core/lib/models/form_data_model.g.dart
Normal file
26
packages/apidash_core/lib/models/form_data_model.g.dart
Normal file
@ -0,0 +1,26 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'form_data_model.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
_$FormDataModelImpl _$$FormDataModelImplFromJson(Map<String, dynamic> json) =>
|
||||
_$FormDataModelImpl(
|
||||
name: json['name'] as String,
|
||||
value: json['value'] as String,
|
||||
type: $enumDecode(_$FormDataTypeEnumMap, json['type']),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$FormDataModelImplToJson(_$FormDataModelImpl instance) =>
|
||||
<String, dynamic>{
|
||||
'name': instance.name,
|
||||
'value': instance.value,
|
||||
'type': _$FormDataTypeEnumMap[instance.type]!,
|
||||
};
|
||||
|
||||
const _$FormDataTypeEnumMap = {
|
||||
FormDataType.text: 'text',
|
||||
FormDataType.file: 'file',
|
||||
};
|
88
packages/apidash_core/lib/models/http_response_model.dart
Normal file
88
packages/apidash_core/lib/models/http_response_model.dart
Normal file
@ -0,0 +1,88 @@
|
||||
import 'dart:io';
|
||||
import 'dart:convert';
|
||||
import 'dart:typed_data';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:collection/collection.dart' show mergeMaps;
|
||||
import 'package:http/http.dart';
|
||||
import 'package:http_parser/http_parser.dart';
|
||||
import '../utils/utils.dart';
|
||||
import '../consts.dart';
|
||||
|
||||
part 'http_response_model.freezed.dart';
|
||||
|
||||
part 'http_response_model.g.dart';
|
||||
|
||||
class Uint8ListConverter implements JsonConverter<Uint8List?, List<int>?> {
|
||||
const Uint8ListConverter();
|
||||
|
||||
@override
|
||||
Uint8List? fromJson(List<int>? json) {
|
||||
return json == null ? null : Uint8List.fromList(json);
|
||||
}
|
||||
|
||||
@override
|
||||
List<int>? toJson(Uint8List? object) {
|
||||
return object?.toList();
|
||||
}
|
||||
}
|
||||
|
||||
class DurationConverter implements JsonConverter<Duration?, int?> {
|
||||
const DurationConverter();
|
||||
|
||||
@override
|
||||
Duration? fromJson(int? json) {
|
||||
return json == null ? null : Duration(microseconds: json);
|
||||
}
|
||||
|
||||
@override
|
||||
int? toJson(Duration? object) {
|
||||
return object?.inMicroseconds;
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class HttpResponseModel with _$HttpResponseModel {
|
||||
const HttpResponseModel._();
|
||||
|
||||
@JsonSerializable(
|
||||
explicitToJson: true,
|
||||
anyMap: true,
|
||||
)
|
||||
const factory HttpResponseModel({
|
||||
int? statusCode,
|
||||
Map<String, String>? headers,
|
||||
Map<String, String>? requestHeaders,
|
||||
String? body,
|
||||
String? formattedBody,
|
||||
@Uint8ListConverter() Uint8List? bodyBytes,
|
||||
@DurationConverter() Duration? time,
|
||||
}) = _HttpResponseModel;
|
||||
|
||||
factory HttpResponseModel.fromJson(Map<String, Object?> json) =>
|
||||
_$HttpResponseModelFromJson(json);
|
||||
|
||||
String? get contentType => getContentTypeFromHeaders(headers);
|
||||
MediaType? get mediaType => getMediaTypeFromHeaders(headers);
|
||||
|
||||
HttpResponseModel fromResponse({
|
||||
required Response response,
|
||||
Duration? time,
|
||||
}) {
|
||||
final responseHeaders = mergeMaps(
|
||||
{HttpHeaders.contentLengthHeader: response.contentLength.toString()},
|
||||
response.headers);
|
||||
MediaType? mediaType = getMediaTypeFromHeaders(responseHeaders);
|
||||
final body = (mediaType?.subtype == kSubTypeJson)
|
||||
? utf8.decode(response.bodyBytes)
|
||||
: response.body;
|
||||
return HttpResponseModel(
|
||||
statusCode: response.statusCode,
|
||||
headers: responseHeaders,
|
||||
requestHeaders: response.request?.headers,
|
||||
body: body,
|
||||
formattedBody: formatBody(body, mediaType),
|
||||
bodyBytes: response.bodyBytes,
|
||||
time: time,
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,314 @@
|
||||
// coverage:ignore-file
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||
|
||||
part of 'http_response_model.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
T _$identity<T>(T value) => value;
|
||||
|
||||
final _privateConstructorUsedError = UnsupportedError(
|
||||
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
|
||||
|
||||
HttpResponseModel _$HttpResponseModelFromJson(Map<String, dynamic> json) {
|
||||
return _HttpResponseModel.fromJson(json);
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$HttpResponseModel {
|
||||
int? get statusCode => throw _privateConstructorUsedError;
|
||||
Map<String, String>? get headers => throw _privateConstructorUsedError;
|
||||
Map<String, String>? get requestHeaders => throw _privateConstructorUsedError;
|
||||
String? get body => throw _privateConstructorUsedError;
|
||||
String? get formattedBody => throw _privateConstructorUsedError;
|
||||
@Uint8ListConverter()
|
||||
Uint8List? get bodyBytes => throw _privateConstructorUsedError;
|
||||
@DurationConverter()
|
||||
Duration? get time => throw _privateConstructorUsedError;
|
||||
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
@JsonKey(ignore: true)
|
||||
$HttpResponseModelCopyWith<HttpResponseModel> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $HttpResponseModelCopyWith<$Res> {
|
||||
factory $HttpResponseModelCopyWith(
|
||||
HttpResponseModel value, $Res Function(HttpResponseModel) then) =
|
||||
_$HttpResponseModelCopyWithImpl<$Res, HttpResponseModel>;
|
||||
@useResult
|
||||
$Res call(
|
||||
{int? statusCode,
|
||||
Map<String, String>? headers,
|
||||
Map<String, String>? requestHeaders,
|
||||
String? body,
|
||||
String? formattedBody,
|
||||
@Uint8ListConverter() Uint8List? bodyBytes,
|
||||
@DurationConverter() Duration? time});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$HttpResponseModelCopyWithImpl<$Res, $Val extends HttpResponseModel>
|
||||
implements $HttpResponseModelCopyWith<$Res> {
|
||||
_$HttpResponseModelCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? statusCode = freezed,
|
||||
Object? headers = freezed,
|
||||
Object? requestHeaders = freezed,
|
||||
Object? body = freezed,
|
||||
Object? formattedBody = freezed,
|
||||
Object? bodyBytes = freezed,
|
||||
Object? time = freezed,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
statusCode: freezed == statusCode
|
||||
? _value.statusCode
|
||||
: statusCode // ignore: cast_nullable_to_non_nullable
|
||||
as int?,
|
||||
headers: freezed == headers
|
||||
? _value.headers
|
||||
: headers // ignore: cast_nullable_to_non_nullable
|
||||
as Map<String, String>?,
|
||||
requestHeaders: freezed == requestHeaders
|
||||
? _value.requestHeaders
|
||||
: requestHeaders // ignore: cast_nullable_to_non_nullable
|
||||
as Map<String, String>?,
|
||||
body: freezed == body
|
||||
? _value.body
|
||||
: body // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
formattedBody: freezed == formattedBody
|
||||
? _value.formattedBody
|
||||
: formattedBody // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
bodyBytes: freezed == bodyBytes
|
||||
? _value.bodyBytes
|
||||
: bodyBytes // ignore: cast_nullable_to_non_nullable
|
||||
as Uint8List?,
|
||||
time: freezed == time
|
||||
? _value.time
|
||||
: time // ignore: cast_nullable_to_non_nullable
|
||||
as Duration?,
|
||||
) as $Val);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$HttpResponseModelImplCopyWith<$Res>
|
||||
implements $HttpResponseModelCopyWith<$Res> {
|
||||
factory _$$HttpResponseModelImplCopyWith(_$HttpResponseModelImpl value,
|
||||
$Res Function(_$HttpResponseModelImpl) then) =
|
||||
__$$HttpResponseModelImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call(
|
||||
{int? statusCode,
|
||||
Map<String, String>? headers,
|
||||
Map<String, String>? requestHeaders,
|
||||
String? body,
|
||||
String? formattedBody,
|
||||
@Uint8ListConverter() Uint8List? bodyBytes,
|
||||
@DurationConverter() Duration? time});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$HttpResponseModelImplCopyWithImpl<$Res>
|
||||
extends _$HttpResponseModelCopyWithImpl<$Res, _$HttpResponseModelImpl>
|
||||
implements _$$HttpResponseModelImplCopyWith<$Res> {
|
||||
__$$HttpResponseModelImplCopyWithImpl(_$HttpResponseModelImpl _value,
|
||||
$Res Function(_$HttpResponseModelImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? statusCode = freezed,
|
||||
Object? headers = freezed,
|
||||
Object? requestHeaders = freezed,
|
||||
Object? body = freezed,
|
||||
Object? formattedBody = freezed,
|
||||
Object? bodyBytes = freezed,
|
||||
Object? time = freezed,
|
||||
}) {
|
||||
return _then(_$HttpResponseModelImpl(
|
||||
statusCode: freezed == statusCode
|
||||
? _value.statusCode
|
||||
: statusCode // ignore: cast_nullable_to_non_nullable
|
||||
as int?,
|
||||
headers: freezed == headers
|
||||
? _value._headers
|
||||
: headers // ignore: cast_nullable_to_non_nullable
|
||||
as Map<String, String>?,
|
||||
requestHeaders: freezed == requestHeaders
|
||||
? _value._requestHeaders
|
||||
: requestHeaders // ignore: cast_nullable_to_non_nullable
|
||||
as Map<String, String>?,
|
||||
body: freezed == body
|
||||
? _value.body
|
||||
: body // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
formattedBody: freezed == formattedBody
|
||||
? _value.formattedBody
|
||||
: formattedBody // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
bodyBytes: freezed == bodyBytes
|
||||
? _value.bodyBytes
|
||||
: bodyBytes // ignore: cast_nullable_to_non_nullable
|
||||
as Uint8List?,
|
||||
time: freezed == time
|
||||
? _value.time
|
||||
: time // ignore: cast_nullable_to_non_nullable
|
||||
as Duration?,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
@JsonSerializable(explicitToJson: true, anyMap: true)
|
||||
class _$HttpResponseModelImpl extends _HttpResponseModel {
|
||||
const _$HttpResponseModelImpl(
|
||||
{this.statusCode,
|
||||
final Map<String, String>? headers,
|
||||
final Map<String, String>? requestHeaders,
|
||||
this.body,
|
||||
this.formattedBody,
|
||||
@Uint8ListConverter() this.bodyBytes,
|
||||
@DurationConverter() this.time})
|
||||
: _headers = headers,
|
||||
_requestHeaders = requestHeaders,
|
||||
super._();
|
||||
|
||||
factory _$HttpResponseModelImpl.fromJson(Map<String, dynamic> json) =>
|
||||
_$$HttpResponseModelImplFromJson(json);
|
||||
|
||||
@override
|
||||
final int? statusCode;
|
||||
final Map<String, String>? _headers;
|
||||
@override
|
||||
Map<String, String>? get headers {
|
||||
final value = _headers;
|
||||
if (value == null) return null;
|
||||
if (_headers is EqualUnmodifiableMapView) return _headers;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableMapView(value);
|
||||
}
|
||||
|
||||
final Map<String, String>? _requestHeaders;
|
||||
@override
|
||||
Map<String, String>? get requestHeaders {
|
||||
final value = _requestHeaders;
|
||||
if (value == null) return null;
|
||||
if (_requestHeaders is EqualUnmodifiableMapView) return _requestHeaders;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableMapView(value);
|
||||
}
|
||||
|
||||
@override
|
||||
final String? body;
|
||||
@override
|
||||
final String? formattedBody;
|
||||
@override
|
||||
@Uint8ListConverter()
|
||||
final Uint8List? bodyBytes;
|
||||
@override
|
||||
@DurationConverter()
|
||||
final Duration? time;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'HttpResponseModel(statusCode: $statusCode, headers: $headers, requestHeaders: $requestHeaders, body: $body, formattedBody: $formattedBody, bodyBytes: $bodyBytes, time: $time)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$HttpResponseModelImpl &&
|
||||
(identical(other.statusCode, statusCode) ||
|
||||
other.statusCode == statusCode) &&
|
||||
const DeepCollectionEquality().equals(other._headers, _headers) &&
|
||||
const DeepCollectionEquality()
|
||||
.equals(other._requestHeaders, _requestHeaders) &&
|
||||
(identical(other.body, body) || other.body == body) &&
|
||||
(identical(other.formattedBody, formattedBody) ||
|
||||
other.formattedBody == formattedBody) &&
|
||||
const DeepCollectionEquality().equals(other.bodyBytes, bodyBytes) &&
|
||||
(identical(other.time, time) || other.time == time));
|
||||
}
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
int get hashCode => Object.hash(
|
||||
runtimeType,
|
||||
statusCode,
|
||||
const DeepCollectionEquality().hash(_headers),
|
||||
const DeepCollectionEquality().hash(_requestHeaders),
|
||||
body,
|
||||
formattedBody,
|
||||
const DeepCollectionEquality().hash(bodyBytes),
|
||||
time);
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$HttpResponseModelImplCopyWith<_$HttpResponseModelImpl> get copyWith =>
|
||||
__$$HttpResponseModelImplCopyWithImpl<_$HttpResponseModelImpl>(
|
||||
this, _$identity);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$$HttpResponseModelImplToJson(
|
||||
this,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _HttpResponseModel extends HttpResponseModel {
|
||||
const factory _HttpResponseModel(
|
||||
{final int? statusCode,
|
||||
final Map<String, String>? headers,
|
||||
final Map<String, String>? requestHeaders,
|
||||
final String? body,
|
||||
final String? formattedBody,
|
||||
@Uint8ListConverter() final Uint8List? bodyBytes,
|
||||
@DurationConverter() final Duration? time}) = _$HttpResponseModelImpl;
|
||||
const _HttpResponseModel._() : super._();
|
||||
|
||||
factory _HttpResponseModel.fromJson(Map<String, dynamic> json) =
|
||||
_$HttpResponseModelImpl.fromJson;
|
||||
|
||||
@override
|
||||
int? get statusCode;
|
||||
@override
|
||||
Map<String, String>? get headers;
|
||||
@override
|
||||
Map<String, String>? get requestHeaders;
|
||||
@override
|
||||
String? get body;
|
||||
@override
|
||||
String? get formattedBody;
|
||||
@override
|
||||
@Uint8ListConverter()
|
||||
Uint8List? get bodyBytes;
|
||||
@override
|
||||
@DurationConverter()
|
||||
Duration? get time;
|
||||
@override
|
||||
@JsonKey(ignore: true)
|
||||
_$$HttpResponseModelImplCopyWith<_$HttpResponseModelImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
35
packages/apidash_core/lib/models/http_response_model.g.dart
Normal file
35
packages/apidash_core/lib/models/http_response_model.g.dart
Normal file
@ -0,0 +1,35 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'http_response_model.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
_$HttpResponseModelImpl _$$HttpResponseModelImplFromJson(Map json) =>
|
||||
_$HttpResponseModelImpl(
|
||||
statusCode: (json['statusCode'] as num?)?.toInt(),
|
||||
headers: (json['headers'] as Map?)?.map(
|
||||
(k, e) => MapEntry(k as String, e as String),
|
||||
),
|
||||
requestHeaders: (json['requestHeaders'] as Map?)?.map(
|
||||
(k, e) => MapEntry(k as String, e as String),
|
||||
),
|
||||
body: json['body'] as String?,
|
||||
formattedBody: json['formattedBody'] as String?,
|
||||
bodyBytes:
|
||||
const Uint8ListConverter().fromJson(json['bodyBytes'] as List<int>?),
|
||||
time: const DurationConverter().fromJson((json['time'] as num?)?.toInt()),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$HttpResponseModelImplToJson(
|
||||
_$HttpResponseModelImpl instance) =>
|
||||
<String, dynamic>{
|
||||
'statusCode': instance.statusCode,
|
||||
'headers': instance.headers,
|
||||
'requestHeaders': instance.requestHeaders,
|
||||
'body': instance.body,
|
||||
'formattedBody': instance.formattedBody,
|
||||
'bodyBytes': const Uint8ListConverter().toJson(instance.bodyBytes),
|
||||
'time': const DurationConverter().toJson(instance.time),
|
||||
};
|
@ -1,2 +1,4 @@
|
||||
export 'form_data_model.dart';
|
||||
export 'http_request_model.dart';
|
||||
export 'http_response_model.dart';
|
||||
export 'name_value_model.dart';
|
||||
|
97
packages/apidash_core/lib/services/http_service.dart
Normal file
97
packages/apidash_core/lib/services/http_service.dart
Normal file
@ -0,0 +1,97 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'package:http/http.dart' as http;
|
||||
import '../consts.dart';
|
||||
import '../models/models.dart';
|
||||
import '../utils/utils.dart';
|
||||
|
||||
typedef HttpResponse = http.Response;
|
||||
|
||||
Future<(HttpResponse?, Duration?, String?)> request(
|
||||
HttpRequestModel requestModel, {
|
||||
String defaultUriScheme = kDefaultUriScheme,
|
||||
}) async {
|
||||
(Uri?, String?) uriRec = getValidRequestUri(
|
||||
requestModel.url,
|
||||
requestModel.enabledParams,
|
||||
defaultUriScheme: defaultUriScheme,
|
||||
);
|
||||
if (uriRec.$1 != null) {
|
||||
Uri requestUrl = uriRec.$1!;
|
||||
Map<String, String> headers = requestModel.enabledHeadersMap;
|
||||
HttpResponse response;
|
||||
String? body;
|
||||
try {
|
||||
Stopwatch stopwatch = Stopwatch()..start();
|
||||
var isMultiPartRequest =
|
||||
requestModel.bodyContentType == ContentType.formdata;
|
||||
if (kMethodsWithBody.contains(requestModel.method)) {
|
||||
var requestBody = requestModel.body;
|
||||
if (requestBody != null && !isMultiPartRequest) {
|
||||
var contentLength = utf8.encode(requestBody).length;
|
||||
if (contentLength > 0) {
|
||||
body = requestBody;
|
||||
headers[HttpHeaders.contentLengthHeader] = contentLength.toString();
|
||||
if (!requestModel.hasContentTypeHeader) {
|
||||
headers[HttpHeaders.contentTypeHeader] =
|
||||
requestModel.bodyContentType.header;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isMultiPartRequest) {
|
||||
var multiPartRequest = http.MultipartRequest(
|
||||
requestModel.method.name.toUpperCase(),
|
||||
requestUrl,
|
||||
);
|
||||
multiPartRequest.headers.addAll(headers);
|
||||
for (var formData in requestModel.formDataList) {
|
||||
if (formData.type == FormDataType.text) {
|
||||
multiPartRequest.fields.addAll({formData.name: formData.value});
|
||||
} else {
|
||||
multiPartRequest.files.add(
|
||||
await http.MultipartFile.fromPath(
|
||||
formData.name,
|
||||
formData.value,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
http.StreamedResponse multiPartResponse =
|
||||
await multiPartRequest.send();
|
||||
stopwatch.stop();
|
||||
http.Response convertedMultiPartResponse =
|
||||
await convertStreamedResponse(multiPartResponse);
|
||||
return (convertedMultiPartResponse, stopwatch.elapsed, null);
|
||||
}
|
||||
}
|
||||
switch (requestModel.method) {
|
||||
case HTTPVerb.get:
|
||||
response = await http.get(requestUrl, headers: headers);
|
||||
break;
|
||||
case HTTPVerb.head:
|
||||
response = await http.head(requestUrl, headers: headers);
|
||||
break;
|
||||
case HTTPVerb.post:
|
||||
response = await http.post(requestUrl, headers: headers, body: body);
|
||||
break;
|
||||
case HTTPVerb.put:
|
||||
response = await http.put(requestUrl, headers: headers, body: body);
|
||||
break;
|
||||
case HTTPVerb.patch:
|
||||
response = await http.patch(requestUrl, headers: headers, body: body);
|
||||
break;
|
||||
case HTTPVerb.delete:
|
||||
response =
|
||||
await http.delete(requestUrl, headers: headers, body: body);
|
||||
break;
|
||||
}
|
||||
stopwatch.stop();
|
||||
return (response, stopwatch.elapsed, null);
|
||||
} catch (e) {
|
||||
return (null, null, e.toString());
|
||||
}
|
||||
} else {
|
||||
return (null, null, uriRec.$2);
|
||||
}
|
||||
}
|
1
packages/apidash_core/lib/services/services.dart
Normal file
1
packages/apidash_core/lib/services/services.dart
Normal file
@ -0,0 +1 @@
|
||||
export 'http_service.dart';
|
83
packages/apidash_core/lib/utils/http_request_utils.dart
Normal file
83
packages/apidash_core/lib/utils/http_request_utils.dart
Normal file
@ -0,0 +1,83 @@
|
||||
import 'package:collection/collection.dart';
|
||||
import '../consts.dart';
|
||||
import '../models/models.dart';
|
||||
|
||||
Map<String, String>? rowsToMap(List<NameValueModel>? kvRows,
|
||||
{bool isHeader = false}) {
|
||||
if (kvRows == null) {
|
||||
return null;
|
||||
}
|
||||
Map<String, String> finalMap = {};
|
||||
for (var row in kvRows) {
|
||||
if (row.name.trim() != "") {
|
||||
String key = row.name;
|
||||
if (isHeader) {
|
||||
key = key.toLowerCase();
|
||||
}
|
||||
finalMap[key] = row.value.toString();
|
||||
}
|
||||
}
|
||||
return finalMap;
|
||||
}
|
||||
|
||||
List<NameValueModel>? mapToRows(Map<String, String>? kvMap) {
|
||||
if (kvMap == null) {
|
||||
return null;
|
||||
}
|
||||
List<NameValueModel> finalRows = [];
|
||||
for (var k in kvMap.keys) {
|
||||
finalRows.add(NameValueModel(name: k, value: kvMap[k]));
|
||||
}
|
||||
return finalRows;
|
||||
}
|
||||
|
||||
List<Map<String, String>>? rowsToFormDataMapList(
|
||||
List<FormDataModel>? kvRows,
|
||||
) {
|
||||
if (kvRows == null) {
|
||||
return null;
|
||||
}
|
||||
List<Map<String, String>> finalMap = kvRows
|
||||
.map((FormDataModel formData) =>
|
||||
(formData.name.trim().isEmpty && formData.value.trim().isEmpty)
|
||||
? null
|
||||
: {
|
||||
"name": formData.name,
|
||||
"value": formData.value,
|
||||
"type": formData.type.name,
|
||||
})
|
||||
.whereNotNull()
|
||||
.toList();
|
||||
return finalMap;
|
||||
}
|
||||
|
||||
List<FormDataModel>? mapListToFormDataModelRows(List<Map>? kvMap) {
|
||||
if (kvMap == null) {
|
||||
return null;
|
||||
}
|
||||
List<FormDataModel> finalRows = kvMap.map(
|
||||
(formData) {
|
||||
return FormDataModel(
|
||||
name: formData["name"],
|
||||
value: formData["value"],
|
||||
type: getFormDataType(formData["type"]),
|
||||
);
|
||||
},
|
||||
).toList();
|
||||
return finalRows;
|
||||
}
|
||||
|
||||
FormDataType getFormDataType(String? type) {
|
||||
return FormDataType.values.firstWhere((element) => element.name == type,
|
||||
orElse: () => FormDataType.text);
|
||||
}
|
||||
|
||||
List<NameValueModel>? getEnabledRows(
|
||||
List<NameValueModel>? rows, List<bool>? isRowEnabledList) {
|
||||
if (rows == null || isRowEnabledList == null) {
|
||||
return rows;
|
||||
}
|
||||
List<NameValueModel> finalRows =
|
||||
rows.where((element) => isRowEnabledList[rows.indexOf(element)]).toList();
|
||||
return finalRows == [] ? null : finalRows;
|
||||
}
|
75
packages/apidash_core/lib/utils/http_response_utils.dart
Normal file
75
packages/apidash_core/lib/utils/http_response_utils.dart
Normal file
@ -0,0 +1,75 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:http_parser/http_parser.dart';
|
||||
import 'package:xml/xml.dart';
|
||||
import '../consts.dart';
|
||||
|
||||
String? getContentTypeFromHeaders(Map? headers) {
|
||||
return headers?[HttpHeaders.contentTypeHeader];
|
||||
}
|
||||
|
||||
MediaType? getMediaTypeFromHeaders(Map? headers) {
|
||||
var contentType = getContentTypeFromHeaders(headers);
|
||||
MediaType? mediaType = getMediaTypeFromContentType(contentType);
|
||||
return mediaType;
|
||||
}
|
||||
|
||||
MediaType? getMediaTypeFromContentType(String? contentType) {
|
||||
if (contentType != null) {
|
||||
try {
|
||||
MediaType mediaType = MediaType.parse(contentType);
|
||||
return mediaType;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
String? formatBody(String? body, MediaType? mediaType) {
|
||||
if (mediaType != null && body != null) {
|
||||
var subtype = mediaType.subtype;
|
||||
try {
|
||||
if (subtype.contains(kSubTypeJson)) {
|
||||
final tmp = jsonDecode(body);
|
||||
String result = kJsonEncoder.convert(tmp);
|
||||
return result;
|
||||
}
|
||||
if (subtype.contains(kSubTypeXml)) {
|
||||
final document = XmlDocument.parse(body);
|
||||
String result = document.toXmlString(pretty: true, indent: ' ');
|
||||
return result;
|
||||
}
|
||||
if (subtype == kSubTypeHtml) {
|
||||
var len = body.length;
|
||||
var lines = kSplitter.convert(body);
|
||||
var numOfLines = lines.length;
|
||||
if (numOfLines != 0 && len / numOfLines <= kCodeCharsPerLineLimit) {
|
||||
return body;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
60
packages/apidash_core/lib/utils/uri_utils.dart
Normal file
60
packages/apidash_core/lib/utils/uri_utils.dart
Normal file
@ -0,0 +1,60 @@
|
||||
import 'package:collection/collection.dart' show mergeMaps;
|
||||
import '../consts.dart';
|
||||
import '../models/name_value_model.dart';
|
||||
import 'http_request_utils.dart';
|
||||
|
||||
(String?, bool) getUriScheme(Uri uri) {
|
||||
if (uri.hasScheme) {
|
||||
if (kSupportedUriSchemes.contains(uri.scheme)) {
|
||||
return (uri.scheme, true);
|
||||
}
|
||||
return (uri.scheme, false);
|
||||
}
|
||||
return (null, false);
|
||||
}
|
||||
|
||||
String stripUriParams(Uri uri) {
|
||||
return "${uri.scheme}://${uri.authority}${uri.path}";
|
||||
}
|
||||
|
||||
String stripUrlParams(String url) {
|
||||
var idx = url.indexOf("?");
|
||||
return idx > 0 ? url.substring(0, idx) : url;
|
||||
}
|
||||
|
||||
(Uri?, String?) getValidRequestUri(
|
||||
String? url, List<NameValueModel>? requestParams,
|
||||
{String defaultUriScheme = kDefaultUriScheme}) {
|
||||
url = url?.trim();
|
||||
if (url == null || url == "") {
|
||||
return (null, "URL is missing!");
|
||||
}
|
||||
Uri? uri = Uri.tryParse(url);
|
||||
if (uri == null) {
|
||||
return (null, "Check URL (malformed)");
|
||||
}
|
||||
(String?, bool) urlScheme = getUriScheme(uri);
|
||||
|
||||
if (urlScheme.$1 != null) {
|
||||
if (!urlScheme.$2) {
|
||||
return (null, "Unsupported URL Scheme (${urlScheme.$1})");
|
||||
}
|
||||
} else {
|
||||
url = "$defaultUriScheme://$url";
|
||||
}
|
||||
|
||||
uri = Uri.parse(url);
|
||||
if (uri.hasFragment) {
|
||||
uri = uri.removeFragment();
|
||||
}
|
||||
|
||||
Map<String, String>? queryParams = rowsToMap(requestParams);
|
||||
if (queryParams != null && queryParams.isNotEmpty) {
|
||||
if (uri.hasQuery) {
|
||||
Map<String, String> urlQueryParams = uri.queryParameters;
|
||||
queryParams = mergeMaps(urlQueryParams, queryParams);
|
||||
}
|
||||
uri = uri.replace(queryParameters: queryParams);
|
||||
}
|
||||
return (uri, null);
|
||||
}
|
3
packages/apidash_core/lib/utils/utils.dart
Normal file
3
packages/apidash_core/lib/utils/utils.dart
Normal file
@ -0,0 +1,3 @@
|
||||
export 'http_request_utils.dart';
|
||||
export 'http_response_utils.dart';
|
||||
export 'uri_utils.dart';
|
@ -2,6 +2,7 @@ name: apidash_core
|
||||
description: "API Dash Core"
|
||||
version: 0.0.1
|
||||
homepage:
|
||||
publish_to: none
|
||||
|
||||
environment:
|
||||
sdk: ^3.5.3
|
||||
@ -10,7 +11,11 @@ environment:
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
collection: ^1.18.0
|
||||
freezed_annotation: ^2.4.1
|
||||
http: ^1.2.1
|
||||
http_parser: ^4.0.2
|
||||
xml: ^6.3.0
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
Reference in New Issue
Block a user