Skip to content

GSOC API Auth Draft-PR #776

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions lib/consts.dart
Original file line number Diff line number Diff line change
@@ -162,6 +162,27 @@ enum ResponseBodyView {
final IconData icon;
}

enum AuthType {
noauth("No Auth"),
basic("Basic"),
bearer("Bearer"),
apikey("API Key"),
digest("Digest"),
oauth1("OAuth 1.0"),
oauth2("OAuth 2.0");

const AuthType(this.label);
final String label;
}

enum AddTo {
header("Header"),
query("Query Parameter");

const AddTo(this.label);
final String label;
}

const kNoBodyViewOptions = [ResponseBodyView.none];
const kNoRawBodyViewOptions = [ResponseBodyView.none, ResponseBodyView.raw];
const kRawBodyViewOptions = [ResponseBodyView.raw];
@@ -444,6 +465,7 @@ const kLabelURLParams = "URL Params";
const kLabelHeaders = "Headers";
const kLabelBody = "Body";
const kLabelQuery = "Query";
const kLabelAuthorization = "Authorization";
const kNameCheckbox = "Checkbox";
const kNameURLParam = "URL Parameter";
const kNameHeader = "Header Name";
80 changes: 80 additions & 0 deletions lib/models/authorization_model.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import 'package:apidash/consts.dart';
import 'package:apidash_core/apidash_core.dart';

part 'authorization_model.freezed.dart';

part 'authorization_model.g.dart';

@freezed
class AuthorizationModel with _$AuthorizationModel {
@JsonSerializable(
explicitToJson: true,
anyMap: true,
)
const factory AuthorizationModel({
@Default(AuthType.noauth) AuthType authType,
@Default(false) bool isEnabled,
@Default(BasicAuthModel(username: '', password: '')) BasicAuthModel basicAuthModel,
@Default(BearerAuthModel(token: '')) BearerAuthModel bearerAuthModel,
@Default(ApiKeyAuthModel(key: '', value: '', addTo: AddTo.header)) ApiKeyAuthModel apiKeyAuthModel,
// JWTBearerAuthModel? jwtBearerAuthModel,
// DigestAuthModel? digestAuthModel,
// OAuth1AuthModel? oauth1AuthModel,
// OAuth2AuthModel? oauth2AuthModel,
}) = _AuthorizationModel;

factory AuthorizationModel.fromJson(Map<String, Object?> json) =>
_$AuthorizationModelFromJson(json);
}

@freezed
class BasicAuthModel with _$BasicAuthModel {
@JsonSerializable(
explicitToJson: true,
anyMap: true,
)
const factory BasicAuthModel({
@Default("") String username,
@Default("") String password,
}) = _BasicAuthModel;

factory BasicAuthModel.fromJson(Map<String, Object?> json) =>
_$BasicAuthModelFromJson(json);
}

@freezed
class BearerAuthModel with _$BearerAuthModel {
@JsonSerializable(
explicitToJson: true,
anyMap: true,
)
const factory BearerAuthModel({
@Default("") String token,
}) = _BearerAuthModel;

factory BearerAuthModel.fromJson(Map<String, Object?> json) =>
_$BearerAuthModelFromJson(json);
}

@freezed
class ApiKeyAuthModel with _$ApiKeyAuthModel {
@JsonSerializable(
explicitToJson: true,
anyMap: true,
)
const factory ApiKeyAuthModel({
@Default("") String key,
@Default("") String value,
@Default(AddTo.header) AddTo addTo,
}) = _ApiKeyAuthModel;

factory ApiKeyAuthModel.fromJson(Map<String, Object?> json) =>
_$ApiKeyAuthModelFromJson(json);
}

// @freezed
// class DigestAuthModel with _$DigestAuthModel {
// @JsonSerializable(
// explicitToJson: true,
// anyMap: true,
// )
817 changes: 817 additions & 0 deletions lib/models/authorization_model.freezed.dart

Large diffs are not rendered by default.

