feat(block2): trust UI — vouch for a person in chat (web of trust)

- TrustCubit: reads how many vouch for the peer + whether YOU do; toggleVouch
  certifies/revokes over TrustTransport; never vouches for self; offline-safe.
  Kept simple — the full known-member rule (threshold+distance) and its
  bootstrap set are still-open policy (network-trust §2); a live count + your
  vouch is the honest first step.
- Chat screen now opens ONE session carrying both messaging AND trust (the
  shared-connection shape), and shows a slim vouch banner: 'Vouched for by N' +
  an 'I know this person' / 'You vouch for them' toggle.
- i18n en/es/pt (human words: 'I know this person', not 'certify').

Tests: 4 trust cubit + messaging + chat, 10 green; analyzer clean.
This commit is contained in:
vjrj 2026-07-10 11:04:07 +02:00
parent 4326e79419
commit 7cba4f7fcf
10 changed files with 367 additions and 17 deletions

View file

@ -366,5 +366,11 @@
"send": "Send",
"empty": "No messages yet — say hello",
"offline": "Set up sharing to send messages"
},
"trust": {
"none": "No one vouches for them yet",
"count": "Vouched for by {n}",
"vouch": "I know this person",
"vouched": "You vouch for them"
}
}

View file

@ -366,5 +366,11 @@
"send": "Enviar",
"empty": "Aún no hay mensajes — saluda",
"offline": "Configura el compartir para enviar mensajes"
},
"trust": {
"none": "Nadie los avala aún",
"count": "Avalada por {n}",
"vouch": "Conozco a esta persona",
"vouched": "Avalas a esta persona"
}
}

View file

@ -362,5 +362,11 @@
"send": "Enviar",
"empty": "Ainda não há mensagens — diz olá",
"offline": "Configura a partilha para enviar mensagens"
},
"trust": {
"none": "Ainda ninguém os avaliza",
"count": "Avalizada por {n}",
"vouch": "Conheço esta pessoa",
"vouched": "Avalizas esta pessoa"
}
}

View file

@ -4,9 +4,9 @@
/// To regenerate, run: `dart run slang`
///
/// Locales: 3
/// Strings: 974 (324 per locale)
/// Strings: 986 (328 per locale)
///
/// Built on 2026-07-10 at 08:58 UTC
/// Built on 2026-07-10 at 09:03 UTC
// coverage:ignore-file
// ignore_for_file: type=lint, unused_import

View file

@ -71,6 +71,7 @@ class Translations with BaseTranslations<AppLocale, Translations> {
late final Translations$unit$en unit = Translations$unit$en.internal(_root);
late final Translations$market$en market = Translations$market$en.internal(_root);
late final Translations$chat$en chat = Translations$chat$en.internal(_root);
late final Translations$trust$en trust = Translations$trust$en.internal(_root);
}
// Path: app
@ -1162,6 +1163,27 @@ class Translations$chat$en {
String get offline => 'Set up sharing to send messages';
}
// Path: trust
class Translations$trust$en {
Translations$trust$en.internal(this._root);
final Translations _root; // ignore: unused_field
// Translations
/// en: 'No one vouches for them yet'
String get none => 'No one vouches for them yet';
/// en: 'Vouched for by {n}'
String count({required Object n}) => 'Vouched for by ${n}';
/// en: 'I know this person'
String get vouch => 'I know this person';
/// en: 'You vouch for them'
String get vouched => 'You vouch for them';
}
// Path: intro.slides
class Translations$intro$slides$en {
Translations$intro$slides$en.internal(this._root);
@ -1945,6 +1967,10 @@ extension on Translations {
'chat.send' => 'Send',
'chat.empty' => 'No messages yet — say hello',
'chat.offline' => 'Set up sharing to send messages',
'trust.none' => 'No one vouches for them yet',
'trust.count' => ({required Object n}) => 'Vouched for by ${n}',
'trust.vouch' => 'I know this person',
'trust.vouched' => 'You vouch for them',
_ => null,
};
}

View file

@ -70,6 +70,7 @@ class TranslationsEs extends Translations with BaseTranslations<AppLocale, Trans
@override late final _Translations$unit$es unit = _Translations$unit$es._(_root);
@override late final _Translations$market$es market = _Translations$market$es._(_root);
@override late final _Translations$chat$es chat = _Translations$chat$es._(_root);
@override late final _Translations$trust$es trust = _Translations$trust$es._(_root);
}
// Path: app
@ -646,6 +647,19 @@ class _Translations$chat$es extends Translations$chat$en {
@override String get offline => 'Configura el compartir para enviar mensajes';
}
// Path: trust
class _Translations$trust$es extends Translations$trust$en {
_Translations$trust$es._(TranslationsEs root) : this._root = root, super.internal(root);
final TranslationsEs _root; // ignore: unused_field
// Translations
@override String get none => 'Nadie los avala aún';
@override String count({required Object n}) => 'Avalada por ${n}';
@override String get vouch => 'Conozco a esta persona';
@override String get vouched => 'Avalas a esta persona';
}
// Path: intro.slides
class _Translations$intro$slides$es extends Translations$intro$slides$en {
_Translations$intro$slides$es._(TranslationsEs root) : this._root = root, super.internal(root);
@ -1313,6 +1327,10 @@ extension on TranslationsEs {
'chat.send' => 'Enviar',
'chat.empty' => 'Aún no hay mensajes — saluda',
'chat.offline' => 'Configura el compartir para enviar mensajes',
'trust.none' => 'Nadie los avala aún',
'trust.count' => ({required Object n}) => 'Avalada por ${n}',
'trust.vouch' => 'Conozco a esta persona',
'trust.vouched' => 'Avalas a esta persona',
_ => null,
};
}

View file

@ -70,6 +70,7 @@ class TranslationsPt extends Translations with BaseTranslations<AppLocale, Trans
@override late final _Translations$unit$pt unit = _Translations$unit$pt._(_root);
@override late final _Translations$market$pt market = _Translations$market$pt._(_root);
@override late final _Translations$chat$pt chat = _Translations$chat$pt._(_root);
@override late final _Translations$trust$pt trust = _Translations$trust$pt._(_root);
}
// Path: app
@ -642,6 +643,19 @@ class _Translations$chat$pt extends Translations$chat$en {
@override String get offline => 'Configura a partilha para enviar mensagens';
}
// Path: trust
class _Translations$trust$pt extends Translations$trust$en {
_Translations$trust$pt._(TranslationsPt root) : this._root = root, super.internal(root);
final TranslationsPt _root; // ignore: unused_field
// Translations
@override String get none => 'Ainda ninguém os avaliza';
@override String count({required Object n}) => 'Avalizada por ${n}';
@override String get vouch => 'Conheço esta pessoa';
@override String get vouched => 'Avalizas esta pessoa';
}
// Path: intro.slides
class _Translations$intro$slides$pt extends Translations$intro$slides$en {
_Translations$intro$slides$pt._(TranslationsPt root) : this._root = root, super.internal(root);
@ -1305,6 +1319,10 @@ extension on TranslationsPt {
'chat.send' => 'Enviar',
'chat.empty' => 'Ainda não há mensagens — diz olá',
'chat.offline' => 'Configura a partilha para enviar mensagens',
'trust.none' => 'Ainda ninguém os avaliza',
'trust.count' => ({required Object n}) => 'Avalizada por ${n}',
'trust.vouch' => 'Conheço esta pessoa',
'trust.vouched' => 'Avalizas esta pessoa',
_ => null,
};
}