Add proxy routing and sing-box support
This commit is contained in:
@@ -0,0 +1,180 @@
|
||||
/*
|
||||
* Copyright (c) 2024-2026 Fabian Freund.
|
||||
*
|
||||
* This file is part of WebLibre
|
||||
* (see https://weblibre.eu).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_singbox_proxy/flutter_singbox_proxy.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:weblibre/core/logger.dart';
|
||||
import 'package:weblibre/features/geckoview/features/tabs/data/models/container_data.dart';
|
||||
import 'package:weblibre/features/proxy/data/proxy_connection.dart';
|
||||
import 'package:weblibre/features/proxy/domain/providers/proxy_connection_options.dart';
|
||||
import 'package:weblibre/features/proxy/domain/repositories/singbox_proxy_runtime.dart';
|
||||
import 'package:weblibre/features/tor/presentation/controllers/start_tor_proxy.dart';
|
||||
import 'package:weblibre/features/tor/presentation/widgets/tor_dialog.dart';
|
||||
import 'package:weblibre/utils/ui_helper.dart';
|
||||
|
||||
/// Single entry point that prompts the user to start whichever proxy backend
|
||||
/// a container is configured to use. No-op when the container has no proxy
|
||||
/// assigned or when the relevant backend is already running.
|
||||
Future<bool> ensureProxyStartedForContainer(
|
||||
BuildContext context,
|
||||
WidgetRef ref,
|
||||
ContainerData container,
|
||||
) async {
|
||||
final proxyConnectionId = container.metadata.proxyConnectionId;
|
||||
if (proxyConnectionId == null) return true;
|
||||
|
||||
if (proxyConnectionId is TorProxyConnectionId) {
|
||||
return await _ensureTorStarted(context, ref);
|
||||
}
|
||||
|
||||
if (proxyConnectionId is SingboxProxyConnectionId) {
|
||||
return await _maybeStartSingboxProxyForContainer(context, ref, container);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Future<bool> _ensureTorStarted(BuildContext context, WidgetRef ref) async {
|
||||
final shouldPrompt = await ref
|
||||
.read(startProxyControllerProvider.notifier)
|
||||
.shouldPromptProxyStart();
|
||||
if (!context.mounted) return false;
|
||||
if (!shouldPrompt) return true;
|
||||
|
||||
final dialogResult = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) => const TorDialog(),
|
||||
);
|
||||
|
||||
if (dialogResult == true) {
|
||||
await ref.read(startProxyControllerProvider.notifier).startProxy();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
Future<bool> _maybeStartSingboxProxyForContainer(
|
||||
BuildContext context,
|
||||
WidgetRef ref,
|
||||
ContainerData container,
|
||||
) async {
|
||||
final proxyConnectionId = container.metadata.proxyConnectionId;
|
||||
if (proxyConnectionId == null) return true;
|
||||
|
||||
if (proxyConnectionId is! SingboxProxyConnectionId) return true;
|
||||
|
||||
// Await any start/stop in flight so we don't prompt the user a second time
|
||||
// while a start they already triggered is still resolving. If the runtime
|
||||
// provider is already in AsyncError, keep treating that as "not running" so
|
||||
// the user can retry from the container entry point.
|
||||
final runtimeState = ref.read(singboxProxyRuntimeRepositoryProvider);
|
||||
final resolvedRuntimeState = await switch (runtimeState) {
|
||||
AsyncData(:final value) => Future.value(value),
|
||||
AsyncLoading() => _resolveRuntimeStateForPrompt(ref),
|
||||
AsyncError(:final error, :final stackTrace) => Future.value(
|
||||
_runtimeStateRetryFallback(
|
||||
error: error,
|
||||
stackTrace: stackTrace,
|
||||
message:
|
||||
'Singbox proxy runtime is in error state; continuing so the user can retry startup',
|
||||
),
|
||||
),
|
||||
};
|
||||
if (!context.mounted) return false;
|
||||
|
||||
final isRunning = resolvedRuntimeState.endpoints.any(
|
||||
(endpoint) => endpoint.profileId == proxyConnectionId.encode(),
|
||||
);
|
||||
|
||||
if (isRunning) return true;
|
||||
|
||||
final proxyTitle = proxyConnectionTitle(
|
||||
ref.read(proxyConnectionOptionsProvider),
|
||||
proxyConnectionId,
|
||||
);
|
||||
final shouldStart = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
icon: const Icon(Icons.route_outlined),
|
||||
title: const Text('Start Proxy Connection?'),
|
||||
content: Text(
|
||||
'This container uses $proxyTitle, but that connection is not running. Start it now?',
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context, false),
|
||||
child: const Text('Cancel'),
|
||||
),
|
||||
FilledButton(
|
||||
onPressed: () => Navigator.pop(context, true),
|
||||
child: const Text('Start'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
if (shouldStart != true) return false;
|
||||
|
||||
try {
|
||||
await ref
|
||||
.read(singboxProxyRuntimeRepositoryProvider.notifier)
|
||||
.startProfile(proxyConnectionId.profileId);
|
||||
return true;
|
||||
} catch (error, stackTrace) {
|
||||
logger.e(
|
||||
'Failed to start singbox proxy profile ${proxyConnectionId.profileId}',
|
||||
error: error,
|
||||
stackTrace: stackTrace,
|
||||
);
|
||||
if (context.mounted) {
|
||||
showErrorMessage(context, 'Failed to start proxy: $error');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<SingboxProxyRuntimeState> _resolveRuntimeStateForPrompt(
|
||||
WidgetRef ref,
|
||||
) async {
|
||||
try {
|
||||
return await ref.read(singboxProxyRuntimeRepositoryProvider.future);
|
||||
} catch (error, stackTrace) {
|
||||
return _runtimeStateRetryFallback(
|
||||
error: error,
|
||||
stackTrace: stackTrace,
|
||||
message:
|
||||
'Singbox proxy runtime is unresolved; continuing so the user can retry startup',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
SingboxProxyRuntimeState _runtimeStateRetryFallback({
|
||||
required Object error,
|
||||
required StackTrace stackTrace,
|
||||
required String message,
|
||||
}) {
|
||||
logger.w(message, error: error, stackTrace: stackTrace);
|
||||
return SingboxProxyRuntimeState(
|
||||
status: SingboxProxyRuntimeStatus.error,
|
||||
endpoints: const [],
|
||||
message: error.toString(),
|
||||
);
|
||||
}
|
||||
+391
@@ -0,0 +1,391 @@
|
||||
/*
|
||||
* Copyright (c) 2024-2026 Fabian Freund.
|
||||
*
|
||||
* This file is part of WebLibre
|
||||
* (see https://weblibre.eu).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:copy_with_extension/copy_with_extension.dart';
|
||||
import 'package:fast_equatable/fast_equatable.dart';
|
||||
import 'package:flutter_singbox_proxy/flutter_singbox_proxy.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:weblibre/core/uuid.dart';
|
||||
import 'package:weblibre/features/proxy/data/forms/singbox_form_specs.dart';
|
||||
import 'package:weblibre/features/proxy/data/models/proxy_profile_seed.dart';
|
||||
import 'package:weblibre/features/proxy/domain/repositories/singbox_proxy_credentials.dart';
|
||||
import 'package:weblibre/features/proxy/domain/repositories/singbox_proxy_profiles.dart';
|
||||
import 'package:weblibre/features/proxy/domain/repositories/singbox_proxy_runtime.dart';
|
||||
import 'package:weblibre/features/user/data/database/definitions.drift.dart'
|
||||
show ProxyProfile;
|
||||
|
||||
part 'proxy_profile_draft_controller.g.dart';
|
||||
|
||||
const defaultCustomOutboundConfigJson = '''
|
||||
{
|
||||
"type": "socks",
|
||||
"server": "127.0.0.1",
|
||||
"server_port": 1080
|
||||
}''';
|
||||
|
||||
sealed class SaveOutcome {
|
||||
const SaveOutcome();
|
||||
}
|
||||
|
||||
class SaveSucceeded extends SaveOutcome {
|
||||
const SaveSucceeded();
|
||||
}
|
||||
|
||||
class SaveFailed extends SaveOutcome {
|
||||
final String message;
|
||||
|
||||
const SaveFailed(this.message);
|
||||
}
|
||||
|
||||
@CopyWith()
|
||||
class ProxyProfileDraftState with FastEquatable {
|
||||
final String? profileId;
|
||||
final ProxyProfile? existingProfile;
|
||||
final String? loadError;
|
||||
final String name;
|
||||
final SingboxProxyProfileType type;
|
||||
final Map<String, String> values;
|
||||
final String? dnsOverrideJson;
|
||||
final String customConfigJson;
|
||||
final String customSecretJson;
|
||||
final bool isSaving;
|
||||
final bool secretLoaded;
|
||||
|
||||
ProxyProfileDraftState({
|
||||
required this.profileId,
|
||||
required this.existingProfile,
|
||||
required this.loadError,
|
||||
required this.name,
|
||||
required this.type,
|
||||
required this.values,
|
||||
required this.dnsOverrideJson,
|
||||
required this.customConfigJson,
|
||||
required this.customSecretJson,
|
||||
required this.isSaving,
|
||||
required this.secretLoaded,
|
||||
});
|
||||
|
||||
factory ProxyProfileDraftState.newProfile({ProxyProfileSeed? seed}) {
|
||||
final type = seed?.type ?? SingboxProxyProfileType.customOutbound;
|
||||
return ProxyProfileDraftState(
|
||||
profileId: null,
|
||||
existingProfile: null,
|
||||
loadError: null,
|
||||
name: seed?.name ?? '',
|
||||
type: type,
|
||||
values: _initialValuesForType(type, overlay: seed?.values),
|
||||
dnsOverrideJson: seed?.dnsOverrideJson,
|
||||
customConfigJson: defaultCustomOutboundConfigJson,
|
||||
customSecretJson: '',
|
||||
isSaving: false,
|
||||
secretLoaded: true,
|
||||
);
|
||||
}
|
||||
|
||||
factory ProxyProfileDraftState.loadingExisting(String profileId) {
|
||||
return ProxyProfileDraftState(
|
||||
profileId: profileId,
|
||||
existingProfile: null,
|
||||
loadError: null,
|
||||
name: '',
|
||||
type: SingboxProxyProfileType.customOutbound,
|
||||
values: const {},
|
||||
dnsOverrideJson: null,
|
||||
customConfigJson: defaultCustomOutboundConfigJson,
|
||||
customSecretJson: '',
|
||||
isSaving: false,
|
||||
secretLoaded: false,
|
||||
);
|
||||
}
|
||||
|
||||
bool get isEditing => profileId != null;
|
||||
|
||||
bool get isLoading =>
|
||||
isEditing && existingProfile == null && loadError == null;
|
||||
|
||||
@override
|
||||
List<Object?> get hashParameters => [
|
||||
profileId,
|
||||
existingProfile,
|
||||
loadError,
|
||||
name,
|
||||
type,
|
||||
values,
|
||||
dnsOverrideJson,
|
||||
customConfigJson,
|
||||
customSecretJson,
|
||||
isSaving,
|
||||
secretLoaded,
|
||||
];
|
||||
}
|
||||
|
||||
@riverpod
|
||||
class ProxyProfileDraft extends _$ProxyProfileDraft {
|
||||
@override
|
||||
ProxyProfileDraftState build({String? profileId, ProxyProfileSeed? seed}) {
|
||||
if (profileId == null) {
|
||||
return ProxyProfileDraftState.newProfile(seed: seed);
|
||||
}
|
||||
|
||||
unawaited(_loadExistingProfile(profileId));
|
||||
return ProxyProfileDraftState.loadingExisting(profileId);
|
||||
}
|
||||
|
||||
Future<void> _loadExistingProfile(String profileId) async {
|
||||
try {
|
||||
final profile = await ref
|
||||
.read(singboxProxyProfilesRepositoryProvider.notifier)
|
||||
.findProfile(profileId);
|
||||
if (!ref.mounted) return;
|
||||
|
||||
if (profile == null) {
|
||||
state = state.copyWith(
|
||||
loadError: 'Proxy profile not found.',
|
||||
secretLoaded: true,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
final secretJson = await ref
|
||||
.read(singboxProxyCredentialsRepositoryProvider.notifier)
|
||||
.readSecretJson(profile.id);
|
||||
if (!ref.mounted) return;
|
||||
|
||||
final spec = singboxProxyFormSpecs[profile.type];
|
||||
state = state.copyWith(
|
||||
existingProfile: profile,
|
||||
name: profile.name,
|
||||
type: profile.type,
|
||||
values: spec == null
|
||||
? const {}
|
||||
: spec.valuesFromJson(
|
||||
configJson: profile.configJson,
|
||||
secretJson: secretJson,
|
||||
),
|
||||
dnsOverrideJson: profile.dnsOverrideJson,
|
||||
customConfigJson: profile.type == SingboxProxyProfileType.customOutbound
|
||||
? profile.configJson
|
||||
: defaultCustomOutboundConfigJson,
|
||||
customSecretJson: profile.type == SingboxProxyProfileType.customOutbound
|
||||
? secretJson ?? ''
|
||||
: '',
|
||||
secretLoaded: true,
|
||||
);
|
||||
} catch (error) {
|
||||
if (!ref.mounted) return;
|
||||
state = state.copyWith(
|
||||
loadError: 'Failed to load proxy profile: $error',
|
||||
secretLoaded: true,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void setName(String name) {
|
||||
state = state.copyWith(name: name);
|
||||
}
|
||||
|
||||
void setType(SingboxProxyProfileType type) {
|
||||
if (state.isEditing || state.type == type) return;
|
||||
state = state.copyWith(
|
||||
type: type,
|
||||
values: _initialValuesForType(type),
|
||||
customConfigJson: defaultCustomOutboundConfigJson,
|
||||
customSecretJson: '',
|
||||
secretLoaded: true,
|
||||
);
|
||||
}
|
||||
|
||||
void setFieldValue(String key, String value) {
|
||||
state = state.copyWith(values: {...state.values, key: value});
|
||||
}
|
||||
|
||||
void setDnsOverrideJson(String? json) {
|
||||
state = state.copyWith.dnsOverrideJson(json);
|
||||
}
|
||||
|
||||
void setCustomConfigJson(String json) {
|
||||
state = state.copyWith(customConfigJson: json);
|
||||
}
|
||||
|
||||
void setCustomSecretJson(String json) {
|
||||
state = state.copyWith(customSecretJson: json);
|
||||
}
|
||||
|
||||
Future<SaveOutcome> save() async {
|
||||
if (state.isSaving) {
|
||||
return const SaveFailed('Profile is already saving.');
|
||||
}
|
||||
|
||||
final draft = state;
|
||||
final trimmedName = draft.name.trim();
|
||||
if (trimmedName.isEmpty) {
|
||||
return const SaveFailed('Profile name is required.');
|
||||
}
|
||||
|
||||
if (draft.isLoading) {
|
||||
return const SaveFailed('Profile is still loading, please wait.');
|
||||
}
|
||||
|
||||
if (draft.loadError != null) {
|
||||
return SaveFailed(draft.loadError!);
|
||||
}
|
||||
|
||||
final encoded = _encodeDraft(draft);
|
||||
switch (encoded) {
|
||||
case _DraftEncodeFailure(:final message):
|
||||
return SaveFailed(message);
|
||||
case _DraftEncodeSuccess(:final configJson, :final secretJson):
|
||||
state = state.copyWith(isSaving: true);
|
||||
|
||||
try {
|
||||
final existing = draft.existingProfile;
|
||||
final profile = ProxyProfile(
|
||||
id: existing?.id ?? uuid.v4(),
|
||||
name: trimmedName,
|
||||
type: draft.type,
|
||||
configJson: configJson,
|
||||
dnsOverrideJson: draft.dnsOverrideJson,
|
||||
createdAt: existing?.createdAt ?? DateTime.now(),
|
||||
updatedAt: DateTime.now(),
|
||||
);
|
||||
|
||||
final validationMessage = await ref
|
||||
.read(singboxProxyRuntimeRepositoryProvider.notifier)
|
||||
.validateProfileDraft(profile, secretJson: secretJson);
|
||||
if (validationMessage != null) {
|
||||
return SaveFailed(validationMessage);
|
||||
}
|
||||
|
||||
if (existing == null) {
|
||||
await ref
|
||||
.read(singboxProxyProfilesRepositoryProvider.notifier)
|
||||
.createProfile(
|
||||
name: trimmedName,
|
||||
type: draft.type,
|
||||
configJson: configJson,
|
||||
secretJson: secretJson,
|
||||
dnsOverrideJson: draft.dnsOverrideJson,
|
||||
);
|
||||
} else {
|
||||
await ref
|
||||
.read(singboxProxyProfilesRepositoryProvider.notifier)
|
||||
.updateProfile(profile);
|
||||
await ref
|
||||
.read(singboxProxyCredentialsRepositoryProvider.notifier)
|
||||
.writeSecretJson(profile.id, secretJson);
|
||||
}
|
||||
|
||||
return const SaveSucceeded();
|
||||
} catch (error) {
|
||||
return SaveFailed('Failed to save proxy profile: $error');
|
||||
} finally {
|
||||
if (ref.mounted) {
|
||||
state = state.copyWith(isSaving: false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sealed class _DraftEncodeResult {
|
||||
const _DraftEncodeResult();
|
||||
}
|
||||
|
||||
class _DraftEncodeSuccess extends _DraftEncodeResult {
|
||||
final String configJson;
|
||||
final String? secretJson;
|
||||
|
||||
const _DraftEncodeSuccess({
|
||||
required this.configJson,
|
||||
required this.secretJson,
|
||||
});
|
||||
}
|
||||
|
||||
class _DraftEncodeFailure extends _DraftEncodeResult {
|
||||
final String message;
|
||||
|
||||
const _DraftEncodeFailure(this.message);
|
||||
}
|
||||
|
||||
/// Validates and encodes the draft into wire-format JSON in a single pass.
|
||||
/// For custom-outbound profiles we only check JSON shape; for spec-driven
|
||||
/// types the spec carries field-level validation.
|
||||
_DraftEncodeResult _encodeDraft(ProxyProfileDraftState draft) {
|
||||
if (draft.type == SingboxProxyProfileType.customOutbound) {
|
||||
final normalizedConfigJson = _normalizeJsonObject(draft.customConfigJson);
|
||||
if (normalizedConfigJson == null) {
|
||||
return const _DraftEncodeFailure('Config must be a JSON object.');
|
||||
}
|
||||
|
||||
final rawSecret = draft.customSecretJson.trim();
|
||||
if (rawSecret.isEmpty) {
|
||||
return _DraftEncodeSuccess(
|
||||
configJson: normalizedConfigJson,
|
||||
secretJson: null,
|
||||
);
|
||||
}
|
||||
final normalizedSecretJson = _normalizeJsonObject(draft.customSecretJson);
|
||||
if (normalizedSecretJson == null) {
|
||||
return const _DraftEncodeFailure('Secrets must be a JSON object.');
|
||||
}
|
||||
return _DraftEncodeSuccess(
|
||||
configJson: normalizedConfigJson,
|
||||
secretJson: normalizedSecretJson,
|
||||
);
|
||||
}
|
||||
|
||||
final spec = singboxProxyFormSpecs[draft.type]!;
|
||||
final validationMessage = spec.validate(draft.values);
|
||||
if (validationMessage != null) {
|
||||
return _DraftEncodeFailure(validationMessage);
|
||||
}
|
||||
return _DraftEncodeSuccess(
|
||||
configJson: spec.toConfigJson(draft.values),
|
||||
secretJson: spec.toSecretJson(draft.values),
|
||||
);
|
||||
}
|
||||
|
||||
String? _normalizeJsonObject(String rawJson) {
|
||||
try {
|
||||
final decoded = jsonDecode(rawJson) as Object?;
|
||||
if (decoded is! Map<String, dynamic>) {
|
||||
return null;
|
||||
}
|
||||
return const JsonEncoder.withIndent(' ').convert(decoded);
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, String> _initialValuesForType(
|
||||
SingboxProxyProfileType type, {
|
||||
Map<String, String>? overlay,
|
||||
}) {
|
||||
final spec = singboxProxyFormSpecs[type];
|
||||
if (spec == null) return overlay == null ? const {} : Map.of(overlay);
|
||||
|
||||
return {
|
||||
for (final field in spec.fields)
|
||||
if (field.defaultValue != null) field.key: field.defaultValue!,
|
||||
if (overlay != null) ...overlay,
|
||||
};
|
||||
}
|
||||
+296
@@ -0,0 +1,296 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'proxy_profile_draft_controller.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// CopyWithGenerator
|
||||
// **************************************************************************
|
||||
|
||||
abstract class _$ProxyProfileDraftStateCWProxy {
|
||||
ProxyProfileDraftState profileId(String? profileId);
|
||||
|
||||
ProxyProfileDraftState existingProfile(ProxyProfile? existingProfile);
|
||||
|
||||
ProxyProfileDraftState loadError(String? loadError);
|
||||
|
||||
ProxyProfileDraftState name(String name);
|
||||
|
||||
ProxyProfileDraftState type(SingboxProxyProfileType type);
|
||||
|
||||
ProxyProfileDraftState values(Map<String, String> values);
|
||||
|
||||
ProxyProfileDraftState dnsOverrideJson(String? dnsOverrideJson);
|
||||
|
||||
ProxyProfileDraftState customConfigJson(String customConfigJson);
|
||||
|
||||
ProxyProfileDraftState customSecretJson(String customSecretJson);
|
||||
|
||||
ProxyProfileDraftState isSaving(bool isSaving);
|
||||
|
||||
ProxyProfileDraftState secretLoaded(bool secretLoaded);
|
||||
|
||||
/// Creates a new instance with the provided field values.
|
||||
/// Passing `null` to a nullable field nullifies it, while `null` for a non-nullable field is ignored. To update a single field use `ProxyProfileDraftState(...).copyWith.fieldName(value)`.
|
||||
///
|
||||
/// Example:
|
||||
/// ```dart
|
||||
/// ProxyProfileDraftState(...).copyWith(id: 12, name: "My name")
|
||||
/// ```
|
||||
ProxyProfileDraftState call({
|
||||
String? profileId,
|
||||
ProxyProfile? existingProfile,
|
||||
String? loadError,
|
||||
String name,
|
||||
SingboxProxyProfileType type,
|
||||
Map<String, String> values,
|
||||
String? dnsOverrideJson,
|
||||
String customConfigJson,
|
||||
String customSecretJson,
|
||||
bool isSaving,
|
||||
bool secretLoaded,
|
||||
});
|
||||
}
|
||||
|
||||
/// Callable proxy for `copyWith` functionality.
|
||||
/// Use as `instanceOfProxyProfileDraftState.copyWith(...)` or call `instanceOfProxyProfileDraftState.copyWith.fieldName(value)` for a single field.
|
||||
class _$ProxyProfileDraftStateCWProxyImpl
|
||||
implements _$ProxyProfileDraftStateCWProxy {
|
||||
const _$ProxyProfileDraftStateCWProxyImpl(this._value);
|
||||
|
||||
final ProxyProfileDraftState _value;
|
||||
|
||||
@override
|
||||
ProxyProfileDraftState profileId(String? profileId) =>
|
||||
call(profileId: profileId);
|
||||
|
||||
@override
|
||||
ProxyProfileDraftState existingProfile(ProxyProfile? existingProfile) =>
|
||||
call(existingProfile: existingProfile);
|
||||
|
||||
@override
|
||||
ProxyProfileDraftState loadError(String? loadError) =>
|
||||
call(loadError: loadError);
|
||||
|
||||
@override
|
||||
ProxyProfileDraftState name(String name) => call(name: name);
|
||||
|
||||
@override
|
||||
ProxyProfileDraftState type(SingboxProxyProfileType type) => call(type: type);
|
||||
|
||||
@override
|
||||
ProxyProfileDraftState values(Map<String, String> values) =>
|
||||
call(values: values);
|
||||
|
||||
@override
|
||||
ProxyProfileDraftState dnsOverrideJson(String? dnsOverrideJson) =>
|
||||
call(dnsOverrideJson: dnsOverrideJson);
|
||||
|
||||
@override
|
||||
ProxyProfileDraftState customConfigJson(String customConfigJson) =>
|
||||
call(customConfigJson: customConfigJson);
|
||||
|
||||
@override
|
||||
ProxyProfileDraftState customSecretJson(String customSecretJson) =>
|
||||
call(customSecretJson: customSecretJson);
|
||||
|
||||
@override
|
||||
ProxyProfileDraftState isSaving(bool isSaving) => call(isSaving: isSaving);
|
||||
|
||||
@override
|
||||
ProxyProfileDraftState secretLoaded(bool secretLoaded) =>
|
||||
call(secretLoaded: secretLoaded);
|
||||
|
||||
@override
|
||||
/// Creates a new instance with the provided field values.
|
||||
/// Passing `null` to a nullable field nullifies it, while `null` for a non-nullable field is ignored. To update a single field use `ProxyProfileDraftState(...).copyWith.fieldName(value)`.
|
||||
///
|
||||
/// Example:
|
||||
/// ```dart
|
||||
/// ProxyProfileDraftState(...).copyWith(id: 12, name: "My name")
|
||||
/// ```
|
||||
ProxyProfileDraftState call({
|
||||
Object? profileId = const $CopyWithPlaceholder(),
|
||||
Object? existingProfile = const $CopyWithPlaceholder(),
|
||||
Object? loadError = const $CopyWithPlaceholder(),
|
||||
Object? name = const $CopyWithPlaceholder(),
|
||||
Object? type = const $CopyWithPlaceholder(),
|
||||
Object? values = const $CopyWithPlaceholder(),
|
||||
Object? dnsOverrideJson = const $CopyWithPlaceholder(),
|
||||
Object? customConfigJson = const $CopyWithPlaceholder(),
|
||||
Object? customSecretJson = const $CopyWithPlaceholder(),
|
||||
Object? isSaving = const $CopyWithPlaceholder(),
|
||||
Object? secretLoaded = const $CopyWithPlaceholder(),
|
||||
}) {
|
||||
return ProxyProfileDraftState(
|
||||
profileId: profileId == const $CopyWithPlaceholder()
|
||||
? _value.profileId
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: profileId as String?,
|
||||
existingProfile: existingProfile == const $CopyWithPlaceholder()
|
||||
? _value.existingProfile
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: existingProfile as ProxyProfile?,
|
||||
loadError: loadError == const $CopyWithPlaceholder()
|
||||
? _value.loadError
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: loadError as String?,
|
||||
name: name == const $CopyWithPlaceholder() || name == null
|
||||
? _value.name
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: name as String,
|
||||
type: type == const $CopyWithPlaceholder() || type == null
|
||||
? _value.type
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: type as SingboxProxyProfileType,
|
||||
values: values == const $CopyWithPlaceholder() || values == null
|
||||
? _value.values
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: values as Map<String, String>,
|
||||
dnsOverrideJson: dnsOverrideJson == const $CopyWithPlaceholder()
|
||||
? _value.dnsOverrideJson
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: dnsOverrideJson as String?,
|
||||
customConfigJson:
|
||||
customConfigJson == const $CopyWithPlaceholder() ||
|
||||
customConfigJson == null
|
||||
? _value.customConfigJson
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: customConfigJson as String,
|
||||
customSecretJson:
|
||||
customSecretJson == const $CopyWithPlaceholder() ||
|
||||
customSecretJson == null
|
||||
? _value.customSecretJson
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: customSecretJson as String,
|
||||
isSaving: isSaving == const $CopyWithPlaceholder() || isSaving == null
|
||||
? _value.isSaving
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: isSaving as bool,
|
||||
secretLoaded:
|
||||
secretLoaded == const $CopyWithPlaceholder() || secretLoaded == null
|
||||
? _value.secretLoaded
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: secretLoaded as bool,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
extension $ProxyProfileDraftStateCopyWith on ProxyProfileDraftState {
|
||||
/// Returns a callable class used to build a new instance with modified fields.
|
||||
/// Example: `instanceOfProxyProfileDraftState.copyWith(...)` or `instanceOfProxyProfileDraftState.copyWith.fieldName(...)`.
|
||||
// ignore: library_private_types_in_public_api
|
||||
_$ProxyProfileDraftStateCWProxy get copyWith =>
|
||||
_$ProxyProfileDraftStateCWProxyImpl(this);
|
||||
}
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint, type=warning
|
||||
|
||||
@ProviderFor(ProxyProfileDraft)
|
||||
final proxyProfileDraftProvider = ProxyProfileDraftFamily._();
|
||||
|
||||
final class ProxyProfileDraftProvider
|
||||
extends $NotifierProvider<ProxyProfileDraft, ProxyProfileDraftState> {
|
||||
ProxyProfileDraftProvider._({
|
||||
required ProxyProfileDraftFamily super.from,
|
||||
required ({String? profileId, ProxyProfileSeed? seed}) super.argument,
|
||||
}) : super(
|
||||
retry: null,
|
||||
name: r'proxyProfileDraftProvider',
|
||||
isAutoDispose: true,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$proxyProfileDraftHash();
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return r'proxyProfileDraftProvider'
|
||||
''
|
||||
'$argument';
|
||||
}
|
||||
|
||||
@$internal
|
||||
@override
|
||||
ProxyProfileDraft create() => ProxyProfileDraft();
|
||||
|
||||
/// {@macro riverpod.override_with_value}
|
||||
Override overrideWithValue(ProxyProfileDraftState value) {
|
||||
return $ProviderOverride(
|
||||
origin: this,
|
||||
providerOverride: $SyncValueProvider<ProxyProfileDraftState>(value),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is ProxyProfileDraftProvider && other.argument == argument;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return argument.hashCode;
|
||||
}
|
||||
}
|
||||
|
||||
String _$proxyProfileDraftHash() => r'88dccb74624a8257f77646ab6755dce4e0de465a';
|
||||
|
||||
final class ProxyProfileDraftFamily extends $Family
|
||||
with
|
||||
$ClassFamilyOverride<
|
||||
ProxyProfileDraft,
|
||||
ProxyProfileDraftState,
|
||||
ProxyProfileDraftState,
|
||||
ProxyProfileDraftState,
|
||||
({String? profileId, ProxyProfileSeed? seed})
|
||||
> {
|
||||
ProxyProfileDraftFamily._()
|
||||
: super(
|
||||
retry: null,
|
||||
name: r'proxyProfileDraftProvider',
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
isAutoDispose: true,
|
||||
);
|
||||
|
||||
ProxyProfileDraftProvider call({String? profileId, ProxyProfileSeed? seed}) =>
|
||||
ProxyProfileDraftProvider._(
|
||||
argument: (profileId: profileId, seed: seed),
|
||||
from: this,
|
||||
);
|
||||
|
||||
@override
|
||||
String toString() => r'proxyProfileDraftProvider';
|
||||
}
|
||||
|
||||
abstract class _$ProxyProfileDraft extends $Notifier<ProxyProfileDraftState> {
|
||||
late final _$args = ref.$arg as ({String? profileId, ProxyProfileSeed? seed});
|
||||
String? get profileId => _$args.profileId;
|
||||
ProxyProfileSeed? get seed => _$args.seed;
|
||||
|
||||
ProxyProfileDraftState build({String? profileId, ProxyProfileSeed? seed});
|
||||
@$mustCallSuper
|
||||
@override
|
||||
void runBuild() {
|
||||
final ref =
|
||||
this.ref as $Ref<ProxyProfileDraftState, ProxyProfileDraftState>;
|
||||
final element =
|
||||
ref.element
|
||||
as $ClassProviderElement<
|
||||
AnyNotifier<ProxyProfileDraftState, ProxyProfileDraftState>,
|
||||
ProxyProfileDraftState,
|
||||
Object?,
|
||||
Object?
|
||||
>;
|
||||
element.handleCreate(
|
||||
ref,
|
||||
() => build(profileId: _$args.profileId, seed: _$args.seed),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user