90 changes: 90 additions & 0 deletions lib/models/authorization_model.g.dart
2 changes: 2 additions & 0 deletions lib/models/request_model.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'package:apidash/models/authorization_model.dart';
import 'package:apidash_core/apidash_core.dart';

part 'request_model.freezed.dart';
@@ -17,6 +18,7 @@ class RequestModel with _$RequestModel {
@Default("") String description,
@JsonKey(includeToJson: false) @Default(0) requestTabIndex,
HttpRequestModel? httpRequestModel,
AuthorizationModel? authorizationModel,
int? responseStatus,
String? message,
HttpResponseModel? httpResponseModel,
43 changes: 42 additions & 1 deletion lib/models/request_model.freezed.dart
Original file line number Diff line number Diff line change
@@ -27,6 +27,8 @@ mixin _$RequestModel {
@JsonKey(includeToJson: false)
dynamic get requestTabIndex => throw _privateConstructorUsedError;
HttpRequestModel? get httpRequestModel => throw _privateConstructorUsedError;
AuthorizationModel? get authorizationModel =>
throw _privateConstructorUsedError;
int? get responseStatus => throw _privateConstructorUsedError;
String? get message => throw _privateConstructorUsedError;
HttpResponseModel? get httpResponseModel =>
@@ -59,13 +61,15 @@ abstract class $RequestModelCopyWith<$Res> {
String description,
@JsonKey(includeToJson: false) dynamic requestTabIndex,
HttpRequestModel? httpRequestModel,
AuthorizationModel? authorizationModel,
int? responseStatus,
String? message,
HttpResponseModel? httpResponseModel,
@JsonKey(includeToJson: false) bool isWorking,
@JsonKey(includeToJson: false) DateTime? sendingTime});

$HttpRequestModelCopyWith<$Res>? get httpRequestModel;
$AuthorizationModelCopyWith<$Res>? get authorizationModel;
$HttpResponseModelCopyWith<$Res>? get httpResponseModel;
}

@@ -90,6 +94,7 @@ class _$RequestModelCopyWithImpl<$Res, $Val extends RequestModel>
Object? description = null,
Object? requestTabIndex = freezed,
Object? httpRequestModel = freezed,
Object? authorizationModel = freezed,
Object? responseStatus = freezed,
Object? message = freezed,
Object? httpResponseModel = freezed,
@@ -121,6 +126,10 @@ class _$RequestModelCopyWithImpl<$Res, $Val extends RequestModel>
? _value.httpRequestModel
: httpRequestModel // ignore: cast_nullable_to_non_nullable
as HttpRequestModel?,
authorizationModel: freezed == authorizationModel
? _value.authorizationModel
: authorizationModel // ignore: cast_nullable_to_non_nullable
as AuthorizationModel?,
responseStatus: freezed == responseStatus
? _value.responseStatus
: responseStatus // ignore: cast_nullable_to_non_nullable
@@ -158,6 +167,21 @@ class _$RequestModelCopyWithImpl<$Res, $Val extends RequestModel>
});
}

/// Create a copy of RequestModel
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$AuthorizationModelCopyWith<$Res>? get authorizationModel {
if (_value.authorizationModel == null) {
return null;
}

return $AuthorizationModelCopyWith<$Res>(_value.authorizationModel!,
(value) {
return _then(_value.copyWith(authorizationModel: value) as $Val);
});
}

/// Create a copy of RequestModel
/// with the given fields replaced by the non-null parameter values.
@override
@@ -188,6 +212,7 @@ abstract class _$$RequestModelImplCopyWith<$Res>
String description,
@JsonKey(includeToJson: false) dynamic requestTabIndex,
HttpRequestModel? httpRequestModel,
AuthorizationModel? authorizationModel,
int? responseStatus,
String? message,
HttpResponseModel? httpResponseModel,
@@ -197,6 +222,8 @@ abstract class _$$RequestModelImplCopyWith<$Res>
@override
$HttpRequestModelCopyWith<$Res>? get httpRequestModel;
@override
$AuthorizationModelCopyWith<$Res>? get authorizationModel;
@override
$HttpResponseModelCopyWith<$Res>? get httpResponseModel;
}

