Skip to content

Commit fc24e40

Browse files
Adam Fiskclaude
andcommitted
Wire Share My Connection toggle in Dart UI (PR 4/4)
Final PR in the four-PR stack. Stacks on lantern #8729 (FFI exports); combined with radiance #458 / #460 / lantern-cloud #2678-#2681 this ships a feature-complete Phase 1 of "Share My Connection" for desktop (macOS + Linux + Windows). * lantern_generated_bindings.dart: add setPeerProxyEnabled + isPeerProxyEnabled. Manually inserted to match the existing pattern rather than regenerating the whole file (a local ffigen run from the macOS header would drop ~5K lines of Windows-only declarations the upstream generator emits). * LanternCoreService / LanternFFIService / LanternPlatformService / LanternService: add setPeerProxyEnabled / isPeerProxyEnabled across all four service layers, mirroring the setBlockAdsEnabled pattern. FFI path on isFFISupported platforms (Windows + Linux), MethodChannel fallback on macOS / mobile. * RadianceSettingsState: new peerProxy bool field with copyWith and equality. * RadianceSettings notifier: new setPeerProxy method (pessimistic — call FFI, log on failure, update state on success — matching setBlockAds). _refresh now reads peerProxy alongside the others. * vpn_setting.dart: SwitchButton tile gated to PlatformUtils.isDesktop with i18n strings share_my_connection / share_my_connection_subtitle in en.po. Other locales will pick up via the standard translation flow. Lifecycle end-to-end: Dart toggle → RadianceSettings.setPeerProxy(bool) → LanternService.setPeerProxyEnabled → FFI: setPeerProxyEnabled(int) -> *char → Core.SetPeerShareEnabled(bool) → ipc.Client.PatchSettings({PeerShareEnabledKey: ...}) → radiance LocalBackend.PatchSettings dispatch → peer.Client.Start / Stop → UPnP MapPort + register + sing-box samizdat inbound + heartbeat flutter analyze: clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent a964590 commit fc24e40

9 files changed

Lines changed: 152 additions & 2 deletions

File tree

assets/locales/en.po

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -488,6 +488,12 @@ msgstr "Block Ads"
488488
msgid "only_active"
489489
msgstr "Only active when VPN is connected"
490490

491+
msgid "share_my_connection"
492+
msgstr "Share My Connection"
493+
494+
msgid "share_my_connection_subtitle"
495+
msgstr "Let other Lantern users route through your connection to bypass censorship."
496+
491497
msgid "vpn_connected"
492498
msgstr "Lantern is now connected."
493499

