feat(sharing): make going online opt-in, and show what it unlocks

Tane dialled its four default relays at launch, before anyone had asked
for anything — an F-Droid reviewer spotted it, and they were right. The
seed book needs no network at all, so the app should not have one until
the person joins the sharing side.

- SocialSettings gains a three-state `sharingEnabled`. `null` means
  "never asked", which is what lets `migrateSharingEnabled` keep an
  existing install exactly as it was: anyone past the intro was on a
  build that connected at launch, so they keep messaging, device sync
  and offer alerts. A fresh install starts fully offline.
- bootstrap only starts the shared connection when sharing is on. The
  inbox/sync/plantaré/alert listeners are untouched: they react to a
  session, and none arrives.
- SharingSwitch is the single place that moves the stored choice, the
  live connection and the flag the UI listens to, so they cannot drift.
- Agreeing to the community rules is the opt-in — one consent surface,
  reached from the market or from the drawer's invitation.
- SocialConnection.start is now idempotent and gains stop(), so turning
  sharing off goes offline immediately instead of at the next launch.
- The social drawer entries stay visible but padlocked while sharing is
  off; tapping one explains what wakes up and offers to join. Hiding
  them would have kept the tool a secret. "Coming soon" is gone for
  good — everything it labelled is built.

Covered by tests for the migration in both directions, start/stop
lifecycle, the gate turning sharing on, the invitation, and the drawer
in all three states (no social layer / off / on).
This commit is contained in:
vjrj 2026-07-25 16:47:56 +02:00
parent 62123582f5
commit fed0e8200e
35 changed files with 926 additions and 173 deletions

View file

@ -93,6 +93,7 @@ class Translations with BaseTranslations<AppLocale, Translations> {
late final Translations$marketGate$en marketGate = Translations$marketGate$en.internal(_root);
late final Translations$report$en report = Translations$report$en.internal(_root);
late final Translations$block$en block = Translations$block$en.internal(_root);
late final Translations$sharingInvite$en sharingInvite = Translations$sharingInvite$en.internal(_root);
}
// Path: avatar
@ -340,9 +341,6 @@ class Translations$common$en {
/// en: 'Type'
String get type => 'Type';
/// en: 'Coming soon'
String get comingSoon => 'Coming soon';
/// en: 'You're offline sharing is paused'
String get offline => 'You\'re offline — sharing is paused';
}
@ -1578,6 +1576,12 @@ class Translations$market$en {
/// en: 'Photo'
String get photo => 'Photo';
/// en: 'Sharing is on'
String get sharingOnLabel => 'Sharing is on';
/// en: 'Turn this off and Tane stops connecting to any server. Your seed book keeps working just the same.'
String get sharingOnHelp => 'Turn this off and Tane stops connecting to any server. Your seed book keeps working just the same.';
}
// Path: profile
@ -2241,6 +2245,9 @@ class Translations$marketGate$en {
/// en: 'Not now'
String get decline => 'Not now';
/// en: 'To carry offers and messages between people, Tane needs to go online and leave them on community servers. Until you agree, it doesn't connect to anything.'
String get networkNote => 'To carry offers and messages between people, Tane needs to go online and leave them on community servers. Until you agree, it doesn\'t connect to anything.';
}
// Path: report
@ -2324,6 +2331,36 @@ class Translations$block$en {
String get unblock => 'Unblock';
}
// Path: sharingInvite
class Translations$sharingInvite$en {
Translations$sharingInvite$en.internal(this._root);
final Translations _root; // ignore: unused_field
// Translations
/// en: 'This wakes up when you start sharing'
String get title => 'This wakes up when you start sharing';
/// en: 'Write to whoever has seeds near you'
String get perkChat => 'Write to whoever has seeds near you';
/// en: 'Keep the offers you like'
String get perkFavorites => 'Keep the offers you like';
/// en: 'Your circle of people you trust'
String get perkPeople => 'Your circle of people you trust';
/// en: 'For that, Tane needs to go online. Until you say yes, it doesn't talk to anyone.'
String get networkNote => 'For that, Tane needs to go online. Until you say yes, it doesn\'t talk to anyone.';
/// en: 'Start sharing'
String get start => 'Start sharing';
/// en: 'Not now'
String get notNow => 'Not now';
}
// Path: intro.slides
class Translations$intro$slides$en {
Translations$intro$slides$en.internal(this._root);
@ -2839,7 +2876,6 @@ extension on Translations {
'common.delete' => 'Delete',
'common.edit' => 'Edit',
'common.type' => 'Type',
'common.comingSoon' => 'Coming soon',
'common.offline' => 'You\'re offline — sharing is paused',
'home.tagline' => 'Share and grow local seeds',
'home.openMarket' => 'Market',
@ -3218,6 +3254,8 @@ extension on Translations {
'market.copyId' => 'Copy code',
'market.idCopied' => 'Code copied',
'market.photo' => 'Photo',
'market.sharingOnLabel' => 'Sharing is on',
'market.sharingOnHelp' => 'Turn this off and Tane stops connecting to any server. Your seed book keeps working just the same.',
'profile.title' => 'Your profile',
'profile.name' => 'Display name',
'profile.nameHint' => 'How others see you',
@ -3292,9 +3330,9 @@ extension on Translations {
'plantare.delete' => 'Remove',
'plantare.statusReturned' => 'Returned',
'plantare.statusForgiven' => 'Settled',
'plantare.openSection' => 'Open',
_ => null,
} ?? switch (path) {
'plantare.openSection' => 'Open',
'plantare.settledSection' => 'Done',
'plantare.removeConfirm' => 'Remove this commitment?',
'plantare.returnBy' => ({required Object date}) => 'Return by ${date}',
@ -3402,6 +3440,7 @@ extension on Translations {
'marketGate.viewLegal' => 'Privacy & rules',
'marketGate.accept' => 'I agree',
'marketGate.decline' => 'Not now',
'marketGate.networkNote' => 'To carry offers and messages between people, Tane needs to go online and leave them on community servers. Until you agree, it doesn\'t connect to anything.',
'report.offer' => 'Report this offer',
'report.person' => 'Report this person',
'report.title' => 'Report',
@ -3423,6 +3462,13 @@ extension on Translations {
'block.manageTitle' => 'Blocked people',
'block.manageEmpty' => 'You haven\'t blocked anyone',
'block.unblock' => 'Unblock',
'sharingInvite.title' => 'This wakes up when you start sharing',
'sharingInvite.perkChat' => 'Write to whoever has seeds near you',
'sharingInvite.perkFavorites' => 'Keep the offers you like',
'sharingInvite.perkPeople' => 'Your circle of people you trust',
'sharingInvite.networkNote' => 'For that, Tane needs to go online. Until you say yes, it doesn\'t talk to anyone.',
'sharingInvite.start' => 'Start sharing',
'sharingInvite.notNow' => 'Not now',
_ => null,
};
}