@@ -219,6 +246,7 @@ class __$$RequestModelImplCopyWithImpl<$Res>
Object? description = null,
Object? requestTabIndex = freezed,
Object? httpRequestModel = freezed,
Object? authorizationModel = freezed,
Object? responseStatus = freezed,
Object? message = freezed,
Object? httpResponseModel = freezed,
@@ -249,6 +277,10 @@ class __$$RequestModelImplCopyWithImpl<$Res>
? _value.httpRequestModel
: httpRequestModel // ignore: cast_nullable_to_non_nullable
as HttpRequestModel?,
authorizationModel: freezed == authorizationModel
? _value.authorizationModel
: authorizationModel // ignore: cast_nullable_to_non_nullable
as AuthorizationModel?,
responseStatus: freezed == responseStatus
? _value.responseStatus
: responseStatus // ignore: cast_nullable_to_non_nullable
@@ -284,6 +316,7 @@ class _$RequestModelImpl implements _RequestModel {
this.description = "",
@JsonKey(includeToJson: false) this.requestTabIndex = 0,
this.httpRequestModel,
this.authorizationModel,
this.responseStatus,
this.message,
this.httpResponseModel,
@@ -310,6 +343,8 @@ class _$RequestModelImpl implements _RequestModel {
@override
final HttpRequestModel? httpRequestModel;
@override
final AuthorizationModel? authorizationModel;
@override
final int? responseStatus;
@override
final String? message;
@@ -324,7 +359,7 @@ class _$RequestModelImpl implements _RequestModel {

@override
String toString() {
return 'RequestModel(id: $id, apiType: $apiType, name: $name, description: $description, requestTabIndex: $requestTabIndex, httpRequestModel: $httpRequestModel, responseStatus: $responseStatus, message: $message, httpResponseModel: $httpResponseModel, isWorking: $isWorking, sendingTime: $sendingTime)';
return 'RequestModel(id: $id, apiType: $apiType, name: $name, description: $description, requestTabIndex: $requestTabIndex, httpRequestModel: $httpRequestModel, authorizationModel: $authorizationModel, responseStatus: $responseStatus, message: $message, httpResponseModel: $httpResponseModel, isWorking: $isWorking, sendingTime: $sendingTime)';
}

@override
@@ -341,6 +376,8 @@ class _$RequestModelImpl implements _RequestModel {
.equals(other.requestTabIndex, requestTabIndex) &&
(identical(other.httpRequestModel, httpRequestModel) ||
other.httpRequestModel == httpRequestModel) &&
(identical(other.authorizationModel, authorizationModel) ||
other.authorizationModel == authorizationModel) &&
(identical(other.responseStatus, responseStatus) ||
other.responseStatus == responseStatus) &&
(identical(other.message, message) || other.message == message) &&
@@ -362,6 +399,7 @@ class _$RequestModelImpl implements _RequestModel {
description,
const DeepCollectionEquality().hash(requestTabIndex),
httpRequestModel,
authorizationModel,
responseStatus,
message,
httpResponseModel,
@@ -392,6 +430,7 @@ abstract class _RequestModel implements RequestModel {
final String description,
@JsonKey(includeToJson: false) final dynamic requestTabIndex,
final HttpRequestModel? httpRequestModel,
final AuthorizationModel? authorizationModel,
final int? responseStatus,
final String? message,
final HttpResponseModel? httpResponseModel,
@@ -416,6 +455,8 @@ abstract class _RequestModel implements RequestModel {
@override
HttpRequestModel? get httpRequestModel;
@override
AuthorizationModel? get authorizationModel;
@override
int? get responseStatus;
@override
String? get message;
5 changes: 5 additions & 0 deletions lib/models/request_model.g.dart
61 changes: 61 additions & 0 deletions lib/providers/authorization_providers.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import 'package:apidash/consts.dart';
import 'package:apidash/providers/collection_providers.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:apidash/models/authorization_model.dart';

class AuthorizationNotifier extends StateNotifier<AuthorizationModel> {
final Ref ref;

AuthorizationNotifier(AuthorizationModel initialState, this.ref)
: super(initialState);

void update({
bool? isEnabled,
AuthType? authType,
String? username,
String? password,
String? token,
String? apiKey,
String? apiValue,
AddTo? addTo,
}) {
if (authType != null && authType != state.authType) {
state = state.copyWith(authType: authType);
}
// General state update
state = state.copyWith(
isEnabled: isEnabled ?? state.isEnabled,
basicAuthModel: state.authType == AuthType.basic
? state.basicAuthModel.copyWith(
username: username ?? state.basicAuthModel.username,
password: password ?? state.basicAuthModel.password,
)
: state.basicAuthModel,
bearerAuthModel: state.authType == AuthType.bearer
? state.bearerAuthModel.copyWith(
token: token ?? state.bearerAuthModel.token,
) : state.bearerAuthModel,
apiKeyAuthModel: state.authType == AuthType.apikey
? state.apiKeyAuthModel.copyWith(
key: apiKey ?? state.apiKeyAuthModel.key,
value: apiValue ?? state.apiKeyAuthModel.value,
addTo: addTo ?? state.apiKeyAuthModel.addTo,
) : state.apiKeyAuthModel,
);

_syncWithRequest();
}

void _syncWithRequest() {
ref.read(collectionStateNotifierProvider.notifier).update(
authorizationModel: state,
);
}
}

final authorizationProvider =
StateNotifierProvider<AuthorizationNotifier, AuthorizationModel>((ref) {
final currentRequest = ref.watch(selectedRequestModelProvider);
return AuthorizationNotifier(
currentRequest?.authorizationModel ?? AuthorizationModel(), ref);
});
6 changes: 6 additions & 0 deletions lib/providers/collection_providers.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'package:apidash/models/authorization_model.dart';
import 'package:apidash_core/apidash_core.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
@@ -25,6 +26,7 @@ final requestSequenceProvider = StateProvider<List<String>>((ref) {
return ids ?? [];
});

/// TODO: Provider for current request model
final StateNotifierProvider<CollectionStateNotifier, Map<String, RequestModel>?>
collectionStateNotifierProvider =
StateNotifierProvider((ref) => CollectionStateNotifier(
@@ -204,10 +206,12 @@ class CollectionStateNotifier
unsave();
}

/// TODO: Update the request model with given parameters for each change
void update({
String? id,
HTTPVerb? method,
APIType? apiType,
AuthorizationModel? authorizationModel,
String? url,
String? name,
String? description,
@@ -233,6 +237,7 @@ class CollectionStateNotifier
var currentHttpRequestModel = currentModel.httpRequestModel;
final newModel = currentModel.copyWith(
apiType: apiType ?? currentModel.apiType,
authorizationModel: authorizationModel ?? currentModel.authorizationModel,
name: name ?? currentModel.name,
description: description ?? currentModel.description,
requestTabIndex: requestTabIndex ?? currentModel.requestTabIndex,
@@ -262,6 +267,7 @@ class CollectionStateNotifier
unsave();
}

/// TODO: Send the request with all given parameters
Future<void> sendRequest() async {
final requestId = ref.read(selectedIdStateProvider);
ref.read(codePaneVisibleStateProvider.notifier).state = false;
Original file line number Diff line number Diff line change
@@ -0,0 +1,374 @@
import 'dart:convert';
import 'dart:math';
import 'package:apidash/providers/authorization_providers.dart';
import 'package:apidash/providers/collection_providers.dart';
import 'package:apidash_core/apidash_core.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:apidash_design_system/apidash_design_system.dart';
import 'package:apidash/consts.dart';

class EditRequestAuthorization extends ConsumerStatefulWidget {
const EditRequestAuthorization({super.key});

@override
ConsumerState<EditRequestAuthorization> createState() =>
_EditRequestAuthorizationState();
}

class _EditRequestAuthorizationState
extends ConsumerState<EditRequestAuthorization> {
late int seed;
final random = Random.secure();
bool _obscurePassword = true;

@override
void initState() {
super.initState();
seed = random.nextInt(kRandMax);
}

void _updateHeaders() {
final currentHeaders =
ref.read(selectedRequestModelProvider)?.httpRequestModel?.headers ?? [];
final authModel = ref.watch(authorizationProvider);
final currentAuthType =
ref.watch(authorizationProvider.select((value) => value.authType));
var newHeaders = currentHeaders.toList();

if (currentAuthType == AuthType.basic ||
currentAuthType == AuthType.bearer) {
newHeaders = currentHeaders
.where((h) => (h.name).toLowerCase() != 'authorization')
.toList();
}
if (currentAuthType == AuthType.apikey &&
authModel.apiKeyAuthModel.addTo == AddTo.header) {
if (authModel.apiKeyAuthModel.key.isNotEmpty &&
authModel.apiKeyAuthModel.value.isNotEmpty) {
newHeaders.add(NameValueModel(
name: authModel.apiKeyAuthModel.key,
value: authModel.apiKeyAuthModel.value,
));
}
}

if (authModel.isEnabled && currentAuthType != AuthType.noauth) {
String authValue = '';
switch (currentAuthType) {
case AuthType.basic:
authValue =
'Basic ${base64Encode(utf8.encode('${authModel.basicAuthModel.username}:${authModel.basicAuthModel.password}'))}';
break;
case AuthType.bearer:
authValue = 'Bearer ${authModel.bearerAuthModel.token}';
break;
case AuthType.noauth:
break;
default:
break;
}

if (authValue.isNotEmpty) {
newHeaders.add(NameValueModel(
name: 'Authorization',
value: authValue,
));
}
}

ref.read(collectionStateNotifierProvider.notifier).update(
headers: newHeaders,
isHeaderEnabledList: List.filled(newHeaders.length, true),
);
}

void _updateQueryParams() {
final currentParams = ref
.read(selectedRequestModelProvider)
?.httpRequestModel
?.params ??
[];
final authModel = ref.watch(authorizationProvider);
final currentAuthType =
ref.watch(authorizationProvider.select((value) => value.authType));

if (authModel.isEnabled &&
currentAuthType == AuthType.apikey &&
authModel.apiKeyAuthModel.addTo == AddTo.query) {
final apiKeyName = authModel.apiKeyAuthModel.key;
final apiKeyvalue = authModel.apiKeyAuthModel.value;

if (apiKeyName.isNotEmpty && apiKeyvalue.isNotEmpty) {
// Check if a parameter with ANY name from apiKey auth already exists
// if it does, we need to update the name and value
// if it doesn't, we need to add a new parameter
bool foundExisting = false;
List<NameValueModel> newParams = currentParams.map((param) {
if (currentParams.indexWhere((p) => p.name == authModel.apiKeyAuthModel.key) != -1) {
foundExisting = true;
return param.copyWith(name: apiKeyName, value: apiKeyvalue);
}
return param;
}).toList();

if (!foundExisting) {
// Add new parameter
newParams = [...newParams, NameValueModel(name: apiKeyName, value: apiKeyvalue)];
}

ref.read(collectionStateNotifierProvider.notifier).update(
params: newParams,
isParamEnabledList: List.filled(newParams.length, true),
);
}
} else {
//If not enabled or not apiKey auth, remove the param if it exists
List<NameValueModel> newParams = currentParams.where((param) => param.name != authModel.apiKeyAuthModel.key).toList();
ref.read(collectionStateNotifierProvider.notifier).update(
params: newParams,
isParamEnabledList: List.filled(newParams.length, true),
);
}
}

// @override
// void didUpdateWidget(covariant EditRequestAuthorization oldWidget) {
// super.didUpdateWidget(oldWidget);
// _updateHeaders();
// _updateQueryParams();

// }

@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final colorScheme = theme.colorScheme;

final authNotifier = ref.read(authorizationProvider.notifier);
final authModel = ref.watch(authorizationProvider);
final currentAuthType =
ref.watch(authorizationProvider.select((value) => value.authType));
final selectedId = ref.watch(selectedIdStateProvider);

return Padding(
padding: kP10,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
ADCheckBox(
value: authModel.isEnabled,
onChanged: (value) {
authNotifier.update(
isEnabled: value,
);
},
colorScheme: colorScheme,
keyId: 'auth-checkbox',
),
const SizedBox(width: 8),
const Text('Enable Authorization'),
],
),
Text(
'The authorization header will be automatically generated when you send the request.'),
const SizedBox(height: 16),
if (authModel.isEnabled) ...[
SizedBox(
height: kHeaderHeight,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text("Select Authorization Type:"),
const SizedBox(width: 8),
ADDropdownButton<AuthType>(
value: currentAuthType,
values: [
(AuthType.noauth, 'No Auth'),
(AuthType.basic, 'Basic Auth'),
(AuthType.bearer, 'Bearer Token'),
(AuthType.apikey, 'API Key'),
],
onChanged: (value) {
authNotifier.update(
authType: value,
);
},
),
],
),
),
const SizedBox(height: 16),
switch (currentAuthType) {
AuthType.basic => Column(children: [
TextFormField(
key: Key("$selectedId-username"),
decoration: InputDecoration(
labelText: 'Username',
contentPadding: const EdgeInsets.symmetric(
vertical: 12, horizontal: 12),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8)),
filled: true,
fillColor: colorScheme.surface,
),
initialValue: authModel.basicAuthModel.username,
onChanged: (value) {
authNotifier.update(
username: value,
);

/// TODO: when value is not null and not empty add the authorization header
if (value.isNotEmpty) {
_updateHeaders();
}
},
),
const SizedBox(height: 12),
TextFormField(
key: Key("$selectedId-password"),
decoration: InputDecoration(
labelText: 'Password',
contentPadding: const EdgeInsets.symmetric(
vertical: 12, horizontal: 12),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8)),
filled: true,
fillColor: colorScheme.surface,
suffixIcon: IconButton(
icon: Icon(
_obscurePassword
? Icons.visibility_off
: Icons.visibility,
color: colorScheme.onSurface.withAlpha(150),
),
onPressed: () {
setState(() {
_obscurePassword = !_obscurePassword;
});
},
),
),
initialValue: authModel.basicAuthModel.password,
obscureText: _obscurePassword,
onChanged: (value) {
authNotifier.update(
password: value,
);
if (value.isNotEmpty) {
_updateHeaders();
}
},
),
]),
AuthType.bearer => Column(children: [
TextFormField(
key: Key("$selectedId-token"),
decoration: InputDecoration(
labelText: 'Token',
contentPadding: const EdgeInsets.symmetric(
vertical: 12, horizontal: 12),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8)),
filled: true,
fillColor: colorScheme.surface,
),
initialValue: authModel.bearerAuthModel.token,
onChanged: (value) {
authNotifier.update(
token: value,
);
if (value.isNotEmpty) {
_updateHeaders();
}
},
),
]),
AuthType.apikey => Column(children: [
TextFormField(
key: Key("$selectedId-key"),
decoration: InputDecoration(
labelText: 'Key',
contentPadding: const EdgeInsets.symmetric(
vertical: 12, horizontal: 12),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8)),
filled: true,
fillColor: colorScheme.surface,
),
initialValue: authModel.apiKeyAuthModel.key,
onChanged: (value) {
authNotifier.update(
apiKey: value,
);
if (value.isNotEmpty &&
authModel.apiKeyAuthModel.addTo == AddTo.query) {
_updateQueryParams();
}
},
),
const SizedBox(height: 12),
TextFormField(
key: Key("$selectedId-value"),
decoration: InputDecoration(
labelText: 'Value',
contentPadding: const EdgeInsets.symmetric(
vertical: 12, horizontal: 12),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8)),
filled: true,
fillColor: colorScheme.surface,
),
initialValue: authModel.apiKeyAuthModel.value,
onChanged: (value) {
authNotifier.update(
apiValue: value,
);
if (value.isNotEmpty &&
authModel.apiKeyAuthModel.addTo == AddTo.query) {
_updateQueryParams();
}
},
),
const SizedBox(height: 12),
SizedBox(
height: kHeaderHeight,
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
const Text("Add to:"),
const SizedBox(width: 8),
ADDropdownButton<AddTo>(
value: authModel.apiKeyAuthModel.addTo,
values: [
(
AddTo.header,
'Header',
),
(AddTo.query, 'Query Params'),
],
onChanged: (value) {
authNotifier.update(
addTo: value,
);
if (value == AddTo.header) {
_updateHeaders();
} else if (value == AddTo.query) {
_updateQueryParams();
}
},
),
],
),
),
]),
_ => Container()
},
],
],
),
);
}
}
Original file line number Diff line number Diff line change
@@ -28,6 +28,8 @@ class EditRequestHeadersState extends ConsumerState<EditRequestHeaders> {
seed = random.nextInt(kRandMax);
}