lib/core/models/radiance_settings_state.dart

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,25 +12,29 @@ class RadianceSettingsState {
1212
final RoutingMode routingMode;
1313
final bool splitTunneling;
1414
final bool telemetry;
15+
final bool peerProxy;
1516

1617
const RadianceSettingsState({
1718
this.blockAds = false,
1819
this.routingMode = RoutingMode.full,
1920
this.splitTunneling = false,
2021
this.telemetry = false,
22+
this.peerProxy = false,
2123
});
2224

2325
RadianceSettingsState copyWith({
2426
bool? blockAds,
2527
RoutingMode? routingMode,
2628
bool? splitTunneling,
2729
bool? telemetry,
30+
bool? peerProxy,
2831
}) {
2932
return RadianceSettingsState(
3033
blockAds: blockAds ?? this.blockAds,
3134
routingMode: routingMode ?? this.routingMode,
3235
splitTunneling: splitTunneling ?? this.splitTunneling,
3336
telemetry: telemetry ?? this.telemetry,
37+
peerProxy: peerProxy ?? this.peerProxy,
3438
);
3539
}
3640

@@ -41,9 +45,10 @@ class RadianceSettingsState {
4145
blockAds == other.blockAds &&
4246
routingMode == other.routingMode &&
4347
splitTunneling == other.splitTunneling &&
44-
telemetry == other.telemetry;
48+
telemetry == other.telemetry &&
49+
peerProxy == other.peerProxy;
4550

4651
@override
4752
int get hashCode =>
48-
Object.hash(blockAds, routingMode, splitTunneling, telemetry);
53+
Object.hash(blockAds, routingMode, splitTunneling, telemetry, peerProxy);
4954
}

lib/features/home/provider/radiance_settings_providers.dart

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,16 +29,19 @@ class RadianceSettings extends _$RadianceSettings {
2929
final routingF = svc.isSmartRoutingEnabled();
3030
final telemetryF = svc.isTelemetryEnabled();
3131
final splitF = PlatformUtils.isIOS ? null : svc.isSplitTunnelingEnabled();
32+
final peerProxyF = svc.isPeerProxyEnabled();
3233

3334
final results = await Future.wait([
3435
blockAdsF,
3536
routingF,
3637
telemetryF,
3738
?splitF,
39+
peerProxyF,
3840
]);
3941
if (!ref.mounted) return;
4042

4143
const defaults = RadianceSettingsState();
44+
final peerIdx = splitF == null ? 3 : 4;
4245
state = RadianceSettingsState(
4346
blockAds: results[0].fold((_) => defaults.blockAds, (v) => v),
4447
routingMode: results[1].fold(
@@ -49,6 +52,7 @@ class RadianceSettings extends _$RadianceSettings {
4952
splitTunneling: splitF == null
5053
? defaults.splitTunneling
5154
: results[3].fold((_) => defaults.splitTunneling, (v) => v),
55+
peerProxy: results[peerIdx].fold((_) => defaults.peerProxy, (v) => v),
5256
);
5357
}
5458

@@ -97,6 +101,16 @@ class RadianceSettings extends _$RadianceSettings {
97101
(_) => state = state.copyWith(telemetry: consent),
98102
);
99103
}
104+
105+
Future<void> setPeerProxy(bool value) async {
106+
final svc = ref.read(lanternServiceProvider);
107+
final result = await svc.setPeerProxyEnabled(value);
108+
if (!ref.mounted) return;
109+
result.fold(
110+
(err) => appLogger.error('setPeerProxyEnabled failed: ${err.error}'),
111+
(_) => state = state.copyWith(peerProxy: value),
112+
);
113+
}
100114
}
101115

102116
/// Fetches whether user logged in via OAuth from radiance.

lib/features/setting/vpn_setting.dart

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ class VPNSetting extends HookConsumerWidget {
3535
final telemetryConsent = ref.watch(
3636
radianceSettingsProvider.select((s) => s.telemetry),
3737
);
38+
final peerProxy = ref.watch(
39+
radianceSettingsProvider.select((s) => s.peerProxy),
40+
);
3841

3942
return ListView(
4043
padding: const EdgeInsets.all(0),
@@ -117,6 +120,36 @@ class VPNSetting extends HookConsumerWidget {
117120
},
118121
),
119122
),
123+
if (PlatformUtils.isDesktop) ...{
124+
SizedBox(height: 16),
125+
AppCard(
126+
padding: EdgeInsets.zero,
127+
child: AppTile(
128+
label: 'share_my_connection'.i18n,
129+
subtitle: Text(
130+
'share_my_connection_subtitle'.i18n,
131+
style: textTheme.labelMedium!.copyWith(
132+
color: context.textTertiary,
133+
letterSpacing: 0.0,
134+
),
135+
),
136+
icon: AppImagePaths.share,
137+
trailing: SwitchButton(
138+
value: peerProxy,
139+
onChanged: (bool? value) {
140+
ref
141+
.read(radianceSettingsProvider.notifier)
142+
.setPeerProxy(value ?? false);
143+
},
144+
),
145+
onPressed: () {
146+
ref
147+
.read(radianceSettingsProvider.notifier)
148+
.setPeerProxy(!peerProxy);
149+
},
150+
),
151+
),
152+
},
120153
SizedBox(height: 16),
121154
AppCard(
122155
padding: EdgeInsets.zero,

lib/lantern/lantern_core_service.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,10 @@ abstract class LanternCoreService {
7676

7777
Future<Either<Failure, bool>> isBlockAdsEnabled();
7878

79+
Future<Either<Failure, Unit>> setPeerProxyEnabled(bool enabled);
80+
81+
Future<Either<Failure, bool>> isPeerProxyEnabled();
82+
7983
Future<Either<Failure, bool>> isSmartRoutingEnabled();
8084

8185
Future<Either<Failure, bool>> isTelemetryEnabled();

lib/lantern/lantern_ffi_service.dart

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1508,6 +1508,34 @@ class LanternFFIService implements LanternCoreService {
15081508
}
15091509
}
15101510

1511+
@override
1512+
Future<Either<Failure, Unit>> setPeerProxyEnabled(bool enabled) async {
1513+
try {
1514+
final result = await runInBackground<String>(() async {
1515+
return _ffiService
1516+
.setPeerProxyEnabled(enabled ? 1 : 0)
1517+
.cast<Utf8>()
1518+
.toDartString();
1519+
});
1520+
checkAPIError(result);
1521+
return right(unit);
1522+
} catch (e, st) {
1523+
appLogger.error('setPeerProxyEnabled error: $e', e, st);
1524+
return Left(e.toFailure());
1525+
}
1526+
}
1527+
1528+
@override
1529+
Future<Either<Failure, bool>> isPeerProxyEnabled() async {
1530+
try {
1531+
final res = _ffiService.isPeerProxyEnabled();
1532+
return right(res != 0);
1533+
} catch (e, st) {
1534+
appLogger.error('isPeerProxyEnabled error: $e', e, st);
1535+
return Left(e.toFailure());
1536+
}
1537+
}
1538+
15111539
@override
15121540
Future<Either<Failure, bool>> isSmartRoutingEnabled() async {
15131541
try {

lib/lantern/lantern_generated_bindings.dart

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6253,6 +6253,26 @@ class LanternBindings {
62536253
late final _isBlockAdsEnabled = _isBlockAdsEnabledPtr
62546254
.asFunction<int Function()>();
62556255

6256+
ffi.Pointer<ffi.Char> setPeerProxyEnabled(int enabled) {
6257+
return _setPeerProxyEnabled(enabled);
6258+
}
6259+
6260+
late final _setPeerProxyEnabledPtr =
6261+
_lookup<ffi.NativeFunction<ffi.Pointer<ffi.Char> Function(ffi.Int)>>(
6262+
'setPeerProxyEnabled',
6263+
);
6264+
late final _setPeerProxyEnabled = _setPeerProxyEnabledPtr
6265+
.asFunction<ffi.Pointer<ffi.Char> Function(int)>();
6266+
6267+
int isPeerProxyEnabled() {
6268+
return _isPeerProxyEnabled();
6269+
}
6270+
6271+
late final _isPeerProxyEnabledPtr =
6272+
_lookup<ffi.NativeFunction<ffi.Int Function()>>('isPeerProxyEnabled');
6273+
late final _isPeerProxyEnabled = _isPeerProxyEnabledPtr
6274+
.asFunction<int Function()>();
6275+
62566276
ffi.Pointer<ffi.Char> setSmartRoutingEnabled(int enabled) {
62576277
return _setSmartRoutingEnabled(enabled);
62586278
}

lib/lantern/lantern_platform_service.dart

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,30 @@ class LanternPlatformService implements LanternCoreService {
288288
}
289289
}
290290

291+
@override
292+
Future<Either<Failure, Unit>> setPeerProxyEnabled(bool enabled) async {
293+
try {
294+
await _methodChannel.invokeMethod('setPeerProxyEnabled', {
295+
'enabled': enabled,
296+
});
297+
return right(unit);
298+
} catch (e, st) {
299+
appLogger.error('setPeerProxyEnabled failed', e, st);
300+
return Left(e.toFailure());
301+
}
302+
}
303+
304+
@override
305+
Future<Either<Failure, bool>> isPeerProxyEnabled() async {
306+
try {
307+
final res = await _methodChannel.invokeMethod<bool>('isPeerProxyEnabled');
308+
return right(res ?? false);
309+
} catch (e, st) {
310+
appLogger.error('isPeerProxyEnabled failed', e, st);
311+
return Left(e.toFailure());
312+
}
313+
}
314+
291315
@override
292316
Future<Either<Failure, bool>> isSmartRoutingEnabled() async {
293317
try {

lib/lantern/lantern_service.dart

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -773,6 +773,22 @@ class LanternService implements LanternCoreService {
773773
return _platformService.setBlockAdsEnabled(enabled);
774774
}
775775

776+
@override
777+
Future<Either<Failure, bool>> isPeerProxyEnabled() {
778+
if (PlatformUtils.isFFISupported) {
779+
return _ffiService.isPeerProxyEnabled();
780+
}
781+
return _platformService.isPeerProxyEnabled();
782+
}
783+
784+
@override
785+
Future<Either<Failure, Unit>> setPeerProxyEnabled(bool enabled) {
786+
if (PlatformUtils.isFFISupported) {
787+
return _ffiService.setPeerProxyEnabled(enabled);
788+
}
789+
return _platformService.setPeerProxyEnabled(enabled);
790+
}
791+
776792
@override
777793
Future<Either<Failure, bool>> isSmartRoutingEnabled() {
778794
if (PlatformUtils.isFFISupported) {

0 commit comments

Comments
 (0)