From 653f4a930bddeaf14f61b1d6e7390de2311070ec Mon Sep 17 00:00:00 2001 From: Abhinav Sharma Date: Wed, 15 Jan 2025 14:35:16 +0530 Subject: [PATCH 01/11] chore: create proxy settings --- lib/models/settings_model.dart | 18 ++++++++ lib/providers/proxy_provider.dart | 54 ++++++++++++++++++++++++ lib/providers/settings_providers.dart | 10 +++++ lib/screens/settings_page.dart | 59 +++++++++++++++++++++++++++ 4 files changed, 141 insertions(+) create mode 100644 lib/providers/proxy_provider.dart diff --git a/lib/models/settings_model.dart b/lib/models/settings_model.dart index 74bc2ff9c..ff95f2b32 100644 --- a/lib/models/settings_model.dart +++ b/lib/models/settings_model.dart @@ -17,6 +17,11 @@ class SettingsModel { this.historyRetentionPeriod = HistoryRetentionPeriod.oneWeek, this.workspaceFolderPath, this.isSSLDisabled = false, + this.isProxyEnabled = false, + this.proxyHost = '', + this.proxyPort = '', + this.proxyUsername, + this.proxyPassword, }); final bool isDark; @@ -32,6 +37,13 @@ class SettingsModel { final String? workspaceFolderPath; final bool isSSLDisabled; + // Proxy settings + final bool isProxyEnabled; + final String proxyHost; + final String proxyPort; + final String? proxyUsername; + final String? proxyPassword; + SettingsModel copyWith({ bool? isDark, bool? alwaysShowCollectionPaneScrollbar, @@ -45,6 +57,12 @@ class SettingsModel { HistoryRetentionPeriod? historyRetentionPeriod, String? workspaceFolderPath, bool? isSSLDisabled, + // Proxy settings + bool? isProxyEnabled, + String? proxyHost, + String? proxyPort, + String? proxyUsername, + String? proxyPassword, }) { return SettingsModel( isDark: isDark ?? this.isDark, diff --git a/lib/providers/proxy_provider.dart b/lib/providers/proxy_provider.dart new file mode 100644 index 000000000..cbb89f4d1 --- /dev/null +++ b/lib/providers/proxy_provider.dart @@ -0,0 +1,54 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import '../models/proxy_config.dart'; + +final proxyConfigProvider = StateNotifierProvider((ref) { + return ProxyNotifier(); +}); + +class ProxyNotifier extends StateNotifier { + ProxyNotifier() : super(ProxyConfig()) { + _loadConfig(); + } + + Future _loadConfig() async { + state = await ProxyConfig.load(); + } + + Future updateConfig({ + bool? isEnabled, + String? host, + String? port, + String? username, + String? password, + }) async { + state = ProxyConfig( + isEnabled: isEnabled ?? state.isEnabled, + host: host ?? state.host, + port: port ?? state.port, + username: username ?? state.username, + password: password ?? state.password, + ); + await ProxyConfig.save(state); + } + + String? getProxyUrl() { + if (!state.isEnabled || state.host.isEmpty || state.port.isEmpty) { + return null; + } + final auth = (state.username?.isNotEmpty ?? false) && (state.password?.isNotEmpty ?? false) + ? '${state.username}:${state.password}@' + : ''; + return 'http://$auth${state.host}:${state.port}'; + } + + // Debug method to print proxy configuration + void printProxyConfig() { + print('Proxy Configuration:'); + print('Enabled: ${state.isEnabled}'); + print('Host: ${state.host}'); + print('Port: ${state.port}'); + print('Username: ${state.username != null ? '***' : null}'); + print('Password: ${state.password != null ? '***' : null}'); + print('Proxy URL: ${getProxyUrl() ?? 'Not configured'}'); + } +} diff --git a/lib/providers/settings_providers.dart b/lib/providers/settings_providers.dart index 6b64343aa..28c4cc97d 100644 --- a/lib/providers/settings_providers.dart +++ b/lib/providers/settings_providers.dart @@ -33,6 +33,11 @@ class ThemeStateNotifier extends StateNotifier { HistoryRetentionPeriod? historyRetentionPeriod, String? workspaceFolderPath, bool? isSSLDisabled, + bool? isProxyEnabled, + String? proxyHost, + String? proxyPort, + String? proxyUsername, + String? proxyPassword, }) async { state = state.copyWith( isDark: isDark, @@ -47,6 +52,11 @@ class ThemeStateNotifier extends StateNotifier { historyRetentionPeriod: historyRetentionPeriod, workspaceFolderPath: workspaceFolderPath, isSSLDisabled: isSSLDisabled, + isProxyEnabled: isProxyEnabled, + proxyHost: proxyHost, + proxyPort: proxyPort, + proxyUsername: proxyUsername, + proxyPassword: proxyPassword, ); await setSettingsToSharedPrefs(state); } diff --git a/lib/screens/settings_page.dart b/lib/screens/settings_page.dart index eb4b60088..69ad4bb0f 100644 --- a/lib/screens/settings_page.dart +++ b/lib/screens/settings_page.dart @@ -50,6 +50,65 @@ class SettingsPage extends ConsumerWidget { ref.read(settingsProvider.notifier).update(isDark: value); }, ), + // Proxy Settings Section + SwitchListTile( + hoverColor: kColorTransparent, + title: const Text('Enable Proxy'), + subtitle: const Text('Configure HTTP proxy settings'), + value: settings.isProxyEnabled, + onChanged: (bool? value) { + ref.read(settingsProvider.notifier).update(isProxyEnabled: value); + }, + ), + if (settings.isProxyEnabled) ...[ + ListTile( + title: TextField( + decoration: const InputDecoration( + labelText: 'Proxy Host', + hintText: 'e.g., localhost', + ), + controller: TextEditingController(text: settings.proxyHost), + onChanged: (value) { + ref.read(settingsProvider.notifier).update(proxyHost: value); + }, + ), + ), + ListTile( + title: TextField( + decoration: const InputDecoration( + labelText: 'Proxy Port', + hintText: 'e.g., 8080', + ), + controller: TextEditingController(text: settings.proxyPort), + onChanged: (value) { + ref.read(settingsProvider.notifier).update(proxyPort: value); + }, + ), + ), + ListTile( + title: TextField( + decoration: const InputDecoration( + labelText: 'Username (Optional)', + ), + controller: TextEditingController(text: settings.proxyUsername), + onChanged: (value) { + ref.read(settingsProvider.notifier).update(proxyUsername: value); + }, + ), + ), + ListTile( + title: TextField( + decoration: const InputDecoration( + labelText: 'Password (Optional)', + ), + obscureText: true, + controller: TextEditingController(text: settings.proxyPassword), + onChanged: (value) { + ref.read(settingsProvider.notifier).update(proxyPassword: value); + }, + ), + ), + ], SwitchListTile( hoverColor: kColorTransparent, title: const Text('Collection Pane Scrollbar Visiblity'), From 24d775bab923e9cef7a34606ece75c41f3ade1cb Mon Sep 17 00:00:00 2001 From: Abhinav Sharma Date: Wed, 15 Jan 2025 19:44:45 +0530 Subject: [PATCH 02/11] fix: proxy toggle button issue solved --- lib/providers/settings_providers.dart | 36 +++++++++++++-------------- lib/screens/settings_page.dart | 17 ++++++++++--- 2 files changed, 32 insertions(+), 21 deletions(-) diff --git a/lib/providers/settings_providers.dart b/lib/providers/settings_providers.dart index 28c4cc97d..478da1ca1 100644 --- a/lib/providers/settings_providers.dart +++ b/lib/providers/settings_providers.dart @@ -39,24 +39,24 @@ class ThemeStateNotifier extends StateNotifier { String? proxyUsername, String? proxyPassword, }) async { - state = state.copyWith( - isDark: isDark, - alwaysShowCollectionPaneScrollbar: alwaysShowCollectionPaneScrollbar, - size: size, - offset: offset, - defaultUriScheme: defaultUriScheme, - defaultCodeGenLang: defaultCodeGenLang, - saveResponses: saveResponses, - promptBeforeClosing: promptBeforeClosing, - activeEnvironmentId: activeEnvironmentId, - historyRetentionPeriod: historyRetentionPeriod, - workspaceFolderPath: workspaceFolderPath, - isSSLDisabled: isSSLDisabled, - isProxyEnabled: isProxyEnabled, - proxyHost: proxyHost, - proxyPort: proxyPort, - proxyUsername: proxyUsername, - proxyPassword: proxyPassword, + state = SettingsModel( + isDark: isDark ?? state.isDark, + alwaysShowCollectionPaneScrollbar: alwaysShowCollectionPaneScrollbar ?? state.alwaysShowCollectionPaneScrollbar, + size: size ?? state.size, + offset: offset ?? state.offset, + defaultUriScheme: defaultUriScheme ?? state.defaultUriScheme, + defaultCodeGenLang: defaultCodeGenLang ?? state.defaultCodeGenLang, + saveResponses: saveResponses ?? state.saveResponses, + promptBeforeClosing: promptBeforeClosing ?? state.promptBeforeClosing, + activeEnvironmentId: activeEnvironmentId ?? state.activeEnvironmentId, + historyRetentionPeriod: historyRetentionPeriod ?? state.historyRetentionPeriod, + workspaceFolderPath: workspaceFolderPath ?? state.workspaceFolderPath, + isSSLDisabled: isSSLDisabled ?? state.isSSLDisabled, + isProxyEnabled: isProxyEnabled ?? state.isProxyEnabled, + proxyHost: proxyHost ?? state.proxyHost, + proxyPort: proxyPort ?? state.proxyPort, + proxyUsername: proxyUsername ?? state.proxyUsername, + proxyPassword: proxyPassword ?? state.proxyPassword, ); await setSettingsToSharedPrefs(state); } diff --git a/lib/screens/settings_page.dart b/lib/screens/settings_page.dart index 69ad4bb0f..bf3ca7ebb 100644 --- a/lib/screens/settings_page.dart +++ b/lib/screens/settings_page.dart @@ -7,6 +7,7 @@ import '../services/services.dart'; import '../utils/utils.dart'; import '../widgets/widgets.dart'; import '../consts.dart'; +import 'dart:developer' as developer; class SettingsPage extends ConsumerWidget { const SettingsPage({super.key}); @@ -55,12 +56,22 @@ class SettingsPage extends ConsumerWidget { hoverColor: kColorTransparent, title: const Text('Enable Proxy'), subtitle: const Text('Configure HTTP proxy settings'), - value: settings.isProxyEnabled, + value: ref.watch(settingsProvider.select((settings) => settings.isProxyEnabled)), onChanged: (bool? value) { - ref.read(settingsProvider.notifier).update(isProxyEnabled: value); + if (value != null) { + developer.log('Toggling proxy settings', name: 'settings_page', error: 'New value: $value'); + ref.read(settingsProvider.notifier).update( + isProxyEnabled: value, + proxyHost: value ? settings.proxyHost : '', + proxyPort: value ? settings.proxyPort : '', + proxyUsername: value ? settings.proxyUsername : null, + proxyPassword: value ? settings.proxyPassword : null, + ); + developer.log('Proxy settings updated', name: 'settings_page'); + } }, ), - if (settings.isProxyEnabled) ...[ + if (ref.watch(settingsProvider.select((settings) => settings.isProxyEnabled))) ...[ ListTile( title: TextField( decoration: const InputDecoration( From f32f9993aa9116206b844b0bf6057a09c56c36a5 Mon Sep 17 00:00:00 2001 From: Abhinav Sharma Date: Sat, 18 Jan 2025 13:37:12 +0530 Subject: [PATCH 03/11] chore: saving of proxy settings --- lib/models/settings_model.dart | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/lib/models/settings_model.dart b/lib/models/settings_model.dart index ff95f2b32..da73a2375 100644 --- a/lib/models/settings_model.dart +++ b/lib/models/settings_model.dart @@ -79,6 +79,12 @@ class SettingsModel { historyRetentionPeriod ?? this.historyRetentionPeriod, workspaceFolderPath: workspaceFolderPath ?? this.workspaceFolderPath, isSSLDisabled: isSSLDisabled ?? this.isSSLDisabled, + // Proxy settings + isProxyEnabled: isProxyEnabled ?? this.isProxyEnabled, + proxyHost: proxyHost ?? this.proxyHost, + proxyPort: proxyPort ?? this.proxyPort, + proxyUsername: proxyUsername ?? this.proxyUsername, + proxyPassword: proxyPassword ?? this.proxyPassword, ); } @@ -152,6 +158,11 @@ class SettingsModel { } final workspaceFolderPath = data["workspaceFolderPath"] as String?; final isSSLDisabled = data["isSSLDisabled"] as bool?; + final isProxyEnabled = data["isProxyEnabled"] as bool?; + final proxyHost = data["proxyHost"] as String?; + final proxyPort = data["proxyPort"] as String?; + final proxyUsername = data["proxyUsername"] as String?; + final proxyPassword = data["proxyPassword"] as String?; const sm = SettingsModel(); @@ -169,6 +180,11 @@ class SettingsModel { historyRetentionPeriod ?? HistoryRetentionPeriod.oneWeek, workspaceFolderPath: workspaceFolderPath, isSSLDisabled: isSSLDisabled, + isProxyEnabled: isProxyEnabled, + proxyHost: proxyHost, + proxyPort: proxyPort, + proxyUsername: proxyUsername, + proxyPassword: proxyPassword, ); } @@ -188,6 +204,12 @@ class SettingsModel { "historyRetentionPeriod": historyRetentionPeriod.name, "workspaceFolderPath": workspaceFolderPath, "isSSLDisabled": isSSLDisabled, + // Proxy settings + "isProxyEnabled": isProxyEnabled, + "proxyHost": proxyHost, + "proxyPort": proxyPort, + "proxyUsername": proxyUsername, + "proxyPassword": proxyPassword, }; } @@ -212,7 +234,12 @@ class SettingsModel { other.activeEnvironmentId == activeEnvironmentId && other.historyRetentionPeriod == historyRetentionPeriod && other.workspaceFolderPath == workspaceFolderPath && - other.isSSLDisabled == isSSLDisabled; + other.isSSLDisabled == isSSLDisabled && + other.isProxyEnabled == isProxyEnabled && + other.proxyHost == proxyHost && + other.proxyPort == proxyPort && + other.proxyUsername == proxyUsername && + other.proxyPassword == proxyPassword; } @override @@ -231,6 +258,11 @@ class SettingsModel { historyRetentionPeriod, workspaceFolderPath, isSSLDisabled, + isProxyEnabled, + proxyHost, + proxyPort, + proxyUsername, + proxyPassword, ); } } From 42414cf15d829ac4975922b5baf6cd0939e3b025 Mon Sep 17 00:00:00 2001 From: Abhinav Sharma Date: Wed, 22 Jan 2025 11:13:49 +0530 Subject: [PATCH 04/11] chore: final implementation of sending the request through proxy server --- lib/providers/collection_providers.dart | 5 ++ lib/providers/settings_providers.dart | 2 + .../lib/services/http_client_manager.dart | 48 ++++++++++++++++++- .../lib/services/http_service.dart | 14 +++++- 4 files changed, 66 insertions(+), 3 deletions(-) diff --git a/lib/providers/collection_providers.dart b/lib/providers/collection_providers.dart index 2fb830775..cddb99c1d 100644 --- a/lib/providers/collection_providers.dart +++ b/lib/providers/collection_providers.dart @@ -299,6 +299,11 @@ class CollectionStateNotifier substitutedHttpRequestModel, defaultUriScheme: defaultUriScheme, noSSL: noSSL, + isProxyEnabled: ref.read(settingsProvider).isProxyEnabled, + proxyHost: ref.read(settingsProvider).proxyHost, + proxyPort: ref.read(settingsProvider).proxyPort, + proxyUsername: ref.read(settingsProvider).proxyUsername, + proxyPassword: ref.read(settingsProvider).proxyPassword, ); late final RequestModel newRequestModel; diff --git a/lib/providers/settings_providers.dart b/lib/providers/settings_providers.dart index 478da1ca1..ef7dd38d2 100644 --- a/lib/providers/settings_providers.dart +++ b/lib/providers/settings_providers.dart @@ -38,6 +38,8 @@ class ThemeStateNotifier extends StateNotifier { String? proxyPort, String? proxyUsername, String? proxyPassword, + bool? useSystemProxy, + String? proxyBypassRules, }) async { state = SettingsModel( isDark: isDark ?? state.isDark, diff --git a/packages/apidash_core/lib/services/http_client_manager.dart b/packages/apidash_core/lib/services/http_client_manager.dart index bec23214f..bdda7bcd4 100644 --- a/packages/apidash_core/lib/services/http_client_manager.dart +++ b/packages/apidash_core/lib/services/http_client_manager.dart @@ -11,6 +11,36 @@ http.Client createHttpClientWithNoSSL() { return IOClient(ioClient); } +http.Client createHttpClientWithProxy( + String proxyHost, + String proxyPort, + {String? proxyUsername, + String? proxyPassword, + bool noSSL = false} +) { + var ioClient = HttpClient(); + + // Configure proxy settings + ioClient.findProxy = (uri) { + return 'PROXY $proxyHost:$proxyPort'; + }; + + // Configure proxy authentication if credentials are provided + if (proxyUsername != null && proxyPassword != null) { + ioClient.authenticate = (Uri url, String scheme, String? realm) async { + return true; + }; + } + + // Disable SSL verification if required + if (noSSL) { + ioClient.badCertificateCallback = + (X509Certificate cert, String host, int port) => true; + } + + return IOClient(ioClient); +} + class HttpClientManager { static final HttpClientManager _instance = HttpClientManager._internal(); static const int _maxCancelledRequests = 100; @@ -26,9 +56,23 @@ class HttpClientManager { http.Client createClient( String requestId, { bool noSSL = false, + String? proxyHost, + String? proxyPort, + String? proxyUsername, + String? proxyPassword, }) { - final client = - (noSSL && !kIsWeb) ? createHttpClientWithNoSSL() : http.Client(); + final client = proxyHost != null && proxyPort != null + ? createHttpClientWithProxy( + proxyHost, + proxyPort, + proxyUsername: proxyUsername, + proxyPassword: proxyPassword, + noSSL: noSSL + ) + : (noSSL && !kIsWeb) + ? createHttpClientWithNoSSL() + : http.Client(); + _clients[requestId] = client; return client; } diff --git a/packages/apidash_core/lib/services/http_service.dart b/packages/apidash_core/lib/services/http_service.dart index 0bbc05019..b7928f43a 100644 --- a/packages/apidash_core/lib/services/http_service.dart +++ b/packages/apidash_core/lib/services/http_service.dart @@ -16,9 +16,21 @@ Future<(HttpResponse?, Duration?, String?)> request( HttpRequestModel requestModel, { SupportedUriSchemes defaultUriScheme = kDefaultUriScheme, bool noSSL = false, + bool? isProxyEnabled, + String? proxyHost, + String? proxyPort, + String? proxyUsername, + String? proxyPassword, }) async { final clientManager = HttpClientManager(); - final client = clientManager.createClient(requestId, noSSL: noSSL); + final client = clientManager.createClient( + requestId, + noSSL: noSSL, + proxyHost: isProxyEnabled == true ? proxyHost : null, + proxyPort: isProxyEnabled == true ? proxyPort : null, + proxyUsername: isProxyEnabled == true ? proxyUsername : null, + proxyPassword: isProxyEnabled == true ? proxyPassword : null, + ); (Uri?, String?) uriRec = getValidRequestUri( requestModel.url, From 5ac4571e02cdef1cfc1024920e35eecc92d93a55 Mon Sep 17 00:00:00 2001 From: Abhinav Sharma Date: Fri, 31 Jan 2025 14:46:00 +0530 Subject: [PATCH 05/11] chore: remove proxy_provider --- lib/providers/proxy_provider.dart | 54 ------------------------------- 1 file changed, 54 deletions(-) delete mode 100644 lib/providers/proxy_provider.dart diff --git a/lib/providers/proxy_provider.dart b/lib/providers/proxy_provider.dart deleted file mode 100644 index cbb89f4d1..000000000 --- a/lib/providers/proxy_provider.dart +++ /dev/null @@ -1,54 +0,0 @@ -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import '../models/proxy_config.dart'; - -final proxyConfigProvider = StateNotifierProvider((ref) { - return ProxyNotifier(); -}); - -class ProxyNotifier extends StateNotifier { - ProxyNotifier() : super(ProxyConfig()) { - _loadConfig(); - } - - Future _loadConfig() async { - state = await ProxyConfig.load(); - } - - Future updateConfig({ - bool? isEnabled, - String? host, - String? port, - String? username, - String? password, - }) async { - state = ProxyConfig( - isEnabled: isEnabled ?? state.isEnabled, - host: host ?? state.host, - port: port ?? state.port, - username: username ?? state.username, - password: password ?? state.password, - ); - await ProxyConfig.save(state); - } - - String? getProxyUrl() { - if (!state.isEnabled || state.host.isEmpty || state.port.isEmpty) { - return null; - } - final auth = (state.username?.isNotEmpty ?? false) && (state.password?.isNotEmpty ?? false) - ? '${state.username}:${state.password}@' - : ''; - return 'http://$auth${state.host}:${state.port}'; - } - - // Debug method to print proxy configuration - void printProxyConfig() { - print('Proxy Configuration:'); - print('Enabled: ${state.isEnabled}'); - print('Host: ${state.host}'); - print('Port: ${state.port}'); - print('Username: ${state.username != null ? '***' : null}'); - print('Password: ${state.password != null ? '***' : null}'); - print('Proxy URL: ${getProxyUrl() ?? 'Not configured'}'); - } -} From 836adc412fe321a159b0d876d65f18d92f898dcd Mon Sep 17 00:00:00 2001 From: Abhinav Sharma Date: Fri, 31 Jan 2025 14:46:44 +0530 Subject: [PATCH 06/11] chore: create proxy model --- packages/apidash_core/lib/models/models.dart | 1 + .../lib/models/proxy_settings_model.dart | 18 ++ .../models/proxy_settings_model.freezed.dart | 221 ++++++++++++++++++ .../lib/models/proxy_settings_model.g.dart | 23 ++ 4 files changed, 263 insertions(+) create mode 100644 packages/apidash_core/lib/models/proxy_settings_model.dart create mode 100644 packages/apidash_core/lib/models/proxy_settings_model.freezed.dart create mode 100644 packages/apidash_core/lib/models/proxy_settings_model.g.dart diff --git a/packages/apidash_core/lib/models/models.dart b/packages/apidash_core/lib/models/models.dart index a33c6fddc..1a4998b16 100644 --- a/packages/apidash_core/lib/models/models.dart +++ b/packages/apidash_core/lib/models/models.dart @@ -1,2 +1,3 @@ export 'http_request_model.dart'; export 'http_response_model.dart'; +export 'proxy_settings_model.dart'; diff --git a/packages/apidash_core/lib/models/proxy_settings_model.dart b/packages/apidash_core/lib/models/proxy_settings_model.dart new file mode 100644 index 000000000..e88010310 --- /dev/null +++ b/packages/apidash_core/lib/models/proxy_settings_model.dart @@ -0,0 +1,18 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:json_annotation/json_annotation.dart'; + +part 'proxy_settings_model.freezed.dart'; +part 'proxy_settings_model.g.dart'; + +@freezed +class ProxySettings with _$ProxySettings { + const factory ProxySettings({ + @Default('') String host, + @Default('') String port, + String? username, + String? password, + }) = _ProxySettings; + + factory ProxySettings.fromJson(Map json) => + _$ProxySettingsFromJson(json); +} diff --git a/packages/apidash_core/lib/models/proxy_settings_model.freezed.dart b/packages/apidash_core/lib/models/proxy_settings_model.freezed.dart new file mode 100644 index 000000000..bc8b0a82d --- /dev/null +++ b/packages/apidash_core/lib/models/proxy_settings_model.freezed.dart @@ -0,0 +1,221 @@ +// 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 'proxy_settings_model.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(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'); + +ProxySettings _$ProxySettingsFromJson(Map json) { + return _ProxySettings.fromJson(json); +} + +/// @nodoc +mixin _$ProxySettings { + String get host => throw _privateConstructorUsedError; + String get port => throw _privateConstructorUsedError; + String? get username => throw _privateConstructorUsedError; + String? get password => throw _privateConstructorUsedError; + + /// Serializes this ProxySettings to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of ProxySettings + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $ProxySettingsCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ProxySettingsCopyWith<$Res> { + factory $ProxySettingsCopyWith( + ProxySettings value, $Res Function(ProxySettings) then) = + _$ProxySettingsCopyWithImpl<$Res, ProxySettings>; + @useResult + $Res call({String host, String port, String? username, String? password}); +} + +/// @nodoc +class _$ProxySettingsCopyWithImpl<$Res, $Val extends ProxySettings> + implements $ProxySettingsCopyWith<$Res> { + _$ProxySettingsCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of ProxySettings + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? host = null, + Object? port = null, + Object? username = freezed, + Object? password = freezed, + }) { + return _then(_value.copyWith( + host: null == host + ? _value.host + : host // ignore: cast_nullable_to_non_nullable + as String, + port: null == port + ? _value.port + : port // ignore: cast_nullable_to_non_nullable + as String, + username: freezed == username + ? _value.username + : username // ignore: cast_nullable_to_non_nullable + as String?, + password: freezed == password + ? _value.password + : password // ignore: cast_nullable_to_non_nullable + as String?, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$ProxySettingsImplCopyWith<$Res> + implements $ProxySettingsCopyWith<$Res> { + factory _$$ProxySettingsImplCopyWith( + _$ProxySettingsImpl value, $Res Function(_$ProxySettingsImpl) then) = + __$$ProxySettingsImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String host, String port, String? username, String? password}); +} + +/// @nodoc +class __$$ProxySettingsImplCopyWithImpl<$Res> + extends _$ProxySettingsCopyWithImpl<$Res, _$ProxySettingsImpl> + implements _$$ProxySettingsImplCopyWith<$Res> { + __$$ProxySettingsImplCopyWithImpl( + _$ProxySettingsImpl _value, $Res Function(_$ProxySettingsImpl) _then) + : super(_value, _then); + + /// Create a copy of ProxySettings + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? host = null, + Object? port = null, + Object? username = freezed, + Object? password = freezed, + }) { + return _then(_$ProxySettingsImpl( + host: null == host + ? _value.host + : host // ignore: cast_nullable_to_non_nullable + as String, + port: null == port + ? _value.port + : port // ignore: cast_nullable_to_non_nullable + as String, + username: freezed == username + ? _value.username + : username // ignore: cast_nullable_to_non_nullable + as String?, + password: freezed == password + ? _value.password + : password // ignore: cast_nullable_to_non_nullable + as String?, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$ProxySettingsImpl implements _ProxySettings { + const _$ProxySettingsImpl( + {this.host = '', this.port = '', this.username, this.password}); + + factory _$ProxySettingsImpl.fromJson(Map json) => + _$$ProxySettingsImplFromJson(json); + + @override + @JsonKey() + final String host; + @override + @JsonKey() + final String port; + @override + final String? username; + @override + final String? password; + + @override + String toString() { + return 'ProxySettings(host: $host, port: $port, username: $username, password: $password)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ProxySettingsImpl && + (identical(other.host, host) || other.host == host) && + (identical(other.port, port) || other.port == port) && + (identical(other.username, username) || + other.username == username) && + (identical(other.password, password) || + other.password == password)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash(runtimeType, host, port, username, password); + + /// Create a copy of ProxySettings + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$ProxySettingsImplCopyWith<_$ProxySettingsImpl> get copyWith => + __$$ProxySettingsImplCopyWithImpl<_$ProxySettingsImpl>(this, _$identity); + + @override + Map toJson() { + return _$$ProxySettingsImplToJson( + this, + ); + } +} + +abstract class _ProxySettings implements ProxySettings { + const factory _ProxySettings( + {final String host, + final String port, + final String? username, + final String? password}) = _$ProxySettingsImpl; + + factory _ProxySettings.fromJson(Map json) = + _$ProxySettingsImpl.fromJson; + + @override + String get host; + @override + String get port; + @override + String? get username; + @override + String? get password; + + /// Create a copy of ProxySettings + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ProxySettingsImplCopyWith<_$ProxySettingsImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/packages/apidash_core/lib/models/proxy_settings_model.g.dart b/packages/apidash_core/lib/models/proxy_settings_model.g.dart new file mode 100644 index 000000000..383e99761 --- /dev/null +++ b/packages/apidash_core/lib/models/proxy_settings_model.g.dart @@ -0,0 +1,23 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'proxy_settings_model.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$ProxySettingsImpl _$$ProxySettingsImplFromJson(Map json) => + _$ProxySettingsImpl( + host: json['host'] as String? ?? '', + port: json['port'] as String? ?? '', + username: json['username'] as String?, + password: json['password'] as String?, + ); + +Map _$$ProxySettingsImplToJson(_$ProxySettingsImpl instance) => + { + 'host': instance.host, + 'port': instance.port, + 'username': instance.username, + 'password': instance.password, + }; From 62d74596579823c6e4e76b285d95b8d3f1a7275a Mon Sep 17 00:00:00 2001 From: Abhinav Sharma Date: Fri, 31 Jan 2025 14:57:39 +0530 Subject: [PATCH 07/11] fix: remove the proxydetails fields from settings model --- lib/models/settings_model.dart | 46 ++----------------- .../lib/services/http_service.dart | 11 +---- 2 files changed, 6 insertions(+), 51 deletions(-) diff --git a/lib/models/settings_model.dart b/lib/models/settings_model.dart index da73a2375..faaeede5e 100644 --- a/lib/models/settings_model.dart +++ b/lib/models/settings_model.dart @@ -18,10 +18,6 @@ class SettingsModel { this.workspaceFolderPath, this.isSSLDisabled = false, this.isProxyEnabled = false, - this.proxyHost = '', - this.proxyPort = '', - this.proxyUsername, - this.proxyPassword, }); final bool isDark; @@ -39,10 +35,6 @@ class SettingsModel { // Proxy settings final bool isProxyEnabled; - final String proxyHost; - final String proxyPort; - final String? proxyUsername; - final String? proxyPassword; SettingsModel copyWith({ bool? isDark, @@ -57,21 +49,16 @@ class SettingsModel { HistoryRetentionPeriod? historyRetentionPeriod, String? workspaceFolderPath, bool? isSSLDisabled, - // Proxy settings bool? isProxyEnabled, - String? proxyHost, - String? proxyPort, - String? proxyUsername, - String? proxyPassword, }) { return SettingsModel( isDark: isDark ?? this.isDark, alwaysShowCollectionPaneScrollbar: alwaysShowCollectionPaneScrollbar ?? this.alwaysShowCollectionPaneScrollbar, size: size ?? this.size, + offset: offset ?? this.offset, defaultUriScheme: defaultUriScheme ?? this.defaultUriScheme, defaultCodeGenLang: defaultCodeGenLang ?? this.defaultCodeGenLang, - offset: offset ?? this.offset, saveResponses: saveResponses ?? this.saveResponses, promptBeforeClosing: promptBeforeClosing ?? this.promptBeforeClosing, activeEnvironmentId: activeEnvironmentId ?? this.activeEnvironmentId, @@ -79,12 +66,7 @@ class SettingsModel { historyRetentionPeriod ?? this.historyRetentionPeriod, workspaceFolderPath: workspaceFolderPath ?? this.workspaceFolderPath, isSSLDisabled: isSSLDisabled ?? this.isSSLDisabled, - // Proxy settings isProxyEnabled: isProxyEnabled ?? this.isProxyEnabled, - proxyHost: proxyHost ?? this.proxyHost, - proxyPort: proxyPort ?? this.proxyPort, - proxyUsername: proxyUsername ?? this.proxyUsername, - proxyPassword: proxyPassword ?? this.proxyPassword, ); } @@ -95,15 +77,16 @@ class SettingsModel { isDark: isDark, alwaysShowCollectionPaneScrollbar: alwaysShowCollectionPaneScrollbar, size: size, + offset: offset, defaultUriScheme: defaultUriScheme, defaultCodeGenLang: defaultCodeGenLang, - offset: offset, saveResponses: saveResponses, promptBeforeClosing: promptBeforeClosing, activeEnvironmentId: activeEnvironmentId, historyRetentionPeriod: historyRetentionPeriod, workspaceFolderPath: workspaceFolderPath, isSSLDisabled: isSSLDisabled, + isProxyEnabled: isProxyEnabled, ); } @@ -159,10 +142,6 @@ class SettingsModel { final workspaceFolderPath = data["workspaceFolderPath"] as String?; final isSSLDisabled = data["isSSLDisabled"] as bool?; final isProxyEnabled = data["isProxyEnabled"] as bool?; - final proxyHost = data["proxyHost"] as String?; - final proxyPort = data["proxyPort"] as String?; - final proxyUsername = data["proxyUsername"] as String?; - final proxyPassword = data["proxyPassword"] as String?; const sm = SettingsModel(); @@ -181,10 +160,6 @@ class SettingsModel { workspaceFolderPath: workspaceFolderPath, isSSLDisabled: isSSLDisabled, isProxyEnabled: isProxyEnabled, - proxyHost: proxyHost, - proxyPort: proxyPort, - proxyUsername: proxyUsername, - proxyPassword: proxyPassword, ); } @@ -204,12 +179,7 @@ class SettingsModel { "historyRetentionPeriod": historyRetentionPeriod.name, "workspaceFolderPath": workspaceFolderPath, "isSSLDisabled": isSSLDisabled, - // Proxy settings "isProxyEnabled": isProxyEnabled, - "proxyHost": proxyHost, - "proxyPort": proxyPort, - "proxyUsername": proxyUsername, - "proxyPassword": proxyPassword, }; } @@ -235,11 +205,7 @@ class SettingsModel { other.historyRetentionPeriod == historyRetentionPeriod && other.workspaceFolderPath == workspaceFolderPath && other.isSSLDisabled == isSSLDisabled && - other.isProxyEnabled == isProxyEnabled && - other.proxyHost == proxyHost && - other.proxyPort == proxyPort && - other.proxyUsername == proxyUsername && - other.proxyPassword == proxyPassword; + other.isProxyEnabled == isProxyEnabled; } @override @@ -259,10 +225,6 @@ class SettingsModel { workspaceFolderPath, isSSLDisabled, isProxyEnabled, - proxyHost, - proxyPort, - proxyUsername, - proxyPassword, ); } } diff --git a/packages/apidash_core/lib/services/http_service.dart b/packages/apidash_core/lib/services/http_service.dart index b7928f43a..2220c7d19 100644 --- a/packages/apidash_core/lib/services/http_service.dart +++ b/packages/apidash_core/lib/services/http_service.dart @@ -16,20 +16,13 @@ Future<(HttpResponse?, Duration?, String?)> request( HttpRequestModel requestModel, { SupportedUriSchemes defaultUriScheme = kDefaultUriScheme, bool noSSL = false, - bool? isProxyEnabled, - String? proxyHost, - String? proxyPort, - String? proxyUsername, - String? proxyPassword, + ProxySettings? proxySettings, }) async { final clientManager = HttpClientManager(); final client = clientManager.createClient( requestId, noSSL: noSSL, - proxyHost: isProxyEnabled == true ? proxyHost : null, - proxyPort: isProxyEnabled == true ? proxyPort : null, - proxyUsername: isProxyEnabled == true ? proxyUsername : null, - proxyPassword: isProxyEnabled == true ? proxyPassword : null, + proxySettings: proxySettings, ); (Uri?, String?) uriRec = getValidRequestUri( From 529640001fdd851976e876a401b61822c41047ff Mon Sep 17 00:00:00 2001 From: Abhinav Sharma Date: Fri, 31 Jan 2025 15:01:32 +0530 Subject: [PATCH 08/11] fix: revert to single custom client creation --- .../lib/services/http_client_manager.dart | 71 ++++++++----------- 1 file changed, 29 insertions(+), 42 deletions(-) diff --git a/packages/apidash_core/lib/services/http_client_manager.dart b/packages/apidash_core/lib/services/http_client_manager.dart index bdda7bcd4..4f9896e05 100644 --- a/packages/apidash_core/lib/services/http_client_manager.dart +++ b/packages/apidash_core/lib/services/http_client_manager.dart @@ -3,39 +3,36 @@ import 'dart:collection'; import 'package:flutter/foundation.dart'; import 'package:http/http.dart' as http; import 'package:http/io_client.dart'; +import 'package:apidash_core/models/models.dart'; -http.Client createHttpClientWithNoSSL() { - var ioClient = HttpClient() - ..badCertificateCallback = - (X509Certificate cert, String host, int port) => true; - return IOClient(ioClient); -} +http.Client createCustomHttpClient({ + bool noSSL = false, + ProxySettings? proxySettings, +}) { + if (kIsWeb) { + return http.Client(); + } -http.Client createHttpClientWithProxy( - String proxyHost, - String proxyPort, - {String? proxyUsername, - String? proxyPassword, - bool noSSL = false} -) { var ioClient = HttpClient(); - - // Configure proxy settings - ioClient.findProxy = (uri) { - return 'PROXY $proxyHost:$proxyPort'; - }; - // Configure proxy authentication if credentials are provided - if (proxyUsername != null && proxyPassword != null) { - ioClient.authenticate = (Uri url, String scheme, String? realm) async { - return true; - }; + // Configure SSL + if (noSSL) { + ioClient.badCertificateCallback = (X509Certificate cert, String host, int port) => true; } - // Disable SSL verification if required - if (noSSL) { - ioClient.badCertificateCallback = - (X509Certificate cert, String host, int port) => true; + // Configure proxy if enabled + if (proxySettings != null) { + // Set proxy server + ioClient.findProxy = (uri) { + return 'PROXY ${proxySettings.host}:${proxySettings.port}'; + }; + + // Configure proxy authentication if credentials are provided + if (proxySettings.username != null && proxySettings.password != null) { + ioClient.authenticate = (Uri url, String scheme, String? realm) async { + return true; + }; + } } return IOClient(ioClient); @@ -56,22 +53,12 @@ class HttpClientManager { http.Client createClient( String requestId, { bool noSSL = false, - String? proxyHost, - String? proxyPort, - String? proxyUsername, - String? proxyPassword, + ProxySettings? proxySettings, }) { - final client = proxyHost != null && proxyPort != null - ? createHttpClientWithProxy( - proxyHost, - proxyPort, - proxyUsername: proxyUsername, - proxyPassword: proxyPassword, - noSSL: noSSL - ) - : (noSSL && !kIsWeb) - ? createHttpClientWithNoSSL() - : http.Client(); + final client = createCustomHttpClient( + noSSL: noSSL, + proxySettings: proxySettings, + ); _clients[requestId] = client; return client; From 419df9e17b6f6f86292bbacd33e1fa3cb44a4853 Mon Sep 17 00:00:00 2001 From: Abhinav Sharma Date: Fri, 31 Jan 2025 22:09:26 +0530 Subject: [PATCH 09/11] fix: remove reinitialization of SettingsModel --- lib/models/settings_model.dart | 19 +++++++++---------- lib/providers/collection_providers.dart | 6 +----- lib/providers/settings_providers.dart | 16 +++------------- 3 files changed, 13 insertions(+), 28 deletions(-) diff --git a/lib/models/settings_model.dart b/lib/models/settings_model.dart index faaeede5e..03e4a3505 100644 --- a/lib/models/settings_model.dart +++ b/lib/models/settings_model.dart @@ -17,7 +17,7 @@ class SettingsModel { this.historyRetentionPeriod = HistoryRetentionPeriod.oneWeek, this.workspaceFolderPath, this.isSSLDisabled = false, - this.isProxyEnabled = false, + this.proxySettings = const ProxySettings(), }); final bool isDark; @@ -34,7 +34,7 @@ class SettingsModel { final bool isSSLDisabled; // Proxy settings - final bool isProxyEnabled; + final ProxySettings proxySettings; SettingsModel copyWith({ bool? isDark, @@ -49,7 +49,7 @@ class SettingsModel { HistoryRetentionPeriod? historyRetentionPeriod, String? workspaceFolderPath, bool? isSSLDisabled, - bool? isProxyEnabled, + ProxySettings? proxySettings, }) { return SettingsModel( isDark: isDark ?? this.isDark, @@ -66,7 +66,7 @@ class SettingsModel { historyRetentionPeriod ?? this.historyRetentionPeriod, workspaceFolderPath: workspaceFolderPath ?? this.workspaceFolderPath, isSSLDisabled: isSSLDisabled ?? this.isSSLDisabled, - isProxyEnabled: isProxyEnabled ?? this.isProxyEnabled, + proxySettings: proxySettings ?? this.proxySettings, ); } @@ -86,7 +86,7 @@ class SettingsModel { historyRetentionPeriod: historyRetentionPeriod, workspaceFolderPath: workspaceFolderPath, isSSLDisabled: isSSLDisabled, - isProxyEnabled: isProxyEnabled, + proxySettings: proxySettings, ); } @@ -141,7 +141,6 @@ class SettingsModel { } final workspaceFolderPath = data["workspaceFolderPath"] as String?; final isSSLDisabled = data["isSSLDisabled"] as bool?; - final isProxyEnabled = data["isProxyEnabled"] as bool?; const sm = SettingsModel(); @@ -159,7 +158,7 @@ class SettingsModel { historyRetentionPeriod ?? HistoryRetentionPeriod.oneWeek, workspaceFolderPath: workspaceFolderPath, isSSLDisabled: isSSLDisabled, - isProxyEnabled: isProxyEnabled, + proxySettings: ProxySettings.fromJson(data["proxySettings"]), ); } @@ -179,7 +178,7 @@ class SettingsModel { "historyRetentionPeriod": historyRetentionPeriod.name, "workspaceFolderPath": workspaceFolderPath, "isSSLDisabled": isSSLDisabled, - "isProxyEnabled": isProxyEnabled, + "proxySettings": proxySettings, }; } @@ -205,7 +204,7 @@ class SettingsModel { other.historyRetentionPeriod == historyRetentionPeriod && other.workspaceFolderPath == workspaceFolderPath && other.isSSLDisabled == isSSLDisabled && - other.isProxyEnabled == isProxyEnabled; + other.proxySettings == proxySettings; } @override @@ -224,7 +223,7 @@ class SettingsModel { historyRetentionPeriod, workspaceFolderPath, isSSLDisabled, - isProxyEnabled, + proxySettings, ); } } diff --git a/lib/providers/collection_providers.dart b/lib/providers/collection_providers.dart index cddb99c1d..8704c970d 100644 --- a/lib/providers/collection_providers.dart +++ b/lib/providers/collection_providers.dart @@ -299,11 +299,7 @@ class CollectionStateNotifier substitutedHttpRequestModel, defaultUriScheme: defaultUriScheme, noSSL: noSSL, - isProxyEnabled: ref.read(settingsProvider).isProxyEnabled, - proxyHost: ref.read(settingsProvider).proxyHost, - proxyPort: ref.read(settingsProvider).proxyPort, - proxyUsername: ref.read(settingsProvider).proxyUsername, - proxyPassword: ref.read(settingsProvider).proxyPassword, + proxySettings: ref.read(settingsProvider).proxySettings, ); late final RequestModel newRequestModel; diff --git a/lib/providers/settings_providers.dart b/lib/providers/settings_providers.dart index ef7dd38d2..7825d7868 100644 --- a/lib/providers/settings_providers.dart +++ b/lib/providers/settings_providers.dart @@ -33,15 +33,9 @@ class ThemeStateNotifier extends StateNotifier { HistoryRetentionPeriod? historyRetentionPeriod, String? workspaceFolderPath, bool? isSSLDisabled, - bool? isProxyEnabled, - String? proxyHost, - String? proxyPort, - String? proxyUsername, - String? proxyPassword, - bool? useSystemProxy, - String? proxyBypassRules, + ProxySettings? proxySettings, }) async { - state = SettingsModel( + state = state.copyWith( isDark: isDark ?? state.isDark, alwaysShowCollectionPaneScrollbar: alwaysShowCollectionPaneScrollbar ?? state.alwaysShowCollectionPaneScrollbar, size: size ?? state.size, @@ -54,11 +48,7 @@ class ThemeStateNotifier extends StateNotifier { historyRetentionPeriod: historyRetentionPeriod ?? state.historyRetentionPeriod, workspaceFolderPath: workspaceFolderPath ?? state.workspaceFolderPath, isSSLDisabled: isSSLDisabled ?? state.isSSLDisabled, - isProxyEnabled: isProxyEnabled ?? state.isProxyEnabled, - proxyHost: proxyHost ?? state.proxyHost, - proxyPort: proxyPort ?? state.proxyPort, - proxyUsername: proxyUsername ?? state.proxyUsername, - proxyPassword: proxyPassword ?? state.proxyPassword, + proxySettings: proxySettings ?? state.proxySettings, ); await setSettingsToSharedPrefs(state); } From 0469f98ee561fdaa446a89349c50b5283fbca1b5 Mon Sep 17 00:00:00 2001 From: Abhinav Sharma Date: Mon, 3 Feb 2025 18:06:57 +0530 Subject: [PATCH 10/11] chore: create separate widget for proxy dialog --- lib/models/settings_model.dart | 23 +++- lib/providers/settings_providers.dart | 35 +++--- lib/screens/settings_page.dart | 79 +++---------- lib/widgets/proxy_settings_dialog.dart | 109 ++++++++++++++++++ .../lib/models/proxy_settings_model.dart | 1 - 5 files changed, 162 insertions(+), 85 deletions(-) create mode 100644 lib/widgets/proxy_settings_dialog.dart diff --git a/lib/models/settings_model.dart b/lib/models/settings_model.dart index 03e4a3505..add981e57 100644 --- a/lib/models/settings_model.dart +++ b/lib/models/settings_model.dart @@ -34,7 +34,7 @@ class SettingsModel { final bool isSSLDisabled; // Proxy settings - final ProxySettings proxySettings; + final ProxySettings? proxySettings; SettingsModel copyWith({ bool? isDark, @@ -66,7 +66,7 @@ class SettingsModel { historyRetentionPeriod ?? this.historyRetentionPeriod, workspaceFolderPath: workspaceFolderPath ?? this.workspaceFolderPath, isSSLDisabled: isSSLDisabled ?? this.isSSLDisabled, - proxySettings: proxySettings ?? this.proxySettings, + proxySettings: proxySettings, ); } @@ -99,13 +99,16 @@ class SettingsModel { final dx = data["dx"] as double?; final dy = data["dy"] as double?; Size? size; + if (width != null && height != null) { size = Size(width, height); } Offset? offset; + if (dx != null && dy != null) { offset = Offset(dx, dy); } + final defaultUriSchemeStr = data["defaultUriScheme"] as String?; SupportedUriSchemes? defaultUriScheme; if (defaultUriSchemeStr != null) { @@ -116,6 +119,7 @@ class SettingsModel { // pass } } + final defaultCodeGenLangStr = data["defaultCodeGenLang"] as String?; CodegenLanguage? defaultCodeGenLang; if (defaultCodeGenLangStr != null) { @@ -126,6 +130,7 @@ class SettingsModel { // pass } } + final saveResponses = data["saveResponses"] as bool?; final promptBeforeClosing = data["promptBeforeClosing"] as bool?; final activeEnvironmentId = data["activeEnvironmentId"] as String?; @@ -139,9 +144,19 @@ class SettingsModel { // pass } } + final workspaceFolderPath = data["workspaceFolderPath"] as String?; final isSSLDisabled = data["isSSLDisabled"] as bool?; + ProxySettings? proxySettings; + if (data["proxySettings"] != null) { + try { + proxySettings = ProxySettings.fromJson(Map.from(data["proxySettings"])); + } catch (e) { + // pass + } + } + const sm = SettingsModel(); return sm.copyWith( @@ -158,7 +173,7 @@ class SettingsModel { historyRetentionPeriod ?? HistoryRetentionPeriod.oneWeek, workspaceFolderPath: workspaceFolderPath, isSSLDisabled: isSSLDisabled, - proxySettings: ProxySettings.fromJson(data["proxySettings"]), + proxySettings: proxySettings, ); } @@ -178,7 +193,7 @@ class SettingsModel { "historyRetentionPeriod": historyRetentionPeriod.name, "workspaceFolderPath": workspaceFolderPath, "isSSLDisabled": isSSLDisabled, - "proxySettings": proxySettings, + "proxySettings": proxySettings?.toJson(), }; } diff --git a/lib/providers/settings_providers.dart b/lib/providers/settings_providers.dart index 7825d7868..dccfbebb3 100644 --- a/lib/providers/settings_providers.dart +++ b/lib/providers/settings_providers.dart @@ -35,21 +35,26 @@ class ThemeStateNotifier extends StateNotifier { bool? isSSLDisabled, ProxySettings? proxySettings, }) async { - state = state.copyWith( - isDark: isDark ?? state.isDark, - alwaysShowCollectionPaneScrollbar: alwaysShowCollectionPaneScrollbar ?? state.alwaysShowCollectionPaneScrollbar, - size: size ?? state.size, - offset: offset ?? state.offset, - defaultUriScheme: defaultUriScheme ?? state.defaultUriScheme, - defaultCodeGenLang: defaultCodeGenLang ?? state.defaultCodeGenLang, - saveResponses: saveResponses ?? state.saveResponses, - promptBeforeClosing: promptBeforeClosing ?? state.promptBeforeClosing, - activeEnvironmentId: activeEnvironmentId ?? state.activeEnvironmentId, - historyRetentionPeriod: historyRetentionPeriod ?? state.historyRetentionPeriod, - workspaceFolderPath: workspaceFolderPath ?? state.workspaceFolderPath, - isSSLDisabled: isSSLDisabled ?? state.isSSLDisabled, - proxySettings: proxySettings ?? state.proxySettings, + + final newState = state.copyWith( + isDark: isDark, + alwaysShowCollectionPaneScrollbar: alwaysShowCollectionPaneScrollbar, + size: size, + offset: offset, + defaultUriScheme: defaultUriScheme, + defaultCodeGenLang: defaultCodeGenLang, + saveResponses: saveResponses, + promptBeforeClosing: promptBeforeClosing, + activeEnvironmentId: activeEnvironmentId, + historyRetentionPeriod: historyRetentionPeriod, + workspaceFolderPath: workspaceFolderPath, + isSSLDisabled: isSSLDisabled, + proxySettings: proxySettings, ); - await setSettingsToSharedPrefs(state); + + if (newState != state) { + state = newState; + await setSettingsToSharedPrefs(state); + } } } diff --git a/lib/screens/settings_page.dart b/lib/screens/settings_page.dart index bf3ca7ebb..05f145d8e 100644 --- a/lib/screens/settings_page.dart +++ b/lib/screens/settings_page.dart @@ -1,3 +1,4 @@ +import 'package:apidash/widgets/proxy_settings_dialog.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -7,7 +8,6 @@ import '../services/services.dart'; import '../utils/utils.dart'; import '../widgets/widgets.dart'; import '../consts.dart'; -import 'dart:developer' as developer; class SettingsPage extends ConsumerWidget { const SettingsPage({super.key}); @@ -51,75 +51,24 @@ class SettingsPage extends ConsumerWidget { ref.read(settingsProvider.notifier).update(isDark: value); }, ), - // Proxy Settings Section SwitchListTile( - hoverColor: kColorTransparent, - title: const Text('Enable Proxy'), - subtitle: const Text('Configure HTTP proxy settings'), - value: ref.watch(settingsProvider.select((settings) => settings.isProxyEnabled)), - onChanged: (bool? value) { - if (value != null) { - developer.log('Toggling proxy settings', name: 'settings_page', error: 'New value: $value'); - ref.read(settingsProvider.notifier).update( - isProxyEnabled: value, - proxyHost: value ? settings.proxyHost : '', - proxyPort: value ? settings.proxyPort : '', - proxyUsername: value ? settings.proxyUsername : null, - proxyPassword: value ? settings.proxyPassword : null, + title: const Text('Proxy Settings'), + subtitle: Text(ref.watch(settingsProvider).proxySettings != null + ? 'Enabled - ${ref.watch(settingsProvider).proxySettings!.host}:${ref.watch(settingsProvider).proxySettings!.port}' + : 'Disabled' + ), + value: ref.watch(settingsProvider).proxySettings != null, + onChanged: (bool value) { + if (!value) { + ref.read(settingsProvider.notifier).update(proxySettings: null); + } else { + showDialog( + context: context, + builder: (context) => const ProxySettingsDialog(), ); - developer.log('Proxy settings updated', name: 'settings_page'); } }, ), - if (ref.watch(settingsProvider.select((settings) => settings.isProxyEnabled))) ...[ - ListTile( - title: TextField( - decoration: const InputDecoration( - labelText: 'Proxy Host', - hintText: 'e.g., localhost', - ), - controller: TextEditingController(text: settings.proxyHost), - onChanged: (value) { - ref.read(settingsProvider.notifier).update(proxyHost: value); - }, - ), - ), - ListTile( - title: TextField( - decoration: const InputDecoration( - labelText: 'Proxy Port', - hintText: 'e.g., 8080', - ), - controller: TextEditingController(text: settings.proxyPort), - onChanged: (value) { - ref.read(settingsProvider.notifier).update(proxyPort: value); - }, - ), - ), - ListTile( - title: TextField( - decoration: const InputDecoration( - labelText: 'Username (Optional)', - ), - controller: TextEditingController(text: settings.proxyUsername), - onChanged: (value) { - ref.read(settingsProvider.notifier).update(proxyUsername: value); - }, - ), - ), - ListTile( - title: TextField( - decoration: const InputDecoration( - labelText: 'Password (Optional)', - ), - obscureText: true, - controller: TextEditingController(text: settings.proxyPassword), - onChanged: (value) { - ref.read(settingsProvider.notifier).update(proxyPassword: value); - }, - ), - ), - ], SwitchListTile( hoverColor: kColorTransparent, title: const Text('Collection Pane Scrollbar Visiblity'), diff --git a/lib/widgets/proxy_settings_dialog.dart b/lib/widgets/proxy_settings_dialog.dart new file mode 100644 index 000000000..ffde5ee68 --- /dev/null +++ b/lib/widgets/proxy_settings_dialog.dart @@ -0,0 +1,109 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:apidash_core/models/models.dart'; +import 'package:apidash/providers/settings_providers.dart'; + +class ProxySettingsDialog extends ConsumerStatefulWidget { + const ProxySettingsDialog({super.key}); + + @override + ConsumerState createState() => + _ProxySettingsDialogState(); +} + +class _ProxySettingsDialogState extends ConsumerState { + late TextEditingController _hostController; + late TextEditingController _portController; + late TextEditingController _usernameController; + late TextEditingController _passwordController; + + @override + void initState() { + super.initState(); + final settings = ref.read(settingsProvider); + final proxy = settings.proxySettings; + _hostController = TextEditingController(text: proxy?.host ?? ''); + _portController = TextEditingController(text: proxy?.port ?? ''); + _usernameController = TextEditingController(text: proxy?.username ?? ''); + _passwordController = TextEditingController(text: proxy?.password ?? ''); + } + + @override + void dispose() { + _hostController.dispose(); + _portController.dispose(); + _usernameController.dispose(); + _passwordController.dispose(); + super.dispose(); + } + + void _saveSettings() { + if (_hostController.text.isNotEmpty && _portController.text.isNotEmpty) { + final newProxy = ProxySettings( + host: _hostController.text, + port: _portController.text, + username: _usernameController.text.isEmpty ? null : _usernameController.text, + password: _passwordController.text.isEmpty ? null : _passwordController.text, + ); + ref.read(settingsProvider.notifier).update(proxySettings: newProxy); + Navigator.of(context).pop(); + } + else{ + ref.read(settingsProvider.notifier).update(proxySettings: null); + } + } + + @override + Widget build(BuildContext context) { + return AlertDialog( + title: const Text('Proxy Settings'), + content: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + TextField( + controller: _hostController, + decoration: const InputDecoration( + labelText: 'Proxy Host', + hintText: 'e.g., localhost', + ), + ), + const SizedBox(height: 8), + TextField( + controller: _portController, + decoration: const InputDecoration( + labelText: 'Proxy Port', + hintText: 'e.g., 8080', + ), + ), + const SizedBox(height: 8), + TextField( + controller: _usernameController, + decoration: const InputDecoration( + labelText: 'Username (Optional)', + ), + ), + const SizedBox(height: 8), + TextField( + controller: _passwordController, + decoration: const InputDecoration( + labelText: 'Password (Optional)', + ), + obscureText: true, + ), + ], + ), + ), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: const Text('Cancel'), + ), + TextButton( + onPressed: _saveSettings, + child: const Text('Save'), + ), + ], + ); + } +} diff --git a/packages/apidash_core/lib/models/proxy_settings_model.dart b/packages/apidash_core/lib/models/proxy_settings_model.dart index e88010310..9fded3506 100644 --- a/packages/apidash_core/lib/models/proxy_settings_model.dart +++ b/packages/apidash_core/lib/models/proxy_settings_model.dart @@ -1,5 +1,4 @@ import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:json_annotation/json_annotation.dart'; part 'proxy_settings_model.freezed.dart'; part 'proxy_settings_model.g.dart'; From 15bca273eece2faa5ef780ad24858d0f4a0377de Mon Sep 17 00:00:00 2001 From: Abhinav Sharma Date: Mon, 3 Feb 2025 18:22:34 +0530 Subject: [PATCH 11/11] chore: add tests for proxy feature --- .../models/proxy_settings_model_test.dart | 55 +++++++++ test/models/settings_model_test.dart | 44 ++++++- test/widgets/proxy_settings_dialog_test.dart | 116 ++++++++++++++++++ 3 files changed, 214 insertions(+), 1 deletion(-) create mode 100644 packages/apidash_core/test/models/proxy_settings_model_test.dart create mode 100644 test/widgets/proxy_settings_dialog_test.dart diff --git a/packages/apidash_core/test/models/proxy_settings_model_test.dart b/packages/apidash_core/test/models/proxy_settings_model_test.dart new file mode 100644 index 000000000..ac7917df9 --- /dev/null +++ b/packages/apidash_core/test/models/proxy_settings_model_test.dart @@ -0,0 +1,55 @@ +import 'package:test/test.dart'; +import 'package:apidash_core/models/proxy_settings_model.dart'; + +void main() { + const proxySettings = ProxySettings( + host: 'localhost', + port: '8080', + username: 'user', + password: 'pass', + ); + + test('Testing toJson()', () { + const expectedResult = { + 'host': 'localhost', + 'port': '8080', + 'username': 'user', + 'password': 'pass', + }; + expect(proxySettings.toJson(), expectedResult); + }); + + test('Testing fromJson()', () { + const input = { + 'host': 'localhost', + 'port': '8080', + 'username': 'user', + 'password': 'pass', + }; + expect(ProxySettings.fromJson(input), proxySettings); + }); + + test('Testing default values', () { + const defaultSettings = ProxySettings(); + expect(defaultSettings.host, ''); + expect(defaultSettings.port, ''); + expect(defaultSettings.username, null); + expect(defaultSettings.password, null); + }); + + test('Testing equality', () { + const settings1 = ProxySettings( + host: 'localhost', + port: '8080', + username: 'user', + password: 'pass', + ); + const settings2 = ProxySettings( + host: 'localhost', + port: '8080', + username: 'user', + password: 'pass', + ); + expect(settings1, settings2); + }); +} diff --git a/test/models/settings_model_test.dart b/test/models/settings_model_test.dart index 1928fd0e3..9cbf592aa 100644 --- a/test/models/settings_model_test.dart +++ b/test/models/settings_model_test.dart @@ -5,6 +5,13 @@ import 'package:apidash/models/settings_model.dart'; import 'package:apidash/consts.dart'; void main() { + const proxySettings = ProxySettings( + host: 'localhost', + port: '8080', + username: 'user', + password: 'pass', + ); + const sm = SettingsModel( isDark: false, alwaysShowCollectionPaneScrollbar: true, @@ -18,6 +25,7 @@ void main() { historyRetentionPeriod: HistoryRetentionPeriod.oneWeek, workspaceFolderPath: null, isSSLDisabled: true, + proxySettings: proxySettings, ); test('Testing toJson()', () { @@ -36,6 +44,12 @@ void main() { "historyRetentionPeriod": "oneWeek", "workspaceFolderPath": null, "isSSLDisabled": true, + "proxySettings": { + "host": "localhost", + "port": "8080", + "username": "user", + "password": "pass", + }, }; expect(sm.toJson(), expectedResult); }); @@ -56,6 +70,12 @@ void main() { "historyRetentionPeriod": "oneWeek", "workspaceFolderPath": null, "isSSLDisabled": true, + "proxySettings": { + "host": "localhost", + "port": "8080", + "username": "user", + "password": "pass", + }, }; expect(SettingsModel.fromJson(input), sm); }); @@ -73,12 +93,14 @@ void main() { activeEnvironmentId: null, historyRetentionPeriod: HistoryRetentionPeriod.oneWeek, isSSLDisabled: false, + proxySettings: null, ); expect( sm.copyWith( isDark: true, saveResponses: false, isSSLDisabled: false, + proxySettings: null, ), expectedResult); }); @@ -98,11 +120,31 @@ void main() { "activeEnvironmentId": null, "historyRetentionPeriod": "oneWeek", "workspaceFolderPath": null, - "isSSLDisabled": true + "isSSLDisabled": true, + "proxySettings": { + "host": "localhost", + "port": "8080", + "username": "user", + "password": "pass" + } }'''; expect(sm.toString(), expectedResult); }); + test('Testing proxy settings update', () { + const newProxySettings = ProxySettings( + host: 'proxy.example.com', + port: '3128', + ); + final updatedSettings = sm.copyWith(proxySettings: newProxySettings); + expect(updatedSettings.proxySettings, newProxySettings); + }); + + test('Testing proxy settings disable', () { + final disabledSettings = sm.copyWith(proxySettings: null); + expect(disabledSettings.proxySettings, null); + }); + test('Testing hashcode', () { expect(sm.hashCode, greaterThan(0)); }); diff --git a/test/widgets/proxy_settings_dialog_test.dart b/test/widgets/proxy_settings_dialog_test.dart new file mode 100644 index 000000000..185de1bb8 --- /dev/null +++ b/test/widgets/proxy_settings_dialog_test.dart @@ -0,0 +1,116 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:apidash/widgets/proxy_settings_dialog.dart'; + +void main() { + testWidgets('ProxySettingsDialog shows correctly', (WidgetTester tester) async { + await tester.pumpWidget( + ProviderScope( + child: MaterialApp( + home: Scaffold( + body: Builder( + builder: (context) => TextButton( + onPressed: () { + showDialog( + context: context, + builder: (context) => const ProxySettingsDialog(), + ); + }, + child: const Text('Show Dialog'), + ), + ), + ), + ), + ), + ); + + // Open dialog + await tester.tap(find.text('Show Dialog')); + await tester.pumpAndSettle(); + + // Verify dialog is shown + expect(find.byType(ProxySettingsDialog), findsOneWidget); + expect(find.text('Proxy Settings'), findsOneWidget); + expect(find.text('Proxy Host'), findsOneWidget); + expect(find.text('Proxy Port'), findsOneWidget); + expect(find.text('Username (Optional)'), findsOneWidget); + expect(find.text('Password (Optional)'), findsOneWidget); + }); + + testWidgets('ProxySettingsDialog saves settings', (WidgetTester tester) async { + await tester.pumpWidget( + ProviderScope( + child: MaterialApp( + home: Scaffold( + body: Builder( + builder: (context) => TextButton( + onPressed: () { + showDialog( + context: context, + builder: (context) => const ProxySettingsDialog(), + ); + }, + child: const Text('Show Dialog'), + ), + ), + ), + ), + ), + ); + + // Open dialog + await tester.tap(find.text('Show Dialog')); + await tester.pumpAndSettle(); + + // Enter proxy settings + await tester.enterText(find.widgetWithText(TextField, 'Proxy Host'), 'localhost'); + await tester.enterText(find.widgetWithText(TextField, 'Proxy Port'), '8080'); + await tester.enterText(find.widgetWithText(TextField, 'Username (Optional)'), 'user'); + await tester.enterText(find.widgetWithText(TextField, 'Password (Optional)'), 'pass'); + + // Save settings + await tester.tap(find.text('Save')); + await tester.pumpAndSettle(); + + // Verify dialog is closed + expect(find.byType(ProxySettingsDialog), findsNothing); + }); + + testWidgets('ProxySettingsDialog cancels without saving', (WidgetTester tester) async { + await tester.pumpWidget( + ProviderScope( + child: MaterialApp( + home: Scaffold( + body: Builder( + builder: (context) => TextButton( + onPressed: () { + showDialog( + context: context, + builder: (context) => const ProxySettingsDialog(), + ); + }, + child: const Text('Show Dialog'), + ), + ), + ), + ), + ), + ); + + // Open dialog + await tester.tap(find.text('Show Dialog')); + await tester.pumpAndSettle(); + + // Enter proxy settings + await tester.enterText(find.widgetWithText(TextField, 'Proxy Host'), 'localhost'); + await tester.enterText(find.widgetWithText(TextField, 'Proxy Port'), '8080'); + + // Cancel without saving + await tester.tap(find.text('Cancel')); + await tester.pumpAndSettle(); + + // Verify dialog is closed + expect(find.byType(ProxySettingsDialog), findsNothing); + }); +}