/// TODO: When the field is changed, we update the headers and isHeaderEnabledList
void _onFieldChange() {
ref.read(collectionStateNotifierProvider.notifier).update(
headers: headerRows.sublist(0, headerRows.length - 1),
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:apidash/consts.dart';
import 'package:apidash/screens/home_page/editor_pane/details_card/request_pane/request_authorization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:apidash/providers/providers.dart';
@@ -43,16 +44,20 @@ class EditRestRequestPane extends ConsumerWidget {
showIndicators: [
paramLength > 0,
headerLength > 0,
// TODO: Check if there needs to be an indicator for authorization tab
headerLength > 0,
hasBody,
],
tabLabels: const [
kLabelURLParams,
kLabelHeaders,
kLabelAuthorization,
kLabelBody,
],
children: const [
EditRequestURLParams(),
EditRequestHeaders(),
EditRequestAuthorization(),
EditRequestBody(),
],
);
24 changes: 24 additions & 0 deletions lib/widgets/dropdown_auth_type.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import 'package:apidash/consts.dart';
import 'package:apidash_design_system/apidash_design_system.dart';
import 'package:flutter/material.dart';

class DropdownButtonAuthType extends StatelessWidget {
const DropdownButtonAuthType({
super.key,
this.authType,
this.onChanged,
});

final AuthType? authType;
final void Function(AuthType?)? onChanged;

@override
Widget build(BuildContext context) {
return ADDropdownButton<AuthType>(
value: authType,
values: AuthType.values.map((e) => (e, e.name)),
onChanged: onChanged,
iconSize: 16,
);
}
}
4 changes: 3 additions & 1 deletion lib/widgets/request_pane.dart
Original file line number Diff line number Diff line change
@@ -4,6 +4,8 @@ import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:apidash/consts.dart';
import 'tab_label.dart';

/// TODO: For setting a new tab
class RequestPane extends StatefulHookWidget {
const RequestPane({
super.key,
@@ -14,7 +16,7 @@ class RequestPane extends StatefulHookWidget {
this.onTapTabBar,
required this.tabLabels,
required this.children,
this.showIndicators = const [false, false, false],
this.showIndicators = const [false, false, false, false],
this.showViewCodeButton,
});

2 changes: 2 additions & 0 deletions packages/apidash_core/lib/consts.dart
Original file line number Diff line number Diff line change
@@ -85,6 +85,8 @@ enum ContentType {
final String header;
}



const JsonEncoder kJsonEncoder = JsonEncoder.withIndent(' ');
const JsonDecoder kJsonDecoder = JsonDecoder();
const LineSplitter kSplitter = LineSplitter();
Original file line number Diff line number Diff line change
@@ -9,7 +9,7 @@ import 'package:multi_trigger_autocomplete_plus/multi_trigger_autocomplete_plus.
void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
const MyApp({super.key});

// This widget is the root of your application.
@override
@@ -30,7 +30,7 @@ class MyApp extends StatelessWidget {
}

class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key}) : super(key: key);
const MyHomePage({super.key});

@override
State<MyHomePage> createState() => _MyHomePageState();
Original file line number Diff line number Diff line change
@@ -5,9 +5,9 @@ import 'package:google_fonts/google_fonts.dart';

class ChatMessageList extends StatelessWidget {
const ChatMessageList({
Key? key,
super.key,
required this.messages,
}) : super(key: key);
});

final List<ChatMessage> messages;

Original file line number Diff line number Diff line change
@@ -6,11 +6,11 @@ import 'package:example/src/data.dart';

class ChatMessageTextField extends StatelessWidget {
const ChatMessageTextField({
Key? key,
super.key,
required this.focusNode,
required this.controller,
required this.onSend,
}) : super(key: key);
});

