Add proxy routing and sing-box support

This commit is contained in:
Fabian Freund
2026-05-22 18:16:31 +02:00
parent 51289f1266
commit a5974617aa
262 changed files with 32003 additions and 3962 deletions
@@ -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(),
);
}
@@ -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,
};
}
@@ -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),
);
}
}