Skip to content

Commit b27ae6b

Browse files
Adam Fiskclaude
authored 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 6ae10c5 commit b27ae6b

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
@@ -551,6 +551,12 @@ msgstr "Block Ads"
551551
msgid "only_active"
552552
msgstr "Only active when VPN is connected"
553553

554+
msgid "share_my_connection"
555+
msgstr "Share My Connection"
556+
557+
msgid "share_my_connection_subtitle"
558+
msgstr "Let other Lantern users route through your connection to bypass censorship."
559+
554560
msgid "vpn_connected"
555561
msgstr "Lantern is now connected."
556562

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
@@ -79,6 +79,10 @@ abstract class LanternCoreService {
7979

8080
Future<Either<Failure, bool>> isBlockAdsEnabled();
8181

82+
Future<Either<Failure, Unit>> setPeerProxyEnabled(bool enabled);
83+
84+
Future<Either<Failure, bool>> isPeerProxyEnabled();
85+
8286
Future<Either<Failure, bool>> isSmartRoutingEnabled();
8387

8488
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
@@ -1550,6 +1550,34 @@ class LanternFFIService implements LanternCoreService {
15501550
}
15511551
}
15521552

1553+
@override
1554+
Future<Either<Failure, Unit>> setPeerProxyEnabled(bool enabled) async {
1555+
try {
1556+
final result = await runInBackground<String>(() async {
1557+
return _ffiService
1558+
.setPeerProxyEnabled(enabled ? 1 : 0)
1559+
.cast<Utf8>()
1560+
.toDartString();
1561+
});
1562+
checkAPIError(result);
1563+
return right(unit);
1564+
} catch (e, st) {
1565+
appLogger.error('setPeerProxyEnabled error: $e', e, st);
1566+
return Left(e.toFailure());
1567+
}
1568+
}
1569+
1570+
@override
1571+
Future<Either<Failure, bool>> isPeerProxyEnabled() async {
1572+
try {
1573+
final res = _ffiService.isPeerProxyEnabled();
1574+
return right(res != 0);
1575+
} catch (e, st) {
1576+
appLogger.error('isPeerProxyEnabled error: $e', e, st);
1577+
return Left(e.toFailure());
1578+
}
1579+
}
1580+
15531581
@override
15541582
Future<Either<Failure, bool>> isSmartRoutingEnabled() async {
15551583
try {

lib/lantern/lantern_generated_bindings.dart

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

6278+
ffi.Pointer<ffi.Char> setPeerProxyEnabled(int enabled) {
6279+
return _setPeerProxyEnabled(enabled);
6280+
}
6281+
6282+
late final _setPeerProxyEnabledPtr =
6283+
_lookup<ffi.NativeFunction<ffi.Pointer<ffi.Char> Function(ffi.Int)>>(
6284+
'setPeerProxyEnabled',
6285+
);
6286+
late final _setPeerProxyEnabled = _setPeerProxyEnabledPtr
6287+
.asFunction<ffi.Pointer<ffi.Char> Function(int)>();
6288+
6289+
int isPeerProxyEnabled() {
6290+
return _isPeerProxyEnabled();
6291+
}
6292+
6293+
late final _isPeerProxyEnabledPtr =
6294+
_lookup<ffi.NativeFunction<ffi.Int Function()>>('isPeerProxyEnabled');
6295+
late final _isPeerProxyEnabled = _isPeerProxyEnabledPtr
6296+
.asFunction<int Function()>();
6297+
62786298
ffi.Pointer<ffi.Char> setSmartRoutingEnabled(int enabled) {
62796299
return _setSmartRoutingEnabled(enabled);
62806300
}

lib/lantern/lantern_platform_service.dart

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

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

lib/lantern/lantern_service.dart

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

797+
@override
798+
Future<Either<Failure, bool>> isPeerProxyEnabled() {
799+
if (PlatformUtils.isFFISupported) {
800+
return _ffiService.isPeerProxyEnabled();
801+
}
802+
return _platformService.isPeerProxyEnabled();
803+
}
804+
805+
@override
806+
Future<Either<Failure, Unit>> setPeerProxyEnabled(bool enabled) {
807+
if (PlatformUtils.isFFISupported) {
808+
return _ffiService.setPeerProxyEnabled(enabled);
809+
}
810+
return _platformService.setPeerProxyEnabled(enabled);
811+
}
812+
797813
@override
798814
Future<Either<Failure, bool>> isSmartRoutingEnabled() {
799815
if (PlatformUtils.isFFISupported) {

0 commit comments

Comments
 (0)