final FocusNode focusNode;
final TextEditingController controller;
Original file line number Diff line number Diff line change
@@ -5,10 +5,10 @@ import 'package:example/src/models.dart';

class EmojiAutocompleteOptions extends StatelessWidget {
const EmojiAutocompleteOptions({
Key? key,
super.key,
required this.query,
required this.onEmojiTap,
}) : super(key: key);
});

final String query;
final ValueSetter<Emoji> onEmojiTap;
Original file line number Diff line number Diff line change
@@ -5,10 +5,10 @@ import 'package:example/src/models.dart';

class HashtagAutocompleteOptions extends StatelessWidget {
const HashtagAutocompleteOptions({
Key? key,
super.key,
required this.query,
required this.onHashtagTap,
}) : super(key: key);
});

final String query;
final ValueSetter<Hashtag> onHashtagTap;
Original file line number Diff line number Diff line change
@@ -5,10 +5,10 @@ import 'package:example/src/models.dart';

class MentionAutocompleteOptions extends StatelessWidget {
const MentionAutocompleteOptions({
Key? key,
super.key,
required this.query,
required this.onMentionUserTap,
}) : super(key: key);
});

final String query;
final ValueSetter<User> onMentionUserTap;
14 changes: 7 additions & 7 deletions packages/multi_trigger_autocomplete_plus/example/pubspec.lock
Original file line number Diff line number Diff line change
@@ -69,10 +69,10 @@ packages:
dependency: transitive
description:
name: ffi
sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6"
sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418"
url: "https://pub.dev"
source: hosted
version: "2.1.3"
version: "2.1.4"
flutter:
dependency: "direct main"
description: flutter
@@ -127,10 +127,10 @@ packages:
dependency: transitive
description:
name: http_parser
sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b"
sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571"
url: "https://pub.dev"
source: hosted
version: "4.0.2"
version: "4.1.2"
leak_tracker:
dependency: transitive
description:
@@ -347,10 +347,10 @@ packages:
dependency: transitive
description:
name: web
sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb
sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
version: "1.1.1"
xdg_directories:
dependency: transitive
description:
@@ -360,5 +360,5 @@ packages:
source: hosted
version: "1.1.0"
sdks:
dart: ">=3.7.0-0 <4.0.0"
dart: ">=3.7.0 <4.0.0"
flutter: ">=3.24.0"
Original file line number Diff line number Diff line change
@@ -466,10 +466,9 @@ class _AutocompleteInvokedTriggerWithQuery {
// The default Material-style Autocomplete text field.
class _MultiTriggerAutocompleteField extends StatelessWidget {
const _MultiTriggerAutocompleteField({
Key? key,
required this.focusNode,
required this.textEditingController,
}) : super(key: key);
});

final FocusNode focusNode;

Original file line number Diff line number Diff line change
@@ -310,7 +310,7 @@ class User {
}

class Boilerplate extends StatelessWidget {
const Boilerplate({Key? key, this.child}) : super(key: key);
const Boilerplate({super.key, this.child});

final Widget? child;