Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 35 additions & 10 deletions assets/locales/en.po
Original file line number Diff line number Diff line change
Expand Up @@ -557,12 +557,15 @@ msgstr "Share My Connection"
msgid "share_my_connection_subtitle"
msgstr "Let other Lantern users route through your connection to bypass censorship."

msgid "share_my_connection_on_tap_to_view"
msgstr "On — tap to view"

# Share My Connection screen — body / hero copy
# Unbounded tab — hero copy. Short variant for the tab embed; the
# longer privacy explanation now lives in the welcome dialog
# (showUnboundedWelcomeDialog) so the tab body stays scannable.
msgid "smc_intro"
msgstr "Help others bypass censorship by sharing a small portion of your home internet connection. While sharing is on, traffic from users in censored regions will egress through your IP."
msgstr "Help others bypass censorship by securely sharing your connection."

# Info-bubble tooltip on the Unbounded tab header
msgid "about_unbounded"
msgstr "About Unbounded"

# Status card — phase labels
msgid "smc_status_label"
Expand Down Expand Up @@ -609,17 +612,21 @@ msgstr "Couldn't share — try toggling again"

# Status card — stats + tooltip
msgid "smc_stat_active_now"
msgstr "Active now"
msgstr "People helping right now"

msgid "smc_stat_total_today"
msgstr "Total today"
# Lifetime ("to date") rather than daily — backed by the persisted
# unboundedTotalHelped setting so the count survives app restarts.
msgid "smc_stat_total_helped"
msgstr "Total people helped to date"

msgid "smc_connections_tooltip"
msgstr "Most connections are short liveness probes — Lantern clients periodically check that this peer is reachable before sending real traffic. A quick burst from many locations is normal; an arc that lingers represents an actual user session."

# Arrival toast — "New connection from {country}"
# Arrival toast surfaced when a censored user starts routing through
# this peer. The "Helping a new person" framing is intentional —
# emphasizes the user-impact framing the SmC tab leans into.
msgid "smc_arrival_toast"
msgstr "New connection from %s"
msgstr "Helping a new person in %s"

# Advanced section / manual port forward
msgid "smc_advanced"
Expand Down Expand Up @@ -674,6 +681,24 @@ msgstr "Basic mode (Unbounded)"
msgid "smc_disclosure_full"
msgstr "Full mode (SmC)"

# Unbounded Settings menu entry (Settings → Unbounded Settings)
msgid "unbounded_settings_title"
msgstr "Unbounded Settings"

# Auto-enable Unbounded toggle
msgid "auto_enable_unbounded"
msgstr "Auto-enable Unbounded"

msgid "auto_enable_unbounded_subtitle"
msgstr "Turn on automatically when Lantern is open"

# Hide Unbounded toggle (collapses the Unbounded tab on the Home shell)
msgid "hide_unbounded"
msgstr "Hide Unbounded"

msgid "hide_unbounded_subtitle"
msgstr "Removes Unbounded from the top of this screen"

msgid "vpn_connected"
msgstr "Lantern is now connected."

Expand Down
38 changes: 38 additions & 0 deletions lib/core/models/app_setting.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,20 @@ class AppSetting {
final bool successfulConnection;
final String dataCapThreshold;
final bool onboardingCompleted;
// Unbounded preferences. autoEnable: turn the peer share on whenever
// the VPN connects (defaults on per the Figma spec). hideTab: hide
// the Unbounded tab + collapse the tab bar when the user doesn't
// want to see it. welcomeSeen: tracks the first-visit info popup so
// we only show it once. All persisted across launches.
final bool unboundedAutoEnable;
final bool unboundedHidden;
final bool unboundedWelcomeSeen;
// Lifetime running total of peers this device has helped. Survives
// restarts so the "Total people helped to date" stat in the
// Unbounded tab can keep climbing — that's the spec wording in the
// Figma. ShareNotifier seeds totalCount from this on build, and
// writes back each time the count increments.
final int unboundedTotalHelped;

const AppSetting({
this.themeMode = 'system',
Expand All @@ -19,6 +33,10 @@ class AppSetting {
this.successfulConnection = false,
this.dataCapThreshold = '',
this.onboardingCompleted = false,
this.unboundedAutoEnable = true,
this.unboundedHidden = false,
this.unboundedWelcomeSeen = false,
this.unboundedTotalHelped = 0,
});

AppSetting copyWith({
Expand All @@ -31,6 +49,10 @@ class AppSetting {
bool? successfulConnection,
String? dataCapThreshold,
bool? onboardingCompleted,
bool? unboundedAutoEnable,
bool? unboundedHidden,
bool? unboundedWelcomeSeen,
int? unboundedTotalHelped,
}) {
return AppSetting(
locale: newLocale ?? locale,
Expand All @@ -42,6 +64,11 @@ class AppSetting {
successfulConnection: successfulConnection ?? this.successfulConnection,
dataCapThreshold: dataCapThreshold ?? this.dataCapThreshold,
onboardingCompleted: onboardingCompleted ?? this.onboardingCompleted,
unboundedAutoEnable: unboundedAutoEnable ?? this.unboundedAutoEnable,
unboundedHidden: unboundedHidden ?? this.unboundedHidden,
unboundedWelcomeSeen: unboundedWelcomeSeen ?? this.unboundedWelcomeSeen,
unboundedTotalHelped:
unboundedTotalHelped ?? this.unboundedTotalHelped,
);
}

Expand All @@ -55,6 +82,10 @@ class AppSetting {
'successfulConnection': successfulConnection,
'dataCapThreshold': dataCapThreshold,
'onboardingCompleted': onboardingCompleted,
'unboundedAutoEnable': unboundedAutoEnable,
'unboundedHidden': unboundedHidden,
'unboundedWelcomeSeen': unboundedWelcomeSeen,
'unboundedTotalHelped': unboundedTotalHelped,
};

factory AppSetting.fromJson(Map<String, dynamic> json) => AppSetting(
Expand All @@ -67,5 +98,12 @@ class AppSetting {
successfulConnection: json['successfulConnection'] == true,
dataCapThreshold: (json['dataCapThreshold'] ?? '').toString(),
onboardingCompleted: json['onboardingCompleted'] == true,
// Default to true when missing (first-time post-upgrade users
// should get the auto-enable behaviour the spec calls for).
unboundedAutoEnable: json['unboundedAutoEnable'] != false,
unboundedHidden: json['unboundedHidden'] == true,
unboundedWelcomeSeen: json['unboundedWelcomeSeen'] == true,
unboundedTotalHelped:
(json['unboundedTotalHelped'] as num?)?.toInt() ?? 0,
);
}
10 changes: 9 additions & 1 deletion lib/core/models/feature_flags.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,15 @@ enum FeatureFlag {
metrics('otel.metrics'),
traces('otel.traces'),
autoUpdateEnabled('autoUpdateEnabled'),
androidSideloadAutoUpdateEnabled('androidSideloadAutoUpdateEnabled');
androidSideloadAutoUpdateEnabled('androidSideloadAutoUpdateEnabled'),
// Server-side gate for the entire Unbounded / Share My Connection
// surface. When false (the default for censored regions), the
// Unbounded tab, settings entry, project link, and auto-enable hooks
// all disappear — censored users should never see a "share your
// connection" UI that could draw attention to them on-device. Mirrors
// radiance/unbounded/unbounded.go shouldRunUnbounded, which already
// gates execution on the same Features[UNBOUNDED] flag.
unbounded('unbounded');

final String key;

Expand Down
Loading