diff --git a/apps/app_seeds/lib/i18n/en.i18n.json b/apps/app_seeds/lib/i18n/en.i18n.json index 9d61506..7b56325 100644 --- a/apps/app_seeds/lib/i18n/en.i18n.json +++ b/apps/app_seeds/lib/i18n/en.i18n.json @@ -12,8 +12,8 @@ }, "home": { "tagline": "Share and grow local seeds", - "openMarket": "Open market", - "openMarketSubtitle": "Browse and exchange", + "openMarket": "Market", + "openMarketSubtitle": "Discover and share seeds nearby", "yourInventory": "Your inventory", "yourInventorySubtitle": "Manage your seeds" }, @@ -339,6 +339,11 @@ "notSetUp": "Sharing isn't set up yet", "notSetUpBody": "Turn on sharing to see and give seeds to people near you. It stays rough — your zone, never your exact address.", "setUp": "Set up sharing", + "cantReach": "Can't reach the community servers right now", + "cantReachBody": "Try again, or check the servers under Advanced setup.", + "retry": "Retry", + "setArea": "Set your area", + "setAreaBody": "Tell the market roughly where you are to see seeds nearby.", "searching": "Looking around your area…", "empty": "No seeds shared near you yet", "near": "Near you", @@ -348,8 +353,13 @@ "setupIntro": "Sharing with people nearby is optional. Just set your rough area — you're already connected to shared community servers so people can find what you offer, with no company in the middle.", "areaLabel": "Your area", "areaHelp": "Kept rough on purpose — your zone, never an exact spot.", + "areaSet": "Your area is set — kept coarse, never your exact spot", + "areaNotSet": "Area not set yet — use your location, or add a code under Advanced", + "advanced": "Advanced", + "areaCodeLabel": "Area code", + "areaCodeHint": "A short code like sp3e9 — not a place name", "serversLabel": "Community servers", - "serversHelp": "Pick one or more shared servers. Anyone can use them — or add your own community's below.", + "serversHelp": "One address per line (wss://…). Leave the defaults if unsure.", "serversAdvanced": "Add another server", "save": "Save", "saved": "Saved", @@ -367,6 +377,8 @@ "nameHint": "How others see you", "about": "About", "aboutHint": "A short line — what you grow, where", + "g1": "Ğ1 address (optional)", + "g1Hint": "So people can pay you in Ğ1 — separate from your key", "yourId": "Your identity", "idHelp": "Share this so people can recognise you", "copy": "Copy", @@ -383,7 +395,9 @@ "hint": "Write a message…", "send": "Send", "empty": "No messages yet — say hello", - "offline": "Set up sharing to send messages" + "offline": "Set up sharing to send messages", + "payG1": "Pay in Ğ1", + "g1Copied": "Ğ1 address copied — paste it in your wallet" }, "trust": { "none": "No one vouches for them yet", diff --git a/apps/app_seeds/lib/i18n/es.i18n.json b/apps/app_seeds/lib/i18n/es.i18n.json index 8957e1b..fa2ba35 100644 --- a/apps/app_seeds/lib/i18n/es.i18n.json +++ b/apps/app_seeds/lib/i18n/es.i18n.json @@ -12,8 +12,8 @@ }, "home": { "tagline": "Comparte y cultiva semillas locales", - "openMarket": "Mercado abierto", - "openMarketSubtitle": "Explora e intercambia", + "openMarket": "Mercado", + "openMarketSubtitle": "Descubre y comparte semillas cerca", "yourInventory": "Tu inventario", "yourInventorySubtitle": "Gestiona tus semillas" }, @@ -339,6 +339,11 @@ "notSetUp": "Aún no has configurado el compartir", "notSetUpBody": "Activa el compartir para ver y dar semillas a gente cercana. Se mantiene aproximado — tu zona, nunca tu dirección exacta.", "setUp": "Configurar el compartir", + "cantReach": "No se puede conectar con los servidores ahora mismo", + "cantReachBody": "Inténtalo de nuevo, o revisa los servidores en la configuración avanzada.", + "retry": "Reintentar", + "setArea": "Indica tu zona", + "setAreaBody": "Dile al mercado tu zona aproximada para ver semillas cerca.", "searching": "Buscando por tu zona…", "empty": "Aún no hay semillas compartidas cerca de ti", "near": "Cerca de ti", @@ -348,8 +353,13 @@ "setupIntro": "Compartir con gente cercana es opcional. Solo indica tu zona aproximada — ya estás conectado a servidores comunitarios compartidos para que otras personas encuentren lo que ofreces, sin ninguna empresa en medio.", "areaLabel": "Tu zona", "areaHelp": "Se mantiene aproximado a propósito — tu zona, nunca un punto exacto.", + "areaSet": "Tu zona está puesta — aproximada, nunca tu punto exacto", + "areaNotSet": "Zona sin definir — usa tu ubicación, o añade un código en Avanzado", + "advanced": "Avanzado", + "areaCodeLabel": "Código de zona", + "areaCodeHint": "Un código corto como sp3e9 — no un nombre de lugar", "serversLabel": "Servidores de la comunidad", - "serversHelp": "Elige uno o más servidores compartidos. Cualquiera puede usarlos — o añade abajo el de tu comunidad.", + "serversHelp": "Una dirección por línea (wss://…). Déjalo así si no sabes.", "serversAdvanced": "Añadir otro servidor", "save": "Guardar", "saved": "Guardado", @@ -367,6 +377,8 @@ "nameHint": "Cómo te ven los demás", "about": "Sobre ti", "aboutHint": "Una línea — qué cultivas, dónde", + "g1": "Dirección Ğ1 (opcional)", + "g1Hint": "Para que te paguen en Ğ1 — aparte de tu clave", "yourId": "Tu identidad", "idHelp": "Compártela para que te reconozcan", "copy": "Copiar", @@ -383,7 +395,9 @@ "hint": "Escribe un mensaje…", "send": "Enviar", "empty": "Aún no hay mensajes — saluda", - "offline": "Configura el compartir para enviar mensajes" + "offline": "Configura el compartir para enviar mensajes", + "payG1": "Pagar en Ğ1", + "g1Copied": "Dirección Ğ1 copiada — pégala en tu cartera" }, "trust": { "none": "Nadie los avala aún", diff --git a/apps/app_seeds/lib/i18n/pt.i18n.json b/apps/app_seeds/lib/i18n/pt.i18n.json index 20408da..698880d 100644 --- a/apps/app_seeds/lib/i18n/pt.i18n.json +++ b/apps/app_seeds/lib/i18n/pt.i18n.json @@ -12,8 +12,8 @@ }, "home": { "tagline": "Partilha e cultiva sementes locais", - "openMarket": "Mercado aberto", - "openMarketSubtitle": "Descobre e troca", + "openMarket": "Mercado", + "openMarketSubtitle": "Descobre e partilha sementes por perto", "yourInventory": "O teu inventário", "yourInventorySubtitle": "Gere as tuas sementes" }, @@ -335,6 +335,11 @@ "notSetUp": "Ainda não configuraste a partilha", "notSetUpBody": "Ativa a partilha para ver e dar sementes a pessoas por perto. Mantém-se aproximado — a tua zona, nunca a tua morada exata.", "setUp": "Configurar a partilha", + "cantReach": "Não é possível ligar aos servidores neste momento", + "cantReachBody": "Tenta de novo, ou verifica os servidores na configuração avançada.", + "retry": "Tentar de novo", + "setArea": "Indica a tua zona", + "setAreaBody": "Diz ao mercado a tua zona aproximada para ver sementes por perto.", "searching": "A procurar pela tua zona…", "empty": "Ainda não há sementes partilhadas perto de ti", "near": "Perto de ti", @@ -344,8 +349,13 @@ "setupIntro": "Partilhar com pessoas por perto é opcional. Basta indicar a tua zona aproximada — já estás ligado a servidores comunitários partilhados para que outras pessoas encontrem o que ofereces, sem nenhuma empresa no meio.", "areaLabel": "A tua zona", "areaHelp": "Mantém-se aproximado de propósito — a tua zona, nunca um ponto exato.", + "areaSet": "A tua zona está definida — aproximada, nunca o teu ponto exato", + "areaNotSet": "Zona por definir — usa a tua localização, ou adiciona um código em Avançado", + "advanced": "Avançado", + "areaCodeLabel": "Código de zona", + "areaCodeHint": "Um código curto como sp3e9 — não um nome de lugar", "serversLabel": "Servidores da comunidade", - "serversHelp": "Escolhe um ou mais servidores partilhados. Qualquer pessoa os pode usar — ou adiciona abaixo o da tua comunidade.", + "serversHelp": "Um endereço por linha (wss://…). Deixa assim se não souberes.", "serversAdvanced": "Adicionar outro servidor", "save": "Guardar", "saved": "Guardado", @@ -363,6 +373,8 @@ "nameHint": "Como os outros te veem", "about": "Sobre ti", "aboutHint": "Uma linha — o que cultivas, onde", + "g1": "Endereço Ğ1 (opcional)", + "g1Hint": "Para que te paguem em Ğ1 — separado da tua chave", "yourId": "A tua identidade", "idHelp": "Partilha-a para que te reconheçam", "copy": "Copiar", @@ -379,7 +391,9 @@ "hint": "Escreve uma mensagem…", "send": "Enviar", "empty": "Ainda não há mensagens — diz olá", - "offline": "Configura a partilha para enviar mensagens" + "offline": "Configura a partilha para enviar mensagens", + "payG1": "Pagar em Ğ1", + "g1Copied": "Endereço Ğ1 copiado — cola-o na tua carteira" }, "trust": { "none": "Ainda ninguém os avaliza", diff --git a/apps/app_seeds/lib/i18n/strings.g.dart b/apps/app_seeds/lib/i18n/strings.g.dart index b2cca1f..342d3c9 100644 --- a/apps/app_seeds/lib/i18n/strings.g.dart +++ b/apps/app_seeds/lib/i18n/strings.g.dart @@ -4,9 +4,9 @@ /// To regenerate, run: `dart run slang` /// /// Locales: 3 -/// Strings: 1031 (343 per locale) +/// Strings: 1073 (357 per locale) /// -/// Built on 2026-07-10 at 10:36 UTC +/// Built on 2026-07-10 at 11:35 UTC // coverage:ignore-file // ignore_for_file: type=lint, unused_import diff --git a/apps/app_seeds/lib/i18n/strings_en.g.dart b/apps/app_seeds/lib/i18n/strings_en.g.dart index 747b95f..ee506e0 100644 --- a/apps/app_seeds/lib/i18n/strings_en.g.dart +++ b/apps/app_seeds/lib/i18n/strings_en.g.dart @@ -126,11 +126,11 @@ class Translations$home$en { /// en: 'Share and grow local seeds' String get tagline => 'Share and grow local seeds'; - /// en: 'Open market' - String get openMarket => 'Open market'; + /// en: 'Market' + String get openMarket => 'Market'; - /// en: 'Browse and exchange' - String get openMarketSubtitle => 'Browse and exchange'; + /// en: 'Discover and share seeds nearby' + String get openMarketSubtitle => 'Discover and share seeds nearby'; /// en: 'Your inventory' String get yourInventory => 'Your inventory'; @@ -1080,6 +1080,21 @@ class Translations$market$en { /// en: 'Set up sharing' String get setUp => 'Set up sharing'; + /// en: 'Can't reach the community servers right now' + String get cantReach => 'Can\'t reach the community servers right now'; + + /// en: 'Try again, or check the servers under Advanced setup.' + String get cantReachBody => 'Try again, or check the servers under Advanced setup.'; + + /// en: 'Retry' + String get retry => 'Retry'; + + /// en: 'Set your area' + String get setArea => 'Set your area'; + + /// en: 'Tell the market roughly where you are to see seeds nearby.' + String get setAreaBody => 'Tell the market roughly where you are to see seeds nearby.'; + /// en: 'Looking around your area…' String get searching => 'Looking around your area…'; @@ -1107,11 +1122,26 @@ class Translations$market$en { /// en: 'Kept rough on purpose — your zone, never an exact spot.' String get areaHelp => 'Kept rough on purpose — your zone, never an exact spot.'; + /// en: 'Your area is set — kept coarse, never your exact spot' + String get areaSet => 'Your area is set — kept coarse, never your exact spot'; + + /// en: 'Area not set yet — use your location, or add a code under Advanced' + String get areaNotSet => 'Area not set yet — use your location, or add a code under Advanced'; + + /// en: 'Advanced' + String get advanced => 'Advanced'; + + /// en: 'Area code' + String get areaCodeLabel => 'Area code'; + + /// en: 'A short code like sp3e9 — not a place name' + String get areaCodeHint => 'A short code like sp3e9 — not a place name'; + /// en: 'Community servers' String get serversLabel => 'Community servers'; - /// en: 'Pick one or more shared servers. Anyone can use them — or add your own community's below.' - String get serversHelp => 'Pick one or more shared servers. Anyone can use them — or add your own community\'s below.'; + /// en: 'One address per line (wss://…). Leave the defaults if unsure.' + String get serversHelp => 'One address per line (wss://…). Leave the defaults if unsure.'; /// en: 'Add another server' String get serversAdvanced => 'Add another server'; @@ -1167,6 +1197,12 @@ class Translations$profile$en { /// en: 'A short line — what you grow, where' String get aboutHint => 'A short line — what you grow, where'; + /// en: 'Ğ1 address (optional)' + String get g1 => 'Ğ1 address (optional)'; + + /// en: 'So people can pay you in Ğ1 — separate from your key' + String get g1Hint => 'So people can pay you in Ğ1 — separate from your key'; + /// en: 'Your identity' String get yourId => 'Your identity'; @@ -1223,6 +1259,12 @@ class Translations$chat$en { /// en: 'Set up sharing to send messages' String get offline => 'Set up sharing to send messages'; + + /// en: 'Pay in Ğ1' + String get payG1 => 'Pay in Ğ1'; + + /// en: 'Ğ1 address copied — paste it in your wallet' + String get g1Copied => 'Ğ1 address copied — paste it in your wallet'; } // Path: trust @@ -1714,8 +1756,8 @@ extension on Translations { 'common.type' => 'Type', 'common.comingSoon' => 'Coming soon', 'home.tagline' => 'Share and grow local seeds', - 'home.openMarket' => 'Open market', - 'home.openMarketSubtitle' => 'Browse and exchange', + 'home.openMarket' => 'Market', + 'home.openMarketSubtitle' => 'Discover and share seeds nearby', 'home.yourInventory' => 'Your inventory', 'home.yourInventorySubtitle' => 'Manage your seeds', 'photo.camera' => 'Take a photo', @@ -2007,6 +2049,11 @@ extension on Translations { 'market.notSetUp' => 'Sharing isn\'t set up yet', 'market.notSetUpBody' => 'Turn on sharing to see and give seeds to people near you. It stays rough — your zone, never your exact address.', 'market.setUp' => 'Set up sharing', + 'market.cantReach' => 'Can\'t reach the community servers right now', + 'market.cantReachBody' => 'Try again, or check the servers under Advanced setup.', + 'market.retry' => 'Retry', + 'market.setArea' => 'Set your area', + 'market.setAreaBody' => 'Tell the market roughly where you are to see seeds nearby.', 'market.searching' => 'Looking around your area…', 'market.empty' => 'No seeds shared near you yet', 'market.near' => 'Near you', @@ -2016,8 +2063,13 @@ extension on Translations { 'market.setupIntro' => 'Sharing with people nearby is optional. Just set your rough area — you\'re already connected to shared community servers so people can find what you offer, with no company in the middle.', 'market.areaLabel' => 'Your area', 'market.areaHelp' => 'Kept rough on purpose — your zone, never an exact spot.', + 'market.areaSet' => 'Your area is set — kept coarse, never your exact spot', + 'market.areaNotSet' => 'Area not set yet — use your location, or add a code under Advanced', + 'market.advanced' => 'Advanced', + 'market.areaCodeLabel' => 'Area code', + 'market.areaCodeHint' => 'A short code like sp3e9 — not a place name', 'market.serversLabel' => 'Community servers', - 'market.serversHelp' => 'Pick one or more shared servers. Anyone can use them — or add your own community\'s below.', + 'market.serversHelp' => 'One address per line (wss://…). Leave the defaults if unsure.', 'market.serversAdvanced' => 'Add another server', 'market.save' => 'Save', 'market.saved' => 'Saved', @@ -2033,6 +2085,8 @@ extension on Translations { 'profile.nameHint' => 'How others see you', 'profile.about' => 'About', 'profile.aboutHint' => 'A short line — what you grow, where', + 'profile.g1' => 'Ğ1 address (optional)', + 'profile.g1Hint' => 'So people can pay you in Ğ1 — separate from your key', 'profile.yourId' => 'Your identity', 'profile.idHelp' => 'Share this so people can recognise you', 'profile.copy' => 'Copy', @@ -2046,6 +2100,8 @@ extension on Translations { 'chat.send' => 'Send', 'chat.empty' => 'No messages yet — say hello', 'chat.offline' => 'Set up sharing to send messages', + 'chat.payG1' => 'Pay in Ğ1', + 'chat.g1Copied' => 'Ğ1 address copied — paste it in your wallet', 'trust.none' => 'No one vouches for them yet', 'trust.count' => ({required Object n}) => 'Vouched for by ${n}', 'trust.vouch' => 'I know this person', diff --git a/apps/app_seeds/lib/i18n/strings_es.g.dart b/apps/app_seeds/lib/i18n/strings_es.g.dart index a18ecd9..bec2514 100644 --- a/apps/app_seeds/lib/i18n/strings_es.g.dart +++ b/apps/app_seeds/lib/i18n/strings_es.g.dart @@ -108,8 +108,8 @@ class _Translations$home$es extends Translations$home$en { // Translations @override String get tagline => 'Comparte y cultiva semillas locales'; - @override String get openMarket => 'Mercado abierto'; - @override String get openMarketSubtitle => 'Explora e intercambia'; + @override String get openMarket => 'Mercado'; + @override String get openMarketSubtitle => 'Descubre y comparte semillas cerca'; @override String get yourInventory => 'Tu inventario'; @override String get yourInventorySubtitle => 'Gestiona tus semillas'; } @@ -613,6 +613,11 @@ class _Translations$market$es extends Translations$market$en { @override String get notSetUp => 'Aún no has configurado el compartir'; @override String get notSetUpBody => 'Activa el compartir para ver y dar semillas a gente cercana. Se mantiene aproximado — tu zona, nunca tu dirección exacta.'; @override String get setUp => 'Configurar el compartir'; + @override String get cantReach => 'No se puede conectar con los servidores ahora mismo'; + @override String get cantReachBody => 'Inténtalo de nuevo, o revisa los servidores en la configuración avanzada.'; + @override String get retry => 'Reintentar'; + @override String get setArea => 'Indica tu zona'; + @override String get setAreaBody => 'Dile al mercado tu zona aproximada para ver semillas cerca.'; @override String get searching => 'Buscando por tu zona…'; @override String get empty => 'Aún no hay semillas compartidas cerca de ti'; @override String get near => 'Cerca de ti'; @@ -622,8 +627,13 @@ class _Translations$market$es extends Translations$market$en { @override String get setupIntro => 'Compartir con gente cercana es opcional. Solo indica tu zona aproximada — ya estás conectado a servidores comunitarios compartidos para que otras personas encuentren lo que ofreces, sin ninguna empresa en medio.'; @override String get areaLabel => 'Tu zona'; @override String get areaHelp => 'Se mantiene aproximado a propósito — tu zona, nunca un punto exacto.'; + @override String get areaSet => 'Tu zona está puesta — aproximada, nunca tu punto exacto'; + @override String get areaNotSet => 'Zona sin definir — usa tu ubicación, o añade un código en Avanzado'; + @override String get advanced => 'Avanzado'; + @override String get areaCodeLabel => 'Código de zona'; + @override String get areaCodeHint => 'Un código corto como sp3e9 — no un nombre de lugar'; @override String get serversLabel => 'Servidores de la comunidad'; - @override String get serversHelp => 'Elige uno o más servidores compartidos. Cualquiera puede usarlos — o añade abajo el de tu comunidad.'; + @override String get serversHelp => 'Una dirección por línea (wss://…). Déjalo así si no sabes.'; @override String get serversAdvanced => 'Añadir otro servidor'; @override String get save => 'Guardar'; @override String get saved => 'Guardado'; @@ -648,6 +658,8 @@ class _Translations$profile$es extends Translations$profile$en { @override String get nameHint => 'Cómo te ven los demás'; @override String get about => 'Sobre ti'; @override String get aboutHint => 'Una línea — qué cultivas, dónde'; + @override String get g1 => 'Dirección Ğ1 (opcional)'; + @override String get g1Hint => 'Para que te paguen en Ğ1 — aparte de tu clave'; @override String get yourId => 'Tu identidad'; @override String get idHelp => 'Compártela para que te reconozcan'; @override String get copy => 'Copiar'; @@ -679,6 +691,8 @@ class _Translations$chat$es extends Translations$chat$en { @override String get send => 'Enviar'; @override String get empty => 'Aún no hay mensajes — saluda'; @override String get offline => 'Configura el compartir para enviar mensajes'; + @override String get payG1 => 'Pagar en Ğ1'; + @override String get g1Copied => 'Dirección Ğ1 copiada — pégala en tu cartera'; } // Path: trust @@ -1044,8 +1058,8 @@ extension on TranslationsEs { 'common.type' => 'Tipo', 'common.comingSoon' => 'Pronto', 'home.tagline' => 'Comparte y cultiva semillas locales', - 'home.openMarket' => 'Mercado abierto', - 'home.openMarketSubtitle' => 'Explora e intercambia', + 'home.openMarket' => 'Mercado', + 'home.openMarketSubtitle' => 'Descubre y comparte semillas cerca', 'home.yourInventory' => 'Tu inventario', 'home.yourInventorySubtitle' => 'Gestiona tus semillas', 'photo.camera' => 'Hacer una foto', @@ -1337,6 +1351,11 @@ extension on TranslationsEs { 'market.notSetUp' => 'Aún no has configurado el compartir', 'market.notSetUpBody' => 'Activa el compartir para ver y dar semillas a gente cercana. Se mantiene aproximado — tu zona, nunca tu dirección exacta.', 'market.setUp' => 'Configurar el compartir', + 'market.cantReach' => 'No se puede conectar con los servidores ahora mismo', + 'market.cantReachBody' => 'Inténtalo de nuevo, o revisa los servidores en la configuración avanzada.', + 'market.retry' => 'Reintentar', + 'market.setArea' => 'Indica tu zona', + 'market.setAreaBody' => 'Dile al mercado tu zona aproximada para ver semillas cerca.', 'market.searching' => 'Buscando por tu zona…', 'market.empty' => 'Aún no hay semillas compartidas cerca de ti', 'market.near' => 'Cerca de ti', @@ -1346,8 +1365,13 @@ extension on TranslationsEs { 'market.setupIntro' => 'Compartir con gente cercana es opcional. Solo indica tu zona aproximada — ya estás conectado a servidores comunitarios compartidos para que otras personas encuentren lo que ofreces, sin ninguna empresa en medio.', 'market.areaLabel' => 'Tu zona', 'market.areaHelp' => 'Se mantiene aproximado a propósito — tu zona, nunca un punto exacto.', + 'market.areaSet' => 'Tu zona está puesta — aproximada, nunca tu punto exacto', + 'market.areaNotSet' => 'Zona sin definir — usa tu ubicación, o añade un código en Avanzado', + 'market.advanced' => 'Avanzado', + 'market.areaCodeLabel' => 'Código de zona', + 'market.areaCodeHint' => 'Un código corto como sp3e9 — no un nombre de lugar', 'market.serversLabel' => 'Servidores de la comunidad', - 'market.serversHelp' => 'Elige uno o más servidores compartidos. Cualquiera puede usarlos — o añade abajo el de tu comunidad.', + 'market.serversHelp' => 'Una dirección por línea (wss://…). Déjalo así si no sabes.', 'market.serversAdvanced' => 'Añadir otro servidor', 'market.save' => 'Guardar', 'market.saved' => 'Guardado', @@ -1363,6 +1387,8 @@ extension on TranslationsEs { 'profile.nameHint' => 'Cómo te ven los demás', 'profile.about' => 'Sobre ti', 'profile.aboutHint' => 'Una línea — qué cultivas, dónde', + 'profile.g1' => 'Dirección Ğ1 (opcional)', + 'profile.g1Hint' => 'Para que te paguen en Ğ1 — aparte de tu clave', 'profile.yourId' => 'Tu identidad', 'profile.idHelp' => 'Compártela para que te reconozcan', 'profile.copy' => 'Copiar', @@ -1376,6 +1402,8 @@ extension on TranslationsEs { 'chat.send' => 'Enviar', 'chat.empty' => 'Aún no hay mensajes — saluda', 'chat.offline' => 'Configura el compartir para enviar mensajes', + 'chat.payG1' => 'Pagar en Ğ1', + 'chat.g1Copied' => 'Dirección Ğ1 copiada — pégala en tu cartera', 'trust.none' => 'Nadie los avala aún', 'trust.count' => ({required Object n}) => 'Avalada por ${n}', 'trust.vouch' => 'Conozco a esta persona', diff --git a/apps/app_seeds/lib/i18n/strings_pt.g.dart b/apps/app_seeds/lib/i18n/strings_pt.g.dart index 882f447..fd060db 100644 --- a/apps/app_seeds/lib/i18n/strings_pt.g.dart +++ b/apps/app_seeds/lib/i18n/strings_pt.g.dart @@ -108,8 +108,8 @@ class _Translations$home$pt extends Translations$home$en { // Translations @override String get tagline => 'Partilha e cultiva sementes locais'; - @override String get openMarket => 'Mercado aberto'; - @override String get openMarketSubtitle => 'Descobre e troca'; + @override String get openMarket => 'Mercado'; + @override String get openMarketSubtitle => 'Descobre e partilha sementes por perto'; @override String get yourInventory => 'O teu inventário'; @override String get yourInventorySubtitle => 'Gere as tuas sementes'; } @@ -609,6 +609,11 @@ class _Translations$market$pt extends Translations$market$en { @override String get notSetUp => 'Ainda não configuraste a partilha'; @override String get notSetUpBody => 'Ativa a partilha para ver e dar sementes a pessoas por perto. Mantém-se aproximado — a tua zona, nunca a tua morada exata.'; @override String get setUp => 'Configurar a partilha'; + @override String get cantReach => 'Não é possível ligar aos servidores neste momento'; + @override String get cantReachBody => 'Tenta de novo, ou verifica os servidores na configuração avançada.'; + @override String get retry => 'Tentar de novo'; + @override String get setArea => 'Indica a tua zona'; + @override String get setAreaBody => 'Diz ao mercado a tua zona aproximada para ver sementes por perto.'; @override String get searching => 'A procurar pela tua zona…'; @override String get empty => 'Ainda não há sementes partilhadas perto de ti'; @override String get near => 'Perto de ti'; @@ -618,8 +623,13 @@ class _Translations$market$pt extends Translations$market$en { @override String get setupIntro => 'Partilhar com pessoas por perto é opcional. Basta indicar a tua zona aproximada — já estás ligado a servidores comunitários partilhados para que outras pessoas encontrem o que ofereces, sem nenhuma empresa no meio.'; @override String get areaLabel => 'A tua zona'; @override String get areaHelp => 'Mantém-se aproximado de propósito — a tua zona, nunca um ponto exato.'; + @override String get areaSet => 'A tua zona está definida — aproximada, nunca o teu ponto exato'; + @override String get areaNotSet => 'Zona por definir — usa a tua localização, ou adiciona um código em Avançado'; + @override String get advanced => 'Avançado'; + @override String get areaCodeLabel => 'Código de zona'; + @override String get areaCodeHint => 'Um código curto como sp3e9 — não um nome de lugar'; @override String get serversLabel => 'Servidores da comunidade'; - @override String get serversHelp => 'Escolhe um ou mais servidores partilhados. Qualquer pessoa os pode usar — ou adiciona abaixo o da tua comunidade.'; + @override String get serversHelp => 'Um endereço por linha (wss://…). Deixa assim se não souberes.'; @override String get serversAdvanced => 'Adicionar outro servidor'; @override String get save => 'Guardar'; @override String get saved => 'Guardado'; @@ -644,6 +654,8 @@ class _Translations$profile$pt extends Translations$profile$en { @override String get nameHint => 'Como os outros te veem'; @override String get about => 'Sobre ti'; @override String get aboutHint => 'Uma linha — o que cultivas, onde'; + @override String get g1 => 'Endereço Ğ1 (opcional)'; + @override String get g1Hint => 'Para que te paguem em Ğ1 — separado da tua chave'; @override String get yourId => 'A tua identidade'; @override String get idHelp => 'Partilha-a para que te reconheçam'; @override String get copy => 'Copiar'; @@ -675,6 +687,8 @@ class _Translations$chat$pt extends Translations$chat$en { @override String get send => 'Enviar'; @override String get empty => 'Ainda não há mensagens — diz olá'; @override String get offline => 'Configura a partilha para enviar mensagens'; + @override String get payG1 => 'Pagar em Ğ1'; + @override String get g1Copied => 'Endereço Ğ1 copiado — cola-o na tua carteira'; } // Path: trust @@ -1040,8 +1054,8 @@ extension on TranslationsPt { 'common.type' => 'Tipo', 'common.comingSoon' => 'Em breve', 'home.tagline' => 'Partilha e cultiva sementes locais', - 'home.openMarket' => 'Mercado aberto', - 'home.openMarketSubtitle' => 'Descobre e troca', + 'home.openMarket' => 'Mercado', + 'home.openMarketSubtitle' => 'Descobre e partilha sementes por perto', 'home.yourInventory' => 'O teu inventário', 'home.yourInventorySubtitle' => 'Gere as tuas sementes', 'photo.camera' => 'Tirar uma foto', @@ -1329,6 +1343,11 @@ extension on TranslationsPt { 'market.notSetUp' => 'Ainda não configuraste a partilha', 'market.notSetUpBody' => 'Ativa a partilha para ver e dar sementes a pessoas por perto. Mantém-se aproximado — a tua zona, nunca a tua morada exata.', 'market.setUp' => 'Configurar a partilha', + 'market.cantReach' => 'Não é possível ligar aos servidores neste momento', + 'market.cantReachBody' => 'Tenta de novo, ou verifica os servidores na configuração avançada.', + 'market.retry' => 'Tentar de novo', + 'market.setArea' => 'Indica a tua zona', + 'market.setAreaBody' => 'Diz ao mercado a tua zona aproximada para ver sementes por perto.', 'market.searching' => 'A procurar pela tua zona…', 'market.empty' => 'Ainda não há sementes partilhadas perto de ti', 'market.near' => 'Perto de ti', @@ -1338,8 +1357,13 @@ extension on TranslationsPt { 'market.setupIntro' => 'Partilhar com pessoas por perto é opcional. Basta indicar a tua zona aproximada — já estás ligado a servidores comunitários partilhados para que outras pessoas encontrem o que ofereces, sem nenhuma empresa no meio.', 'market.areaLabel' => 'A tua zona', 'market.areaHelp' => 'Mantém-se aproximado de propósito — a tua zona, nunca um ponto exato.', + 'market.areaSet' => 'A tua zona está definida — aproximada, nunca o teu ponto exato', + 'market.areaNotSet' => 'Zona por definir — usa a tua localização, ou adiciona um código em Avançado', + 'market.advanced' => 'Avançado', + 'market.areaCodeLabel' => 'Código de zona', + 'market.areaCodeHint' => 'Um código curto como sp3e9 — não um nome de lugar', 'market.serversLabel' => 'Servidores da comunidade', - 'market.serversHelp' => 'Escolhe um ou mais servidores partilhados. Qualquer pessoa os pode usar — ou adiciona abaixo o da tua comunidade.', + 'market.serversHelp' => 'Um endereço por linha (wss://…). Deixa assim se não souberes.', 'market.serversAdvanced' => 'Adicionar outro servidor', 'market.save' => 'Guardar', 'market.saved' => 'Guardado', @@ -1355,6 +1379,8 @@ extension on TranslationsPt { 'profile.nameHint' => 'Como os outros te veem', 'profile.about' => 'Sobre ti', 'profile.aboutHint' => 'Uma linha — o que cultivas, onde', + 'profile.g1' => 'Endereço Ğ1 (opcional)', + 'profile.g1Hint' => 'Para que te paguem em Ğ1 — separado da tua chave', 'profile.yourId' => 'A tua identidade', 'profile.idHelp' => 'Partilha-a para que te reconheçam', 'profile.copy' => 'Copiar', @@ -1368,6 +1394,8 @@ extension on TranslationsPt { 'chat.send' => 'Enviar', 'chat.empty' => 'Ainda não há mensagens — diz olá', 'chat.offline' => 'Configura a partilha para enviar mensagens', + 'chat.payG1' => 'Pagar em Ğ1', + 'chat.g1Copied' => 'Endereço Ğ1 copiado — cola-o na tua carteira', 'trust.none' => 'Ainda ninguém os avaliza', 'trust.count' => ({required Object n}) => 'Avalizada por ${n}', 'trust.vouch' => 'Conheço esta pessoa', diff --git a/apps/app_seeds/lib/services/coarse_location.dart b/apps/app_seeds/lib/services/coarse_location.dart index 5dfef0b..aa576ec 100644 --- a/apps/app_seeds/lib/services/coarse_location.dart +++ b/apps/app_seeds/lib/services/coarse_location.dart @@ -20,16 +20,13 @@ class GeolocatorCoarseLocation implements CoarseLocationProvider { if (permission == LocationPermission.denied) { permission = await Geolocator.requestPermission(); } - // Give the user a way out of a permanent denial. - if (permission == LocationPermission.deniedForever) { - await Geolocator.openAppSettings(); - return null; - } - if (permission == LocationPermission.denied) return null; - if (!await Geolocator.isLocationServiceEnabled()) { - await Geolocator.openLocationSettings(); + // Denied (or turned off): return null quietly — the UI shows an actionable + // message. We don't yank the user into system settings. + if (permission == LocationPermission.denied || + permission == LocationPermission.deniedForever) { return null; } + if (!await Geolocator.isLocationServiceEnabled()) return null; // A fresh coarse fix can take a while (or never come indoors); fall back // to the last known position so the button stays responsive. diff --git a/apps/app_seeds/lib/services/profile_store.dart b/apps/app_seeds/lib/services/profile_store.dart index 99e2660..d99c4f4 100644 --- a/apps/app_seeds/lib/services/profile_store.dart +++ b/apps/app_seeds/lib/services/profile_store.dart @@ -9,12 +9,21 @@ class ProfileStore { final SecretStore _store; static const _nameKey = 'tane.social.profile.name'; static const _aboutKey = 'tane.social.profile.about'; + static const _g1Key = 'tane.social.profile.g1'; Future name() async => (await _store.read(_nameKey)) ?? ''; Future about() async => (await _store.read(_aboutKey)) ?? ''; - Future save({required String name, required String about}) async { + /// Your Ğ1 (Duniter) address, if you chose to share one. + Future g1() async => (await _store.read(_g1Key)) ?? ''; + + Future save({ + required String name, + required String about, + String g1 = '', + }) async { await _store.write(_nameKey, name.trim()); await _store.write(_aboutKey, about.trim()); + await _store.write(_g1Key, g1.trim()); } } diff --git a/apps/app_seeds/lib/state/offers_cubit.dart b/apps/app_seeds/lib/state/offers_cubit.dart index f9b2d46..053f6e7 100644 --- a/apps/app_seeds/lib/state/offers_cubit.dart +++ b/apps/app_seeds/lib/state/offers_cubit.dart @@ -76,6 +76,7 @@ class OffersCubit extends Cubit { /// (null in tests, where the transport is a fake with nothing to close). final Future Function()? _onDispose; StreamSubscription? _sub; + Timer? _searchTimeout; /// Whether a live transport is available (relay configured and reachable). bool get isOnline => _transport != null; @@ -88,6 +89,7 @@ class OffersCubit extends Cubit { return; } await _sub?.cancel(); + _searchTimeout?.cancel(); emit(OffersState( areaGeohash: geohashPrefix, searching: true, @@ -99,6 +101,14 @@ class OffersCubit extends Cubit { onError: (Object e) => emit(state.copyWith(searching: false, error: () => '$e')), ); + // The discover stream stays open for live offers and never signals "done", + // so stop the spinner after a beat: no results → show the empty state, not + // an endless "searching". + _searchTimeout = Timer(const Duration(seconds: 6), () { + if (!isClosed && state.searching) { + emit(state.copyWith(searching: false)); + } + }); } /// Publishes [offer]; returns the transport's verdict. No-op result when @@ -157,6 +167,7 @@ class OffersCubit extends Cubit { @override Future close() async { + _searchTimeout?.cancel(); await _sub?.cancel(); await _onDispose?.call(); return super.close(); diff --git a/apps/app_seeds/lib/ui/chat_screen.dart b/apps/app_seeds/lib/ui/chat_screen.dart index a618656..97a555c 100644 --- a/apps/app_seeds/lib/ui/chat_screen.dart +++ b/apps/app_seeds/lib/ui/chat_screen.dart @@ -1,7 +1,9 @@ import 'dart:async'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:url_launcher/url_launcher.dart'; import '../i18n/strings.g.dart'; import '../services/message_store.dart'; @@ -45,6 +47,7 @@ class _ChatScreenState extends State { TrustCubit? _trust; bool _loading = true; String? _peerName; + String? _peerG1; final _input = TextEditingController(); @override @@ -94,9 +97,15 @@ class _ChatScreenState extends State { unawaited(() async { try { final profile = await live.profile.fetch(widget.peerPubkey); - if (profile != null && profile.name.isNotEmpty && mounted) { - await widget.profileCache!.setName(widget.peerPubkey, profile.name); - setState(() => _peerName = profile.name); + if (profile != null && mounted) { + if (profile.name.isNotEmpty) { + await widget.profileCache! + .setName(widget.peerPubkey, profile.name); + } + setState(() { + if (profile.name.isNotEmpty) _peerName = profile.name; + if (profile.g1.isNotEmpty) _peerG1 = profile.g1; + }); } } catch (_) { // best effort @@ -105,6 +114,30 @@ class _ChatScreenState extends State { } } + /// Opens the peer's Ğ1 wallet so you can pay them (Ğ1 is separate from the + /// Nostr key — this uses the address they published in their profile). Falls + /// back to copying the address if no wallet handles the link. + /// + /// NOTE: confirm the deep-link scheme for the target wallet (Ğ1nkgo / Ğecko / + /// Cesium) — this uses the Cesium web wallet as a universal fallback. + Future _payG1() async { + final g1 = _peerG1; + if (g1 == null) return; + final messenger = ScaffoldMessenger.of(context); + final t = context.t; + final uri = Uri.parse('https://demo.cesium.app/#/app/wallet/$g1/'); + var launched = false; + try { + launched = await launchUrl(uri, mode: LaunchMode.externalApplication); + } catch (_) { + launched = false; + } + if (!launched && mounted) { + await Clipboard.setData(ClipboardData(text: g1)); + messenger.showSnackBar(SnackBar(content: Text(t.chat.g1Copied))); + } + } + Future _send() async { final cubit = _messages; if (cubit == null || _input.text.trim().isEmpty) return; @@ -126,9 +159,19 @@ class _ChatScreenState extends State { Widget build(BuildContext context) { final messages = _messages; final trust = _trust; + final t = context.t; return Scaffold( appBar: AppBar( title: Text(_peerName ?? shortPubkey(widget.peerPubkey)), + actions: [ + if (_peerG1 != null) + IconButton( + key: const Key('chat.payG1'), + icon: const Icon(Icons.payments_outlined), + tooltip: t.chat.payG1, + onPressed: _payG1, + ), + ], ), body: _loading || messages == null || trust == null ? const Center(child: CircularProgressIndicator()) diff --git a/apps/app_seeds/lib/ui/market_screen.dart b/apps/app_seeds/lib/ui/market_screen.dart index 9f418a7..6784d7c 100644 --- a/apps/app_seeds/lib/ui/market_screen.dart +++ b/apps/app_seeds/lib/ui/market_screen.dart @@ -42,6 +42,7 @@ class MarketScreen extends StatefulWidget { class _MarketScreenState extends State { OffersCubit? _cubit; bool _loading = true; + bool _hasArea = false; @override void initState() { @@ -64,6 +65,7 @@ class _MarketScreenState extends State { final previous = _cubit; setState(() { _cubit = cubit; + _hasArea = area.isNotEmpty; _loading = false; }); await previous?.close(); @@ -171,6 +173,8 @@ class _MarketScreenState extends State { value: cubit, child: MarketBody( onConfigure: _openConfig, + onRetry: _init, + hasArea: _hasArea, selfPubkey: widget.social.publicKeyHex, ), ), @@ -181,12 +185,20 @@ class _MarketScreenState extends State { class MarketBody extends StatelessWidget { const MarketBody({ required this.onConfigure, + this.onRetry, + this.hasArea = true, this.selfPubkey, super.key, }); final VoidCallback onConfigure; + /// Re-opens the connection (for the "can't reach servers" retry). + final VoidCallback? onRetry; + + /// Whether the user has set their coarse area yet. + final bool hasArea; + /// This user's own pubkey, so we don't offer to message our own listings. final String? selfPubkey; @@ -196,10 +208,21 @@ class MarketBody extends StatelessWidget { return BlocBuilder( builder: (context, state) { if (!context.read().isOnline) { + // Default community servers exist, so "offline" means unreachable — + // offer a retry rather than "set up sharing". return _EmptyState( icon: Icons.wifi_tethering_off_outlined, - title: t.market.notSetUp, - body: t.market.notSetUpBody, + title: t.market.cantReach, + body: t.market.cantReachBody, + actionLabel: onRetry != null ? t.market.retry : null, + onAction: onRetry, + ); + } + if (!hasArea) { + return _EmptyState( + icon: Icons.place_outlined, + title: t.market.setArea, + body: t.market.setAreaBody, actionLabel: t.market.setUp, onAction: onConfigure, ); @@ -431,6 +454,7 @@ class _ConfigSheet extends StatefulWidget { class _ConfigSheetState extends State<_ConfigSheet> { final _area = TextEditingController(); + final _servers = TextEditingController(); bool _loading = true; bool _locationBusy = false; String? _locationError; @@ -443,13 +467,13 @@ class _ConfigSheetState extends State<_ConfigSheet> { Future _load() async { _area.text = await widget.settings.areaGeohash(); + _servers.text = (await widget.settings.relayUrls()).join('\n'); if (mounted) setState(() => _loading = false); } Future _save() async { - // Relays are managed automatically (default community servers, dead ones - // skipped by the pool) — the user only sets their area here. await widget.settings.setAreaGeohash(_area.text); + await widget.settings.setRelayUrls(_servers.text.split('\n')); if (mounted) Navigator.of(context).pop(true); } @@ -477,12 +501,14 @@ class _ConfigSheetState extends State<_ConfigSheet> { @override void dispose() { _area.dispose(); + _servers.dispose(); super.dispose(); } @override Widget build(BuildContext context) { final t = context.t; + final hasArea = _area.text.trim().isNotEmpty; return Padding( padding: EdgeInsets.only( left: 20, @@ -491,40 +517,34 @@ class _ConfigSheetState extends State<_ConfigSheet> { bottom: MediaQuery.of(context).viewInsets.bottom + 20, ), child: _loading - ? const SizedBox(height: 120, child: Center(child: CircularProgressIndicator())) + ? const SizedBox( + height: 120, child: Center(child: CircularProgressIndicator())) : SingleChildScrollView( child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Text( - t.market.configTitle, - style: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.w600, - color: seedTitle, + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text( + t.market.configTitle, + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.w600, + color: seedTitle, + ), ), - ), - const SizedBox(height: 8), - Text( - t.market.setupIntro, - style: const TextStyle(color: seedMuted, fontSize: 13, height: 1.4), - ), - const SizedBox(height: 16), - TextField( - key: const Key('market.area'), - controller: _area, - decoration: InputDecoration( - labelText: t.market.areaLabel, - helperText: t.market.areaHelp, - helperMaxLines: 3, + const SizedBox(height: 8), + Text( + t.market.setupIntro, + style: const TextStyle( + color: seedMuted, fontSize: 13, height: 1.4), ), - ), - if (widget.location != null) - Align( - alignment: AlignmentDirectional.centerStart, - child: TextButton.icon( + const SizedBox(height: 18), + // Setting your area from device location is the human path; + // typing a code is the advanced fallback. + if (widget.location != null) + FilledButton.tonalIcon( key: const Key('market.useLocation'), + onPressed: _locationBusy ? null : _useLocation, icon: _locationBusy ? const SizedBox( width: 16, @@ -533,26 +553,85 @@ class _ConfigSheetState extends State<_ConfigSheet> { ) : const Icon(Icons.my_location, size: 18), label: Text(t.market.useLocation), - onPressed: _locationBusy ? null : _useLocation, + ), + if (_locationError != null) + Padding( + padding: const EdgeInsets.only(top: 6), + child: Text( + _locationError!, + style: const TextStyle( + color: Color(0xFFB3261E), fontSize: 12), + ), + ), + const SizedBox(height: 12), + Row( + children: [ + Icon( + hasArea + ? Icons.check_circle + : Icons.pending_outlined, + size: 18, + color: hasArea ? seedGreen : seedMuted, + ), + const SizedBox(width: 8), + Expanded( + child: Text( + hasArea ? t.market.areaSet : t.market.areaNotSet, + style: TextStyle( + color: hasArea ? seedOnSurface : seedMuted, + fontSize: 13, + ), + ), + ), + ], + ), + const SizedBox(height: 8), + Theme( + data: Theme.of(context) + .copyWith(dividerColor: Colors.transparent), + child: ExpansionTile( + key: const Key('market.advanced'), + tilePadding: EdgeInsets.zero, + childrenPadding: const EdgeInsets.only(bottom: 8), + title: Text( + t.market.advanced, + style: const TextStyle(fontSize: 13, color: seedMuted), + ), + children: [ + TextField( + key: const Key('market.area'), + controller: _area, + onChanged: (_) => setState(() {}), + decoration: InputDecoration( + labelText: t.market.areaCodeLabel, + helperText: t.market.areaCodeHint, + helperMaxLines: 2, + ), + ), + const SizedBox(height: 14), + TextField( + key: const Key('market.servers'), + controller: _servers, + minLines: 1, + maxLines: 3, + decoration: InputDecoration( + labelText: t.market.serversLabel, + helperText: t.market.serversHelp, + helperMaxLines: 2, + ), + ), + ], ), ), - if (_locationError != null) - Padding( - padding: const EdgeInsets.only(top: 4), - child: Text( - _locationError!, - style: const TextStyle(color: Color(0xFFB3261E), fontSize: 12), - ), + const SizedBox(height: 20), + FilledButton( + key: const Key('market.save'), + onPressed: _save, + child: Text(t.market.save), ), - const SizedBox(height: 20), - FilledButton( - key: const Key('market.save'), - onPressed: _save, - child: Text(t.market.save), - ), - ], + ], + ), ), - ), ); } } diff --git a/apps/app_seeds/lib/ui/profile_screen.dart b/apps/app_seeds/lib/ui/profile_screen.dart index 56f0141..815671d 100644 --- a/apps/app_seeds/lib/ui/profile_screen.dart +++ b/apps/app_seeds/lib/ui/profile_screen.dart @@ -30,6 +30,7 @@ class ProfileScreen extends StatefulWidget { class _ProfileScreenState extends State { final _name = TextEditingController(); final _about = TextEditingController(); + final _g1 = TextEditingController(); bool _loading = true; bool _saving = false; @@ -42,6 +43,7 @@ class _ProfileScreenState extends State { Future _load() async { _name.text = await widget.profileStore.name(); _about.text = await widget.profileStore.about(); + _g1.text = await widget.profileStore.g1(); if (mounted) setState(() => _loading = false); } @@ -57,7 +59,8 @@ class _ProfileScreenState extends State { final messenger = ScaffoldMessenger.of(context); final t = context.t; setState(() => _saving = true); - await widget.profileStore.save(name: _name.text, about: _about.text); + await widget.profileStore + .save(name: _name.text, about: _about.text, g1: _g1.text); // Best-effort publish to the network so peers see the name. final relays = await widget.settings.relayUrls(); @@ -67,6 +70,7 @@ class _ProfileScreenState extends State { await session.profile.publish( name: _name.text.trim(), about: _about.text.trim(), + g1: _g1.text.trim(), ); await session.close(); } catch (_) { @@ -82,6 +86,7 @@ class _ProfileScreenState extends State { void dispose() { _name.dispose(); _about.dispose(); + _g1.dispose(); super.dispose(); } @@ -116,6 +121,16 @@ class _ProfileScreenState extends State { helperText: t.profile.aboutHint, ), ), + const SizedBox(height: 16), + TextField( + key: const Key('profile.g1'), + controller: _g1, + decoration: InputDecoration( + labelText: t.profile.g1, + helperText: t.profile.g1Hint, + helperMaxLines: 2, + ), + ), const SizedBox(height: 24), FilledButton( key: const Key('profile.save'), diff --git a/apps/app_seeds/test/ui/market_screen_test.dart b/apps/app_seeds/test/ui/market_screen_test.dart index e6a578d..03824d7 100644 --- a/apps/app_seeds/test/ui/market_screen_test.dart +++ b/apps/app_seeds/test/ui/market_screen_test.dart @@ -66,7 +66,7 @@ Widget _wrapMarket(SocialService social, SocialSettings settings, } void main() { - testWidgets('offline (no transport) shows the "set up sharing" prompt', + testWidgets('offline (no transport) shows the "can\'t reach servers" state', (tester) async { final cubit = OffersCubit(null); addTearDown(cubit.close); @@ -74,8 +74,8 @@ void main() { await tester.pumpWidget(_wrapBody(cubit)); await tester.pumpAndSettle(); - expect(find.text("Sharing isn't set up yet"), findsOneWidget); - expect(find.text('Set up sharing'), findsOneWidget); + expect(find.textContaining("Can't reach the community servers"), + findsOneWidget); }); testWidgets('MarketScreen with no relays configured degrades to offline', @@ -88,7 +88,7 @@ void main() { await tester.pumpAndSettle(); expect(find.text('Seeds near you'), findsOneWidget); // app bar title - expect(find.text('Set up sharing'), findsOneWidget); // offline prompt + expect(find.text('Retry'), findsOneWidget); // can't-reach state offers retry }); testWidgets('"use my location" fills the area with a coarse geohash', @@ -107,8 +107,7 @@ void main() { await tester.tap(find.byKey(const Key('market.useLocation'))); await tester.pumpAndSettle(); - final area = tester.widget(find.byKey(const Key('market.area'))); - expect(area.controller!.text, hasLength(5)); - expect(area.controller!.text, matches(RegExp(r'^[0-9a-z]+$'))); + // The area status flips to "set" once a coarse location is captured. + expect(find.textContaining('Your area is set'), findsOneWidget); }); } diff --git a/packages/commons_core/lib/src/social/nostr/nostr_profile_transport.dart b/packages/commons_core/lib/src/social/nostr/nostr_profile_transport.dart index 62abd79..c2e5e69 100644 --- a/packages/commons_core/lib/src/social/nostr/nostr_profile_transport.dart +++ b/packages/commons_core/lib/src/social/nostr/nostr_profile_transport.dart @@ -19,11 +19,13 @@ class NostrProfileTransport implements ProfileTransport { String name = '', String about = '', String picture = '', + String g1 = '', }) async { final content = jsonEncode({ if (name.isNotEmpty) 'name': name, if (about.isNotEmpty) 'about': about, if (picture.isNotEmpty) 'picture': picture, + if (g1.isNotEmpty) 'g1': g1, }); final event = Event.from( kind: kindMetadata, @@ -50,6 +52,7 @@ class NostrProfileTransport implements ProfileTransport { name: (map['name'] as String?) ?? '', about: (map['about'] as String?) ?? '', picture: (map['picture'] as String?) ?? '', + g1: (map['g1'] as String?) ?? '', ); } catch (_) { return UserProfile(pubkeyHex: pubkeyHex); diff --git a/packages/commons_core/lib/src/social/profile_transport.dart b/packages/commons_core/lib/src/social/profile_transport.dart index 961ad54..3cc9617 100644 --- a/packages/commons_core/lib/src/social/profile_transport.dart +++ b/packages/commons_core/lib/src/social/profile_transport.dart @@ -6,6 +6,7 @@ class UserProfile { this.name = '', this.about = '', this.picture = '', + this.g1 = '', }); final String pubkeyHex; @@ -13,7 +14,12 @@ class UserProfile { final String about; final String picture; - bool get isEmpty => name.isEmpty && about.isEmpty && picture.isEmpty; + /// Optional Ğ1 (Duniter) address the person chose to publish, so peers can pay + /// them in Ğ1. Separate from the Nostr key; empty when not shared. + final String g1; + + bool get isEmpty => + name.isEmpty && about.isEmpty && picture.isEmpty && g1.isEmpty; } /// Publish/fetch public profile metadata. Kind:0 is replaceable — publishing @@ -21,7 +27,7 @@ class UserProfile { /// sibling of the offer/message/trust transports over the same connection. abstract interface class ProfileTransport { /// Publishes (replaces) this identity's profile. - Future publish({String name, String about, String picture}); + Future publish({String name, String about, String picture, String g1}); /// Fetches [pubkeyHex]'s latest profile, or null if they never published one. Future fetch(String pubkeyHex); diff --git a/packages/commons_core/test/social/nostr_profile_test.dart b/packages/commons_core/test/social/nostr_profile_test.dart index 49c95ec..9b40ccf 100644 --- a/packages/commons_core/test/social/nostr_profile_test.dart +++ b/packages/commons_core/test/social/nostr_profile_test.dart @@ -16,8 +16,11 @@ void main() { test('publish then fetch round-trips a profile (kind:0)', () async { final alice = await idFor(1); final aliceConn = await NostrConnection.connect(relay.url, identity: alice); - await NostrProfileTransport(aliceConn) - .publish(name: 'Alicia', about: 'Guardo tomate rosa'); + await NostrProfileTransport(aliceConn).publish( + name: 'Alicia', + about: 'Guardo tomate rosa', + g1: 'G1abc123', + ); // Anyone else can fetch it. final bob = await idFor(2); @@ -28,6 +31,7 @@ void main() { expect(fetched, isNotNull); expect(fetched!.name, 'Alicia'); expect(fetched.about, 'Guardo tomate rosa'); + expect(fetched.g1, 'G1abc123'); await aliceConn.close(); await bobConn.close();