From df0b5fcd19f3664cb329c96ed7e0940681e20ead Mon Sep 17 00:00:00 2001 From: Rif'at Ahdi R <10791791+atrifat@users.noreply.github.com> Date: Tue, 5 Sep 2023 10:03:35 +0800 Subject: [PATCH 01/21] Add arm64-v8a and armeabi-v7a build target --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 8fc23db2b..6df3bf15f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -54,7 +54,7 @@ android { abi { enable true reset() - include "x86", "x86_64" + include "x86", "x86_64", "arm64-v8a", "armeabi-v7a" universalApk true } } From fc9500b45cb290bbb56b258b3926cec255a39486 Mon Sep 17 00:00:00 2001 From: Rif'at Ahdi R <10791791+atrifat@users.noreply.github.com> Date: Tue, 5 Sep 2023 10:39:36 +0800 Subject: [PATCH 02/21] Update create-release.yml to suppport arm64-v8a and armeabi-v7a --- .github/workflows/create-release.yml | 44 ++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/.github/workflows/create-release.yml b/.github/workflows/create-release.yml index c1346cc53..9e2770411 100644 --- a/.github/workflows/create-release.yml +++ b/.github/workflows/create-release.yml @@ -112,6 +112,28 @@ jobs: asset_name: amethyst-googleplay-x86_64-${{ github.ref_name }}.apk asset_content_type: application/zip + - name: Upload Play APK arm64-v8a Asset + id: upload-release-asset-play-arm64-v8a-apk + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: app/build/outputs/apk/play/release/app-play-arm64-v8a-release-unsigned-signed.apk + asset_name: amethyst-googleplay-arm64-v8a-${{ github.ref_name }}.apk + asset_content_type: application/zip + + - name: Upload Play APK armeabi-v7a Asset + id: upload-release-asset-play-armeabi-v7a-apk + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: app/build/outputs/apk/play/release/app-play-armeabi-v7a-release-unsigned-signed.apk + asset_name: amethyst-googleplay-armeabi-v7a-${{ github.ref_name }}.apk + asset_content_type: application/zip + # F-Droid APK - name: Upload F-Droid APK Universal Asset id: upload-release-asset-fdroid-universal-apk @@ -146,6 +168,28 @@ jobs: asset_name: amethyst-fdroid-x86_64-${{ github.ref_name }}.apk asset_content_type: application/zip + - name: Upload F-Droid APK arm64-v8a Asset + id: upload-release-asset-fdroid-arm64-v8a-apk + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: app/build/outputs/apk/fdroid/release/app-fdroid-arm64-v8a-release-unsigned-signed.apk + asset_name: amethyst-fdroid-arm64-v8a-${{ github.ref_name }}.apk + asset_content_type: application/zip + + - name: Upload F-Droid APK armeabi-v7a Asset + id: upload-release-asset-fdroid-armeabi-v7a-apk + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: app/build/outputs/apk/fdroid/release/app-fdroid-armeabi-v7a-release-unsigned-signed.apk + asset_name: amethyst-fdroid-armeabi-v7a-${{ github.ref_name }}.apk + asset_content_type: application/zip + # Google Play AAB From d3468f3eba769696d9246b38953366e6b5f4a695 Mon Sep 17 00:00:00 2001 From: David Kaspar Date: Tue, 5 Sep 2023 12:52:59 +0200 Subject: [PATCH 03/21] Added new translations for cs/de --- app/src/main/res/values-cs/strings.xml | 48 ++++++++++++++++++++- app/src/main/res/values-de/strings.xml | 58 ++++++++++++++++++++++++++ 2 files changed, 105 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 1afc75465..818e86869 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -12,6 +12,7 @@ Nepodařilo se dešifrovat zprávu Obrázek skupiny Explicitní obsah + Spam Zneužívání identity Nezákonné jednání Neznámý @@ -37,7 +38,8 @@ Přihlaste se pomocí soukromého klíče, abyste mohli zrušit sledování Zapy Počet zobrazení - boostováno + Zvýšení + zvýšeno Citovat Nová částka v sats Přidat @@ -168,6 +170,9 @@ Nostr Adresa nikdy nyní + h + m + d Nahota Neslušnost / Nenávistná řeč Nahlásit nenávistnou řeč @@ -244,6 +249,7 @@ Přidat do veřejných záložek Odebrat ze soukromých záložek Odebrat z veřejných záložek + Služba Wallet Connect Autorizuje Nostr Secret k provedení platby zapsům, aniž byste opustili aplikaci. Uchovávejte tajemství v bezpečí a pokud je to možné, používejte soukromý přepojovač. Veřejný klíč Wallet Connect @@ -251,6 +257,7 @@ Tajemství Wallet Connect Zobrazit tajný klíč nsec / hex soukromý klíč + Částka záruky v sats Zveřejnit hlasování Povinná pole: @@ -262,6 +269,7 @@ Minimální zaps Maximální zaps Konsensus + (0–100)% Uzavřít po dnech Hlasování je uzavřeno pro nové hlasy @@ -282,15 +290,22 @@ Přidat do zprávy Popis obsahu Modrá loď na bílé písčité pláži při západu slunce + Typ zapsu Typ zapsu pro všechny možnosti + Veřejný Všichni mohou vidět transakci a zprávu + Soukromý Odesílatel a příjemce si mohou navzájem vidět a číst zprávu + Anonymní Příjemce a veřejnost neví, kdo platbu poslal + + Né Zap Žádná stopa v Nostr, pouze v Lightning + Souborový server LnAddress nebo @Uživatel imgur.com – důvěryhodný @@ -324,6 +339,7 @@ Seznam sledovaných Všechna sledování Globální + ## Připojte se přes Tor s Orbotem \n\n1. Nainstalujte [Orbot](https://play.google.com/store/apps/details?id=org.torproject.android) \n2. Spusťte Orbot @@ -332,15 +348,20 @@ \n5. Nastavte port Socks v této obrazovce \n6. Stiskněte tlačítko Aktivovat Tor + Orbot Socks Port Neplatné číslo portu Použít Orbot Odpojit Tor/Orbot + Soukromé zprávy Upozorní vás, když dorazí soukromá zpráva + Přijaté zapsy Upozorní vás, když vás někdo zapsne + %1$s sats Od %1$s pro %1$s + Upozornit: Připojit se ke konverzaci ID uživatele nebo skupiny @@ -358,14 +379,18 @@ Varovat, když příspěvky obsahují hlášení od vašich sledovaných osob Nový symbol reakce Nebyly vybrány žádné typy reakcí. Dlouhým stiskem změňte + + Zap-sběr Přidá cílovou částku sats pro tento příspěvek. Podporované klienty ji mohou zobrazovat jako ukazatel postupu k podněcování darování Cílová částka v sats + Zapraiser na %1$s. Do cíle zbývá %2$s sats Číst z Relay Zapisovat do Relay Při pokusu o získání informací z Relay se vyskytla chyba z %1$s Vlastník Verze + Software Kontakt Podporované NIPs Vstupné @@ -385,21 +410,27 @@ Minimální PoW Ověření Platba + Cashu Token Vyměnit Není nastavena žádná Lightning adresa Token zkopírován do schránky + ŽIVĚ + OFFLINE UKONČENO PLÁNOVÁNO + Živé vysílání je offline Živé vysílání ukončeno Odhlášení vymaže všechny vaše místní informace. Ujistěte se, že máte zálohované své privátní klíče, abyste se vyhnuli ztrátě účtu. Chcete pokračovat? Sledované značky Rele + Živě Komunita Chaty Schválené příspěvky + Tato skupina nemá popis ani pravidla. Promluvte si s majitelem, aby je přidal/a. Tato komunita nemá popis. Promluvte si s majitelem, aby ho přidal/a. Citlivý obsah @@ -451,4 +482,19 @@ Automaticky přehrávat videa a GIFy Zobrazit náhledy URL Kdy načíst obrázek + + Kopírovat URL do schránky + Kopírovat ID poznámky do schránky + + Vytvořeno + Pravidla + + Aktualizovat svůj stav + + Chyba při zpracování chybové zprávy + Hlasy jsou váženy podle hodnoty zapu. Můžete nastavit minimální částku, abyste se vyhnuli spammerům, a maximální částku, abyste zabránili velkým zapperům, kteří by mohli ovládnout hlasování. Použijte stejnou částku v obou polích, aby byla hodnota každého hlasu stejná. Nechte prázdné pro přijetí libovolné částky. + + Nelze odeslat zap + Poslat zprávu uživateli + Ok diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 8c77819a1..3b1f7282d 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -12,6 +12,7 @@ Nachricht konnte nicht entschlüsselt werden Gruppenbild Expliziter Inhalt + Spam Vortäuschung Illegales Verhalten Unbekannt @@ -35,7 +36,9 @@ Melden Sie sich mit einem privaten Schlüssel an, um Zaps senden zu können Melden Sie sich mit einem privaten Schlüssel an, um zu folgen Melden Sie sich mit einem privaten Schlüssel an, um nicht mehr zu folgen + Zaps Aufrufe + Verstärken Verstärkt Zitat Neuer Betrag in Sats @@ -75,6 +78,7 @@ Fehler beim Hochladen des Bildes Relay-Adresse Beiträge + Bytes Fehler Startseite Private Nachrichten @@ -161,6 +165,7 @@ anz der Bedingungen ist erforderlich Öffentlicher Chat empfangene Beiträge Entfernen + Auto übersetzt von zu Zuerst in %1$s anzeigen @@ -170,6 +175,9 @@ anz der Bedingungen ist erforderlich nie jetzt Nacktheit + s + m + t Beleidigungen / Hassrede Hassrede melden Nacktheit / Pornografie melden @@ -246,20 +254,28 @@ anz der Bedingungen ist erforderlich Zu den öffentlichen Lesezeichen hinzufügen Aus den privaten Lesezeichen entfernen Aus den öffentlichen Lesezeichen entfernen + Autorisiert einen Nostr Secret, Zaps ohne Verlassen der App zu bezahlen. Bewahren Sie den geheimen Schlüssel sicher auf und verwenden Sie nach Möglichkeit ein privates Relay Wallet Connect Public Key Geheimen Schlüssel anzeigen nsec / hexadezimaler privater Schlüssel + Wallet-Verbindungsdienst + Wallet-Verbindungs-Relay + Wallet-Verbindungs-Geheimnis + + Spendenbetrag in Sats Umfrage veröffentlichen Erforderliche Felder: Zap-Empfänger Hauptbeschreibung der Umfrage… + Option %s Beschreibung der Umfrageoption Optionale Felder: Mindest-Zap-Betrag Maximaler Zap-Betrag Konsens + (0–100)% Schließen nach Tagen Umfrage ist für neue Stimmen geschlossen @@ -280,15 +296,22 @@ anz der Bedingungen ist erforderlich Zur Nachricht hinzufügen Beschreibung des Inhalts Ein blaues Boot an einem weißen Sandstrand bei Sonnenuntergang + Zap-Typ Zap-Typ für alle Optionen + Öffentlich Jeder kann die Transaktion und die Nachricht sehen + Privat Absender und Empfänger können einander sehen und die Nachricht lesen + Anonym Empfänger und die Öffentlichkeit wissen nicht, wer die Zahlung gesendet hat + + Keine Zap Keine Spur in Nostr, nur in Lightning + Dateiserver LnAddress oder @Benutzer imgur.com - vertrauenswürdig @@ -319,8 +342,10 @@ anz der Bedingungen ist erforderlich Deine Daten werden sofort im regulären Netzwerk übertragen Ja Nein + Folgen-Liste Alle Folgen + Weltweit ## Über Tor mit Orbot verbinden \n\n1. Installiere [Orbot](https://play.google.com/store/apps/details?id=org.torproject.android) \n2. Starte Orbot @@ -332,6 +357,7 @@ anz der Bedingungen ist erforderlich Ungültige Portnummer Orbot verwenden Tor/Orbot trennen + Private Nachrichten Benachrichtigt dich, wenn eine private Nachricht eingeht Erhaltene Zaps @@ -356,13 +382,18 @@ anz der Bedingungen ist erforderlich Warnung bei Meldungen von deinen Abonnements Neues Reaktionssymbol Keine Reaktionstypen ausgewählt. Lange drücken, um zu ändern + + Zap-Sammlung Fügt einen Zielbetrag in Sats für diesen Beitrag hinzu. Unterstützende Clients können dies als Fortschrittsbalken anzeigen, um Spenden zu fördern Zielbetrag in Sats + Zapraiser bei %1$s. %2$s Sats bis zum Ziel Von Relay lesen In Relay schreiben Ein Fehler ist beim Abrufen von Relay-Informationen von %1$s aufgetreten Inhaber + Version + Software Kontakt Unterstützte NIPs Eintrittsgebühren @@ -370,6 +401,7 @@ anz der Bedingungen ist erforderlich Einschränkungen Länder Sprachen + Tags Veröffentlichungsrichtlinie Nachrichtenlänge Abonnements @@ -385,14 +417,23 @@ anz der Bedingungen ist erforderlich Einlösen Keine Lightning-Adresse festgelegt Token in die Zwischenablage kopiert + + LIVE + OFFLINE BEENDET GEPLANT + Livestream ist offline Livestream beendet Das Abmelden löscht alle Ihre lokalen Informationen. Stellen Sie sicher, dass Sie Ihre privaten Schlüssel gesichert haben, um einen Kontoverlust zu vermeiden. Möchten Sie fortfahren? Gefolgte Tags Relais + + Live + Community + Chats Genehmigte Beiträge + Diese Gruppe hat keine Beschreibung oder Regeln. Sprechen Sie mit dem Eigentümer, um eine hinzuzufügen. Diese Community hat keine Beschreibung. Sprechen Sie mit dem Eigentümer, um eine hinzuzufügen. Sensibler Inhalt @@ -401,6 +442,8 @@ anz der Bedingungen ist erforderlich Immer Nur WLAN Nie + + System Hell Dunkel Anwendungseinstellungen @@ -443,4 +486,19 @@ anz der Bedingungen ist erforderlich Videos und GIFs automatisch abspielen URL-Vorschauen anzeigen Wann Bilder geladen werden sollen + + URL in die Zwischenablage kopieren + Notiz-ID in die Zwischenablage kopieren + + Erstellt am + Regeln + + Status aktualisieren + + Fehler beim Verarbeiten der Fehlermeldung + Die Abstimmungen werden nach der Höhe des Zaps gewichtet. Sie können einen Mindestbetrag festlegen, um Spam zu verhindern, und einen Höchstbetrag, um zu verhindern, dass große Zapper die Abstimmung dominieren. Verwenden Sie denselben Betrag in beiden Feldern, um sicherzustellen, dass jeder Stimme der gleiche Wert zukommt. Lassen Sie es leer, um jeden Betrag zu akzeptieren. + + Zap konnte nicht gesendet werden + Mit dem Benutzer kommunizieren + Ok From 0234cfca3037aec0ac2e525c35e9b5f70f5b4d1e Mon Sep 17 00:00:00 2001 From: Crowdin Bot Date: Tue, 5 Sep 2023 13:03:08 +0000 Subject: [PATCH 04/21] New Crowdin translations by GitHub Action --- app/src/main/res/values-cs/strings.xml | 34 -------- app/src/main/res/values-de/strings.xml | 48 +---------- app/src/main/res/values-it-rIT/strings.xml | 98 ++++++++++++++++++++++ 3 files changed, 102 insertions(+), 78 deletions(-) diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 818e86869..cbfbb9564 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -12,7 +12,6 @@ Nepodařilo se dešifrovat zprávu Obrázek skupiny Explicitní obsah - Spam Zneužívání identity Nezákonné jednání Neznámý @@ -170,9 +169,6 @@ Nostr Adresa nikdy nyní - h - m - d Nahota Neslušnost / Nenávistná řeč Nahlásit nenávistnou řeč @@ -249,7 +245,6 @@ Přidat do veřejných záložek Odebrat ze soukromých záložek Odebrat z veřejných záložek - Služba Wallet Connect Autorizuje Nostr Secret k provedení platby zapsům, aniž byste opustili aplikaci. Uchovávejte tajemství v bezpečí a pokud je to možné, používejte soukromý přepojovač. Veřejný klíč Wallet Connect @@ -257,7 +252,6 @@ Tajemství Wallet Connect Zobrazit tajný klíč nsec / hex soukromý klíč - Částka záruky v sats Zveřejnit hlasování Povinná pole: @@ -269,7 +263,6 @@ Minimální zaps Maximální zaps Konsensus - (0–100)% Uzavřít po dnech Hlasování je uzavřeno pro nové hlasy @@ -290,22 +283,16 @@ Přidat do zprávy Popis obsahu Modrá loď na bílé písčité pláži při západu slunce - Typ zapsu Typ zapsu pro všechny možnosti - Veřejný Všichni mohou vidět transakci a zprávu - Soukromý Odesílatel a příjemce si mohou navzájem vidět a číst zprávu - Anonymní Příjemce a veřejnost neví, kdo platbu poslal - Né Zap Žádná stopa v Nostr, pouze v Lightning - Souborový server LnAddress nebo @Uživatel imgur.com – důvěryhodný @@ -339,7 +326,6 @@ Seznam sledovaných Všechna sledování Globální - ## Připojte se přes Tor s Orbotem \n\n1. Nainstalujte [Orbot](https://play.google.com/store/apps/details?id=org.torproject.android) \n2. Spusťte Orbot @@ -348,20 +334,15 @@ \n5. Nastavte port Socks v této obrazovce \n6. Stiskněte tlačítko Aktivovat Tor - Orbot Socks Port Neplatné číslo portu Použít Orbot Odpojit Tor/Orbot - Soukromé zprávy Upozorní vás, když dorazí soukromá zpráva - Přijaté zapsy Upozorní vás, když vás někdo zapsne - %1$s sats Od %1$s pro %1$s - Upozornit: Připojit se ke konverzaci ID uživatele nebo skupiny @@ -379,18 +360,15 @@ Varovat, když příspěvky obsahují hlášení od vašich sledovaných osob Nový symbol reakce Nebyly vybrány žádné typy reakcí. Dlouhým stiskem změňte - Zap-sběr Přidá cílovou částku sats pro tento příspěvek. Podporované klienty ji mohou zobrazovat jako ukazatel postupu k podněcování darování Cílová částka v sats - Zapraiser na %1$s. Do cíle zbývá %2$s sats Číst z Relay Zapisovat do Relay Při pokusu o získání informací z Relay se vyskytla chyba z %1$s Vlastník Verze - Software Kontakt Podporované NIPs Vstupné @@ -410,27 +388,21 @@ Minimální PoW Ověření Platba - Cashu Token Vyměnit Není nastavena žádná Lightning adresa Token zkopírován do schránky - ŽIVĚ - OFFLINE UKONČENO PLÁNOVÁNO - Živé vysílání je offline Živé vysílání ukončeno Odhlášení vymaže všechny vaše místní informace. Ujistěte se, že máte zálohované své privátní klíče, abyste se vyhnuli ztrátě účtu. Chcete pokračovat? Sledované značky Rele - Živě Komunita Chaty Schválené příspěvky - Tato skupina nemá popis ani pravidla. Promluvte si s majitelem, aby je přidal/a. Tato komunita nemá popis. Promluvte si s majitelem, aby ho přidal/a. Citlivý obsah @@ -482,19 +454,13 @@ Automaticky přehrávat videa a GIFy Zobrazit náhledy URL Kdy načíst obrázek - Kopírovat URL do schránky Kopírovat ID poznámky do schránky - Vytvořeno Pravidla - Aktualizovat svůj stav - Chyba při zpracování chybové zprávy Hlasy jsou váženy podle hodnoty zapu. Můžete nastavit minimální částku, abyste se vyhnuli spammerům, a maximální částku, abyste zabránili velkým zapperům, kteří by mohli ovládnout hlasování. Použijte stejnou částku v obou polích, aby byla hodnota každého hlasu stejná. Nechte prázdné pro přijetí libovolné částky. - Nelze odeslat zap Poslat zprávu uživateli - Ok diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 3b1f7282d..91e737c26 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -12,7 +12,6 @@ Nachricht konnte nicht entschlüsselt werden Gruppenbild Expliziter Inhalt - Spam Vortäuschung Illegales Verhalten Unbekannt @@ -36,7 +35,6 @@ Melden Sie sich mit einem privaten Schlüssel an, um Zaps senden zu können Melden Sie sich mit einem privaten Schlüssel an, um zu folgen Melden Sie sich mit einem privaten Schlüssel an, um nicht mehr zu folgen - Zaps Aufrufe Verstärken Verstärkt @@ -78,7 +76,6 @@ Fehler beim Hochladen des Bildes Relay-Adresse Beiträge - Bytes Fehler Startseite Private Nachrichten @@ -165,7 +162,6 @@ anz der Bedingungen ist erforderlich Öffentlicher Chat empfangene Beiträge Entfernen - Auto übersetzt von zu Zuerst in %1$s anzeigen @@ -174,10 +170,9 @@ anz der Bedingungen ist erforderlich Nostr-Adresse nie jetzt - Nacktheit s - m t + Nacktheit Beleidigungen / Hassrede Hassrede melden Nacktheit / Pornografie melden @@ -254,28 +249,23 @@ anz der Bedingungen ist erforderlich Zu den öffentlichen Lesezeichen hinzufügen Aus den privaten Lesezeichen entfernen Aus den öffentlichen Lesezeichen entfernen - + Wallet-Verbindungsdienst Autorisiert einen Nostr Secret, Zaps ohne Verlassen der App zu bezahlen. Bewahren Sie den geheimen Schlüssel sicher auf und verwenden Sie nach Möglichkeit ein privates Relay Wallet Connect Public Key - Geheimen Schlüssel anzeigen - nsec / hexadezimaler privater Schlüssel - Wallet-Verbindungsdienst Wallet-Verbindungs-Relay Wallet-Verbindungs-Geheimnis - - + Geheimen Schlüssel anzeigen + nsec / hexadezimaler privater Schlüssel Spendenbetrag in Sats Umfrage veröffentlichen Erforderliche Felder: Zap-Empfänger Hauptbeschreibung der Umfrage… - Option %s Beschreibung der Umfrageoption Optionale Felder: Mindest-Zap-Betrag Maximaler Zap-Betrag Konsens - (0–100)% Schließen nach Tagen Umfrage ist für neue Stimmen geschlossen @@ -296,22 +286,16 @@ anz der Bedingungen ist erforderlich Zur Nachricht hinzufügen Beschreibung des Inhalts Ein blaues Boot an einem weißen Sandstrand bei Sonnenuntergang - Zap-Typ Zap-Typ für alle Optionen - Öffentlich Jeder kann die Transaktion und die Nachricht sehen - Privat Absender und Empfänger können einander sehen und die Nachricht lesen - Anonym Empfänger und die Öffentlichkeit wissen nicht, wer die Zahlung gesendet hat - Keine Zap Keine Spur in Nostr, nur in Lightning - Dateiserver LnAddress oder @Benutzer imgur.com - vertrauenswürdig @@ -342,7 +326,6 @@ anz der Bedingungen ist erforderlich Deine Daten werden sofort im regulären Netzwerk übertragen Ja Nein - Folgen-Liste Alle Folgen Weltweit @@ -357,7 +340,6 @@ anz der Bedingungen ist erforderlich Ungültige Portnummer Orbot verwenden Tor/Orbot trennen - Private Nachrichten Benachrichtigt dich, wenn eine private Nachricht eingeht Erhaltene Zaps @@ -382,18 +364,14 @@ anz der Bedingungen ist erforderlich Warnung bei Meldungen von deinen Abonnements Neues Reaktionssymbol Keine Reaktionstypen ausgewählt. Lange drücken, um zu ändern - Zap-Sammlung Fügt einen Zielbetrag in Sats für diesen Beitrag hinzu. Unterstützende Clients können dies als Fortschrittsbalken anzeigen, um Spenden zu fördern Zielbetrag in Sats - Zapraiser bei %1$s. %2$s Sats bis zum Ziel Von Relay lesen In Relay schreiben Ein Fehler ist beim Abrufen von Relay-Informationen von %1$s aufgetreten Inhaber - Version - Software Kontakt Unterstützte NIPs Eintrittsgebühren @@ -401,7 +379,6 @@ anz der Bedingungen ist erforderlich Einschränkungen Länder Sprachen - Tags Veröffentlichungsrichtlinie Nachrichtenlänge Abonnements @@ -417,23 +394,14 @@ anz der Bedingungen ist erforderlich Einlösen Keine Lightning-Adresse festgelegt Token in die Zwischenablage kopiert - - LIVE - OFFLINE BEENDET GEPLANT - Livestream ist offline Livestream beendet Das Abmelden löscht alle Ihre lokalen Informationen. Stellen Sie sicher, dass Sie Ihre privaten Schlüssel gesichert haben, um einen Kontoverlust zu vermeiden. Möchten Sie fortfahren? Gefolgte Tags Relais - - Live - Community - Chats Genehmigte Beiträge - Diese Gruppe hat keine Beschreibung oder Regeln. Sprechen Sie mit dem Eigentümer, um eine hinzuzufügen. Diese Community hat keine Beschreibung. Sprechen Sie mit dem Eigentümer, um eine hinzuzufügen. Sensibler Inhalt @@ -442,8 +410,6 @@ anz der Bedingungen ist erforderlich Immer Nur WLAN Nie - - System Hell Dunkel Anwendungseinstellungen @@ -486,19 +452,13 @@ anz der Bedingungen ist erforderlich Videos und GIFs automatisch abspielen URL-Vorschauen anzeigen Wann Bilder geladen werden sollen - URL in die Zwischenablage kopieren Notiz-ID in die Zwischenablage kopieren - Erstellt am Regeln - Status aktualisieren - Fehler beim Verarbeiten der Fehlermeldung Die Abstimmungen werden nach der Höhe des Zaps gewichtet. Sie können einen Mindestbetrag festlegen, um Spam zu verhindern, und einen Höchstbetrag, um zu verhindern, dass große Zapper die Abstimmung dominieren. Verwenden Sie denselben Betrag in beiden Feldern, um sicherzustellen, dass jeder Stimme der gleiche Wert zukommt. Lassen Sie es leer, um jeden Betrag zu akzeptieren. - Zap konnte nicht gesendet werden Mit dem Benutzer kommunizieren - Ok diff --git a/app/src/main/res/values-it-rIT/strings.xml b/app/src/main/res/values-it-rIT/strings.xml index ffd0a955d..fc704e4d8 100644 --- a/app/src/main/res/values-it-rIT/strings.xml +++ b/app/src/main/res/values-it-rIT/strings.xml @@ -21,6 +21,7 @@ Copia testo Copia ID autore Copia ID nota + Trasmissione Richiedi cancellazione Blocca / Segnala @@ -39,4 +40,101 @@ Visualizzazioni Boost ricondiviso + Cita + Nuovo importo in Sats + Aggiungi + "rispondendo a" + " e " + "nel canale " + Immagine copertina + Pagamento effettuato + Errore durante l\'interpretazione del messaggio d\'errore + " Seguiti" + " Seguaci" + Profilo + Filtri di sicurezza + Esci + Mostra altro + Fattura Lightning + Paga + Mance Lightning + Nota per il Ricevente + Grazie mille! + Importo in Sats + Invia Sats + "Errore nella creazione dell'anteprima di %1$s : %2$s" + "Anteprima immagine per %1$s" + Nuovo canale + Nome canale + Il mio fantastico gruppo + Url foto + Descrizione + "Su di noi... " + A cosa stai pensando? + Post + Salva + Crea + Cancella + Impossibile caricare l\'immagine + Indirizzo Relay + Posts + Bytes + Errori + Home Feed + Lista Messaggi Privati + Lista Chat Pubbliche + Feed Globale + Risultati Ricerca + Aggiungi un Relay + Nome Visualizzato + Il mio nome visualizzato + Username + L\'accettazione dei termini è obbligatoria + La chiave è obbligatoria + Accesso + Genera una nuova chiave + Caricamento del feed + "Errore durante il caricamento delle risposte: " + Prova di nuovo + Il feed è vuoto. + Aggiorna + creato + con la descrizione di + e l\'immagine + modificato il nome della chat in + descrizione per + e immagine per + Esci + Non seguire più + Canale creato + "Informazioni del canale modificate in" + Chat Pubblica + post ricevuti + Cancella + Auto + tradotto da + a + Traduci sempre in %1$s + Non tradurre mai da %1$s + Indirizzo Nostr + mai + adesso + h + m + d + Nuditá + Volgaritá / Linguaggio forte + Segnala linguaggio forte + Segnala Nuditá / Pornografia + altri + Segna tutti i conosciuti come letti + Segna tutti i nuovi come letti + Segna tutti come letti + Esegui backup delle chiavi + ## Backup delle Chiavi e Consigli di Sicurezza + \n\nIl tuo account è reso sicuro da una chiave privata. La chiave è una lunga stringa di caratteri casuali che inizia con **nsec1**. Chiunque abbia accesso alla tua chiave privata puó pubblicare contenuti con la tua identitá. + \n\n- Non devi **mai** inserire la tua chiave privata in un sito o software di cui non ti fidi. + \n- Gli sviluppatori di Amethyst non chiederanno **mai** la tua chiave privata. + \n- Mantieni **sempre** al sicuro un backup della tua chiave privata per recuperare l\'account. Ti suggeriamo di usare un password manager. + From 6a3206b938fff6afdab8b834ba65a3d05359caa8 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Tue, 5 Sep 2023 17:20:37 -0400 Subject: [PATCH 05/21] Avoids race condition when update EOSEs --- .../vitorpamplona/amethyst/service/NostrDataSource.kt | 11 ++++++++++- .../amethyst/service/NostrSingleEventDataSource.kt | 3 +++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrDataSource.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrDataSource.kt index 55f01d3b4..4b230adee 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrDataSource.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrDataSource.kt @@ -14,6 +14,7 @@ import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.cancel import kotlinx.coroutines.launch import java.util.UUID +import java.util.concurrent.atomic.AtomicBoolean import kotlin.Error abstract class NostrDataSource(val debugName: String) { @@ -23,6 +24,7 @@ abstract class NostrDataSource(val debugName: String) { data class Counter(var counter: Int) private var eventCounter = mapOf() + var changingFilters = AtomicBoolean() fun printCounter() { eventCounter.forEach { @@ -59,7 +61,10 @@ abstract class NostrDataSource(val debugName: String) { if (type == Relay.Type.EOSE && subscriptionId != null && subscriptionId in subscriptions.keys) { // updates a per subscripton since date - subscriptions[subscriptionId]?.updateEOSE(TimeUtils.now(), relay.url) + subscriptions[subscriptionId]?.updateEOSE( + TimeUtils.fiveMinutesAgo(), // in case people's clock is slighly off. + relay.url + ) } } @@ -136,6 +141,8 @@ abstract class NostrDataSource(val debugName: String) { // saves the current content to only update if it changes val currentFilters = activeSubscriptions.associate { it.id to it.toJson() } + changingFilters.getAndSet(true) + updateChannelFilters() // Makes sure to only send an updated filter when it actually changes. @@ -167,6 +174,8 @@ abstract class NostrDataSource(val debugName: String) { } } } + + changingFilters.getAndSet(false) } open fun consume(event: Event, relay: Relay) { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrSingleEventDataSource.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrSingleEventDataSource.kt index deb51116f..91ba45d86 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrSingleEventDataSource.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrSingleEventDataSource.kt @@ -134,6 +134,9 @@ object NostrSingleEventDataSource : NostrDataSource("SingleEventFeed") { } val singleEventChannel = requestNewChannel { time, relayUrl -> + // Ignores EOSE if it is in the middle of a filter change. + if (changingFilters.get()) return@requestNewChannel + checkNotInMainThread() eventsToWatch.forEach { From db740f4747f8b5c2e098b8051129e3fb377d8135 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Tue, 5 Sep 2023 17:21:36 -0400 Subject: [PATCH 06/21] Upgrades the RelayPoolStatus to a Flow --- .../amethyst/service/relays/RelayPool.kt | 40 +++++++++------ .../amethyst/ui/navigation/DrawerContent.kt | 50 +++++++++++-------- .../amethyst/ui/screen/RelayPoolViewModel.kt | 20 -------- 3 files changed, 55 insertions(+), 55 deletions(-) delete mode 100644 app/src/main/java/com/vitorpamplona/amethyst/ui/screen/RelayPoolViewModel.kt diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/relays/RelayPool.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/relays/RelayPool.kt index 3233307e9..7638d7b45 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/relays/RelayPool.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/relays/RelayPool.kt @@ -1,9 +1,13 @@ package com.vitorpamplona.amethyst.service.relays -import androidx.lifecycle.LiveData +import androidx.compose.runtime.Immutable import com.vitorpamplona.amethyst.service.checkNotInMainThread import com.vitorpamplona.quartz.events.Event import com.vitorpamplona.quartz.events.EventInterface +import kotlinx.coroutines.channels.BufferOverflow +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.flow.asSharedFlow /** * RelayPool manages the connection to multiple Relays and lets consumers deal with simple events. @@ -12,6 +16,11 @@ object RelayPool : Relay.Listener { private var relays = listOf() private var listeners = setOf() + // Backing property to avoid flow emissions from other classes + private var _lastStatus = RelayPoolStatus(0, 0) + private val _statusFlow = MutableSharedFlow(1, 1, onBufferOverflow = BufferOverflow.DROP_OLDEST) + val statusFlow: SharedFlow = _statusFlow.asSharedFlow() + fun availableRelays(): Int { return relays.size } @@ -76,11 +85,13 @@ object RelayPool : Relay.Listener { fun addRelay(relay: Relay) { relay.register(this) relays += relay + updateStatus() } fun removeRelay(relay: Relay) { relay.unregister(this) relays = relays.minus(relay) + updateStatus() } fun register(listener: Listener) { @@ -109,12 +120,14 @@ object RelayPool : Relay.Listener { override fun onError(relay: Relay, subscriptionId: String, error: Error) { listeners.forEach { it.onError(error, subscriptionId, relay) } - refreshObservers() + updateStatus() } override fun onRelayStateChange(relay: Relay, type: Relay.Type, channel: String?) { listeners.forEach { it.onRelayStateChange(type, relay, channel) } - refreshObservers() + if (type != Relay.Type.EOSE) { + updateStatus() + } } override fun onSendResponse(relay: Relay, eventId: String, success: Boolean, message: String) { @@ -125,18 +138,15 @@ object RelayPool : Relay.Listener { listeners.forEach { it.onAuth(relay, challenge) } } - // Observers line up here. - val live: RelayPoolLiveData = RelayPoolLiveData(this) - - private fun refreshObservers() { - live.refresh() + private fun updateStatus() { + val connected = connectedRelays() + val available = availableRelays() + if (_lastStatus.connected != connected || _lastStatus.available != available) { + _lastStatus = RelayPoolStatus(connected, available) + _statusFlow.tryEmit(_lastStatus) + } } } -class RelayPoolLiveData(val relays: RelayPool) : LiveData(RelayPoolState(relays)) { - fun refresh() { - postValue(RelayPoolState(relays)) - } -} - -class RelayPoolState(val relays: RelayPool) +@Immutable +data class RelayPoolStatus(val connected: Int, val available: Int, val isConnected: Boolean = connected > 0) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/DrawerContent.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/DrawerContent.kt index 0e8f37c8d..c567d0481 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/DrawerContent.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/DrawerContent.kt @@ -40,6 +40,7 @@ import androidx.compose.material.icons.filled.Send import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState @@ -63,7 +64,6 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.lifecycle.map -import androidx.lifecycle.viewmodel.compose.viewModel import coil.compose.AsyncImage import com.vitorpamplona.amethyst.BuildConfig import com.vitorpamplona.amethyst.LocalPreferences @@ -72,11 +72,12 @@ import com.vitorpamplona.amethyst.ServiceManager import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.service.HttpClient +import com.vitorpamplona.amethyst.service.relays.RelayPool +import com.vitorpamplona.amethyst.service.relays.RelayPoolStatus import com.vitorpamplona.amethyst.ui.actions.NewRelayListView import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji import com.vitorpamplona.amethyst.ui.components.RobohashAsyncImageProxy import com.vitorpamplona.amethyst.ui.note.LoadStatuses -import com.vitorpamplona.amethyst.ui.screen.RelayPoolViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountBackupDialog import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.ConnectOrbotDialog @@ -454,7 +455,6 @@ fun ListContent( } val coroutineScope = rememberCoroutineScope() - val relayViewModel: RelayPoolViewModel = viewModel { RelayPoolViewModel() } var wantsToEditRelays by remember { mutableStateOf(false) } @@ -490,7 +490,7 @@ fun ListContent( ) IconRowRelays( - relayViewModel = relayViewModel, + accountViewModel = accountViewModel, onClick = { coroutineScope.launch { scaffoldState.drawerState.close() @@ -633,27 +633,37 @@ private fun enableTor( } @Composable -private fun RelayStatus( - relayViewModel: RelayPoolViewModel -) { - val connectedRelaysText by relayViewModel.connectionStatus.observeAsState("--/--") - val isConnected by relayViewModel.isConnected.observeAsState(false) +private fun RelayStatus(accountViewModel: AccountViewModel) { + val connectedRelaysText by RelayPool.statusFlow.collectAsState(initial = RelayPoolStatus(0, 0)) - RenderRelayStatus(connectedRelaysText, isConnected) + RenderRelayStatus(connectedRelaysText) } @Composable private fun RenderRelayStatus( - connectedRelaysText: String, - isConnected: Boolean + relayPool: RelayPoolStatus ) { + val text by remember(relayPool) { + derivedStateOf { + "${relayPool.connected}/${relayPool.available}" + } + } + + val placeHolder = MaterialTheme.colors.placeholderText + + val color by remember(relayPool) { + derivedStateOf { + if (relayPool.isConnected) { + placeHolder + } else { + Color.Red + } + } + } + Text( - text = connectedRelaysText, - color = if (isConnected) { - MaterialTheme.colors.placeholderText - } else { - Color.Red - }, + text = text, + color = color, style = MaterialTheme.typography.subtitle1 ) } @@ -709,7 +719,7 @@ fun IconRow(title: String, icon: Int, tint: Color, onClick: () -> Unit, onLongCl } @Composable -fun IconRowRelays(relayViewModel: RelayPoolViewModel, onClick: () -> Unit) { +fun IconRowRelays(accountViewModel: AccountViewModel, onClick: () -> Unit) { Row( modifier = Modifier .fillMaxWidth() @@ -736,7 +746,7 @@ fun IconRowRelays(relayViewModel: RelayPoolViewModel, onClick: () -> Unit) { Spacer(modifier = Modifier.width(Size16dp)) - RelayStatus(relayViewModel = relayViewModel) + RelayStatus(accountViewModel = accountViewModel) } } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/RelayPoolViewModel.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/RelayPoolViewModel.kt deleted file mode 100644 index a57974b6c..000000000 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/RelayPoolViewModel.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.vitorpamplona.amethyst.ui.screen - -import androidx.compose.runtime.Stable -import androidx.lifecycle.ViewModel -import androidx.lifecycle.distinctUntilChanged -import androidx.lifecycle.map -import com.vitorpamplona.amethyst.service.relays.RelayPool - -@Stable -class RelayPoolViewModel : ViewModel() { - val connectionStatus = RelayPool.live.map { - val connectedRelays = it.relays.connectedRelays() - val availableRelays = it.relays.availableRelays() - "$connectedRelays/$availableRelays" - }.distinctUntilChanged() - - val isConnected = RelayPool.live.map { - it.relays.connectedRelays() > 0 - }.distinctUntilChanged() -} From 02f18b5b353f6a9e60cb79bef1e301940bed21ee Mon Sep 17 00:00:00 2001 From: David Kaspar Date: Wed, 6 Sep 2023 13:35:45 +0200 Subject: [PATCH 07/21] Updated translations for cs/de --- app/src/main/res/values-cs/strings.xml | 34 ++++++++++++++++++ app/src/main/res/values-de/strings.xml | 48 +++++++++++++++++++++++--- 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index cbfbb9564..818e86869 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -12,6 +12,7 @@ Nepodařilo se dešifrovat zprávu Obrázek skupiny Explicitní obsah + Spam Zneužívání identity Nezákonné jednání Neznámý @@ -169,6 +170,9 @@ Nostr Adresa nikdy nyní + h + m + d Nahota Neslušnost / Nenávistná řeč Nahlásit nenávistnou řeč @@ -245,6 +249,7 @@ Přidat do veřejných záložek Odebrat ze soukromých záložek Odebrat z veřejných záložek + Služba Wallet Connect Autorizuje Nostr Secret k provedení platby zapsům, aniž byste opustili aplikaci. Uchovávejte tajemství v bezpečí a pokud je to možné, používejte soukromý přepojovač. Veřejný klíč Wallet Connect @@ -252,6 +257,7 @@ Tajemství Wallet Connect Zobrazit tajný klíč nsec / hex soukromý klíč + Částka záruky v sats Zveřejnit hlasování Povinná pole: @@ -263,6 +269,7 @@ Minimální zaps Maximální zaps Konsensus + (0–100)% Uzavřít po dnech Hlasování je uzavřeno pro nové hlasy @@ -283,16 +290,22 @@ Přidat do zprávy Popis obsahu Modrá loď na bílé písčité pláži při západu slunce + Typ zapsu Typ zapsu pro všechny možnosti + Veřejný Všichni mohou vidět transakci a zprávu + Soukromý Odesílatel a příjemce si mohou navzájem vidět a číst zprávu + Anonymní Příjemce a veřejnost neví, kdo platbu poslal + Né Zap Žádná stopa v Nostr, pouze v Lightning + Souborový server LnAddress nebo @Uživatel imgur.com – důvěryhodný @@ -326,6 +339,7 @@ Seznam sledovaných Všechna sledování Globální + ## Připojte se přes Tor s Orbotem \n\n1. Nainstalujte [Orbot](https://play.google.com/store/apps/details?id=org.torproject.android) \n2. Spusťte Orbot @@ -334,15 +348,20 @@ \n5. Nastavte port Socks v této obrazovce \n6. Stiskněte tlačítko Aktivovat Tor + Orbot Socks Port Neplatné číslo portu Použít Orbot Odpojit Tor/Orbot + Soukromé zprávy Upozorní vás, když dorazí soukromá zpráva + Přijaté zapsy Upozorní vás, když vás někdo zapsne + %1$s sats Od %1$s pro %1$s + Upozornit: Připojit se ke konverzaci ID uživatele nebo skupiny @@ -360,15 +379,18 @@ Varovat, když příspěvky obsahují hlášení od vašich sledovaných osob Nový symbol reakce Nebyly vybrány žádné typy reakcí. Dlouhým stiskem změňte + Zap-sběr Přidá cílovou částku sats pro tento příspěvek. Podporované klienty ji mohou zobrazovat jako ukazatel postupu k podněcování darování Cílová částka v sats + Zapraiser na %1$s. Do cíle zbývá %2$s sats Číst z Relay Zapisovat do Relay Při pokusu o získání informací z Relay se vyskytla chyba z %1$s Vlastník Verze + Software Kontakt Podporované NIPs Vstupné @@ -388,21 +410,27 @@ Minimální PoW Ověření Platba + Cashu Token Vyměnit Není nastavena žádná Lightning adresa Token zkopírován do schránky + ŽIVĚ + OFFLINE UKONČENO PLÁNOVÁNO + Živé vysílání je offline Živé vysílání ukončeno Odhlášení vymaže všechny vaše místní informace. Ujistěte se, že máte zálohované své privátní klíče, abyste se vyhnuli ztrátě účtu. Chcete pokračovat? Sledované značky Rele + Živě Komunita Chaty Schválené příspěvky + Tato skupina nemá popis ani pravidla. Promluvte si s majitelem, aby je přidal/a. Tato komunita nemá popis. Promluvte si s majitelem, aby ho přidal/a. Citlivý obsah @@ -454,13 +482,19 @@ Automaticky přehrávat videa a GIFy Zobrazit náhledy URL Kdy načíst obrázek + Kopírovat URL do schránky Kopírovat ID poznámky do schránky + Vytvořeno Pravidla + Aktualizovat svůj stav + Chyba při zpracování chybové zprávy Hlasy jsou váženy podle hodnoty zapu. Můžete nastavit minimální částku, abyste se vyhnuli spammerům, a maximální částku, abyste zabránili velkým zapperům, kteří by mohli ovládnout hlasování. Použijte stejnou částku v obou polích, aby byla hodnota každého hlasu stejná. Nechte prázdné pro přijetí libovolné částky. + Nelze odeslat zap Poslat zprávu uživateli + Ok diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 91e737c26..71ef055ef 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -12,6 +12,7 @@ Nachricht konnte nicht entschlüsselt werden Gruppenbild Expliziter Inhalt + Spam Vortäuschung Illegales Verhalten Unbekannt @@ -35,6 +36,7 @@ Melden Sie sich mit einem privaten Schlüssel an, um Zaps senden zu können Melden Sie sich mit einem privaten Schlüssel an, um zu folgen Melden Sie sich mit einem privaten Schlüssel an, um nicht mehr zu folgen + Zaps Aufrufe Verstärken Verstärkt @@ -76,6 +78,7 @@ Fehler beim Hochladen des Bildes Relay-Adresse Beiträge + Bytes Fehler Startseite Private Nachrichten @@ -162,6 +165,7 @@ anz der Bedingungen ist erforderlich Öffentlicher Chat empfangene Beiträge Entfernen + Auto übersetzt von zu Zuerst in %1$s anzeigen @@ -170,9 +174,10 @@ anz der Bedingungen ist erforderlich Nostr-Adresse nie jetzt - s - t Nacktheit + s + m + t Beleidigungen / Hassrede Hassrede melden Nacktheit / Pornografie melden @@ -249,23 +254,28 @@ anz der Bedingungen ist erforderlich Zu den öffentlichen Lesezeichen hinzufügen Aus den privaten Lesezeichen entfernen Aus den öffentlichen Lesezeichen entfernen + Wallet-Verbindungsdienst Autorisiert einen Nostr Secret, Zaps ohne Verlassen der App zu bezahlen. Bewahren Sie den geheimen Schlüssel sicher auf und verwenden Sie nach Möglichkeit ein privates Relay Wallet Connect Public Key - Wallet-Verbindungs-Relay - Wallet-Verbindungs-Geheimnis Geheimen Schlüssel anzeigen nsec / hexadezimaler privater Schlüssel + Wallet-Verbindungs-Relay + Wallet-Verbindungs-Geheimnis + + Spendenbetrag in Sats Umfrage veröffentlichen Erforderliche Felder: Zap-Empfänger Hauptbeschreibung der Umfrage… + Option %s Beschreibung der Umfrageoption Optionale Felder: Mindest-Zap-Betrag Maximaler Zap-Betrag Konsens + (0–100)% Schließen nach Tagen Umfrage ist für neue Stimmen geschlossen @@ -286,16 +296,22 @@ anz der Bedingungen ist erforderlich Zur Nachricht hinzufügen Beschreibung des Inhalts Ein blaues Boot an einem weißen Sandstrand bei Sonnenuntergang + Zap-Typ Zap-Typ für alle Optionen + Öffentlich Jeder kann die Transaktion und die Nachricht sehen + Privat Absender und Empfänger können einander sehen und die Nachricht lesen + Anonym Empfänger und die Öffentlichkeit wissen nicht, wer die Zahlung gesendet hat + Keine Zap Keine Spur in Nostr, nur in Lightning + Dateiserver LnAddress oder @Benutzer imgur.com - vertrauenswürdig @@ -326,6 +342,7 @@ anz der Bedingungen ist erforderlich Deine Daten werden sofort im regulären Netzwerk übertragen Ja Nein + Folgen-Liste Alle Folgen Weltweit @@ -340,6 +357,7 @@ anz der Bedingungen ist erforderlich Ungültige Portnummer Orbot verwenden Tor/Orbot trennen + Private Nachrichten Benachrichtigt dich, wenn eine private Nachricht eingeht Erhaltene Zaps @@ -364,14 +382,18 @@ anz der Bedingungen ist erforderlich Warnung bei Meldungen von deinen Abonnements Neues Reaktionssymbol Keine Reaktionstypen ausgewählt. Lange drücken, um zu ändern + Zap-Sammlung Fügt einen Zielbetrag in Sats für diesen Beitrag hinzu. Unterstützende Clients können dies als Fortschrittsbalken anzeigen, um Spenden zu fördern Zielbetrag in Sats + Zapraiser bei %1$s. %2$s Sats bis zum Ziel Von Relay lesen In Relay schreiben Ein Fehler ist beim Abrufen von Relay-Informationen von %1$s aufgetreten Inhaber + Version + Software Kontakt Unterstützte NIPs Eintrittsgebühren @@ -379,6 +401,7 @@ anz der Bedingungen ist erforderlich Einschränkungen Länder Sprachen + Tags Veröffentlichungsrichtlinie Nachrichtenlänge Abonnements @@ -394,14 +417,23 @@ anz der Bedingungen ist erforderlich Einlösen Keine Lightning-Adresse festgelegt Token in die Zwischenablage kopiert + + LIVE + OFFLINE BEENDET GEPLANT + Livestream ist offline Livestream beendet Das Abmelden löscht alle Ihre lokalen Informationen. Stellen Sie sicher, dass Sie Ihre privaten Schlüssel gesichert haben, um einen Kontoverlust zu vermeiden. Möchten Sie fortfahren? Gefolgte Tags Relais + + Live + Community + Chats Genehmigte Beiträge + Diese Gruppe hat keine Beschreibung oder Regeln. Sprechen Sie mit dem Eigentümer, um eine hinzuzufügen. Diese Community hat keine Beschreibung. Sprechen Sie mit dem Eigentümer, um eine hinzuzufügen. Sensibler Inhalt @@ -410,6 +442,8 @@ anz der Bedingungen ist erforderlich Immer Nur WLAN Nie + + System Hell Dunkel Anwendungseinstellungen @@ -452,13 +486,19 @@ anz der Bedingungen ist erforderlich Videos und GIFs automatisch abspielen URL-Vorschauen anzeigen Wann Bilder geladen werden sollen + URL in die Zwischenablage kopieren Notiz-ID in die Zwischenablage kopieren + Erstellt am Regeln + Status aktualisieren + Fehler beim Verarbeiten der Fehlermeldung Die Abstimmungen werden nach der Höhe des Zaps gewichtet. Sie können einen Mindestbetrag festlegen, um Spam zu verhindern, und einen Höchstbetrag, um zu verhindern, dass große Zapper die Abstimmung dominieren. Verwenden Sie denselben Betrag in beiden Feldern, um sicherzustellen, dass jeder Stimme der gleiche Wert zukommt. Lassen Sie es leer, um jeden Betrag zu akzeptieren. + Zap konnte nicht gesendet werden Mit dem Benutzer kommunizieren + Ok From e67534846133eec84ac3109de0d19ebd16c22e16 Mon Sep 17 00:00:00 2001 From: Crowdin Bot Date: Wed, 6 Sep 2023 14:04:55 +0000 Subject: [PATCH 08/21] New Crowdin translations by GitHub Action --- app/src/main/res/values-cs/strings.xml | 34 ---------------- app/src/main/res/values-de/strings.xml | 46 ++-------------------- app/src/main/res/values-zh-rCN/strings.xml | 46 ++++++++++++++++++++++ 3 files changed, 49 insertions(+), 77 deletions(-) diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 818e86869..cbfbb9564 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -12,7 +12,6 @@ Nepodařilo se dešifrovat zprávu Obrázek skupiny Explicitní obsah - Spam Zneužívání identity Nezákonné jednání Neznámý @@ -170,9 +169,6 @@ Nostr Adresa nikdy nyní - h - m - d Nahota Neslušnost / Nenávistná řeč Nahlásit nenávistnou řeč @@ -249,7 +245,6 @@ Přidat do veřejných záložek Odebrat ze soukromých záložek Odebrat z veřejných záložek - Služba Wallet Connect Autorizuje Nostr Secret k provedení platby zapsům, aniž byste opustili aplikaci. Uchovávejte tajemství v bezpečí a pokud je to možné, používejte soukromý přepojovač. Veřejný klíč Wallet Connect @@ -257,7 +252,6 @@ Tajemství Wallet Connect Zobrazit tajný klíč nsec / hex soukromý klíč - Částka záruky v sats Zveřejnit hlasování Povinná pole: @@ -269,7 +263,6 @@ Minimální zaps Maximální zaps Konsensus - (0–100)% Uzavřít po dnech Hlasování je uzavřeno pro nové hlasy @@ -290,22 +283,16 @@ Přidat do zprávy Popis obsahu Modrá loď na bílé písčité pláži při západu slunce - Typ zapsu Typ zapsu pro všechny možnosti - Veřejný Všichni mohou vidět transakci a zprávu - Soukromý Odesílatel a příjemce si mohou navzájem vidět a číst zprávu - Anonymní Příjemce a veřejnost neví, kdo platbu poslal - Né Zap Žádná stopa v Nostr, pouze v Lightning - Souborový server LnAddress nebo @Uživatel imgur.com – důvěryhodný @@ -339,7 +326,6 @@ Seznam sledovaných Všechna sledování Globální - ## Připojte se přes Tor s Orbotem \n\n1. Nainstalujte [Orbot](https://play.google.com/store/apps/details?id=org.torproject.android) \n2. Spusťte Orbot @@ -348,20 +334,15 @@ \n5. Nastavte port Socks v této obrazovce \n6. Stiskněte tlačítko Aktivovat Tor - Orbot Socks Port Neplatné číslo portu Použít Orbot Odpojit Tor/Orbot - Soukromé zprávy Upozorní vás, když dorazí soukromá zpráva - Přijaté zapsy Upozorní vás, když vás někdo zapsne - %1$s sats Od %1$s pro %1$s - Upozornit: Připojit se ke konverzaci ID uživatele nebo skupiny @@ -379,18 +360,15 @@ Varovat, když příspěvky obsahují hlášení od vašich sledovaných osob Nový symbol reakce Nebyly vybrány žádné typy reakcí. Dlouhým stiskem změňte - Zap-sběr Přidá cílovou částku sats pro tento příspěvek. Podporované klienty ji mohou zobrazovat jako ukazatel postupu k podněcování darování Cílová částka v sats - Zapraiser na %1$s. Do cíle zbývá %2$s sats Číst z Relay Zapisovat do Relay Při pokusu o získání informací z Relay se vyskytla chyba z %1$s Vlastník Verze - Software Kontakt Podporované NIPs Vstupné @@ -410,27 +388,21 @@ Minimální PoW Ověření Platba - Cashu Token Vyměnit Není nastavena žádná Lightning adresa Token zkopírován do schránky - ŽIVĚ - OFFLINE UKONČENO PLÁNOVÁNO - Živé vysílání je offline Živé vysílání ukončeno Odhlášení vymaže všechny vaše místní informace. Ujistěte se, že máte zálohované své privátní klíče, abyste se vyhnuli ztrátě účtu. Chcete pokračovat? Sledované značky Rele - Živě Komunita Chaty Schválené příspěvky - Tato skupina nemá popis ani pravidla. Promluvte si s majitelem, aby je přidal/a. Tato komunita nemá popis. Promluvte si s majitelem, aby ho přidal/a. Citlivý obsah @@ -482,19 +454,13 @@ Automaticky přehrávat videa a GIFy Zobrazit náhledy URL Kdy načíst obrázek - Kopírovat URL do schránky Kopírovat ID poznámky do schránky - Vytvořeno Pravidla - Aktualizovat svůj stav - Chyba při zpracování chybové zprávy Hlasy jsou váženy podle hodnoty zapu. Můžete nastavit minimální částku, abyste se vyhnuli spammerům, a maximální částku, abyste zabránili velkým zapperům, kteří by mohli ovládnout hlasování. Použijte stejnou částku v obou polích, aby byla hodnota každého hlasu stejná. Nechte prázdné pro přijetí libovolné částky. - Nelze odeslat zap Poslat zprávu uživateli - Ok diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 71ef055ef..91e737c26 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -12,7 +12,6 @@ Nachricht konnte nicht entschlüsselt werden Gruppenbild Expliziter Inhalt - Spam Vortäuschung Illegales Verhalten Unbekannt @@ -36,7 +35,6 @@ Melden Sie sich mit einem privaten Schlüssel an, um Zaps senden zu können Melden Sie sich mit einem privaten Schlüssel an, um zu folgen Melden Sie sich mit einem privaten Schlüssel an, um nicht mehr zu folgen - Zaps Aufrufe Verstärken Verstärkt @@ -78,7 +76,6 @@ Fehler beim Hochladen des Bildes Relay-Adresse Beiträge - Bytes Fehler Startseite Private Nachrichten @@ -165,7 +162,6 @@ anz der Bedingungen ist erforderlich Öffentlicher Chat empfangene Beiträge Entfernen - Auto übersetzt von zu Zuerst in %1$s anzeigen @@ -174,10 +170,9 @@ anz der Bedingungen ist erforderlich Nostr-Adresse nie jetzt - Nacktheit s - m t + Nacktheit Beleidigungen / Hassrede Hassrede melden Nacktheit / Pornografie melden @@ -254,28 +249,23 @@ anz der Bedingungen ist erforderlich Zu den öffentlichen Lesezeichen hinzufügen Aus den privaten Lesezeichen entfernen Aus den öffentlichen Lesezeichen entfernen - Wallet-Verbindungsdienst Autorisiert einen Nostr Secret, Zaps ohne Verlassen der App zu bezahlen. Bewahren Sie den geheimen Schlüssel sicher auf und verwenden Sie nach Möglichkeit ein privates Relay Wallet Connect Public Key - Geheimen Schlüssel anzeigen - nsec / hexadezimaler privater Schlüssel Wallet-Verbindungs-Relay Wallet-Verbindungs-Geheimnis - - + Geheimen Schlüssel anzeigen + nsec / hexadezimaler privater Schlüssel Spendenbetrag in Sats Umfrage veröffentlichen Erforderliche Felder: Zap-Empfänger Hauptbeschreibung der Umfrage… - Option %s Beschreibung der Umfrageoption Optionale Felder: Mindest-Zap-Betrag Maximaler Zap-Betrag Konsens - (0–100)% Schließen nach Tagen Umfrage ist für neue Stimmen geschlossen @@ -296,22 +286,16 @@ anz der Bedingungen ist erforderlich Zur Nachricht hinzufügen Beschreibung des Inhalts Ein blaues Boot an einem weißen Sandstrand bei Sonnenuntergang - Zap-Typ Zap-Typ für alle Optionen - Öffentlich Jeder kann die Transaktion und die Nachricht sehen - Privat Absender und Empfänger können einander sehen und die Nachricht lesen - Anonym Empfänger und die Öffentlichkeit wissen nicht, wer die Zahlung gesendet hat - Keine Zap Keine Spur in Nostr, nur in Lightning - Dateiserver LnAddress oder @Benutzer imgur.com - vertrauenswürdig @@ -342,7 +326,6 @@ anz der Bedingungen ist erforderlich Deine Daten werden sofort im regulären Netzwerk übertragen Ja Nein - Folgen-Liste Alle Folgen Weltweit @@ -357,7 +340,6 @@ anz der Bedingungen ist erforderlich Ungültige Portnummer Orbot verwenden Tor/Orbot trennen - Private Nachrichten Benachrichtigt dich, wenn eine private Nachricht eingeht Erhaltene Zaps @@ -382,18 +364,14 @@ anz der Bedingungen ist erforderlich Warnung bei Meldungen von deinen Abonnements Neues Reaktionssymbol Keine Reaktionstypen ausgewählt. Lange drücken, um zu ändern - Zap-Sammlung Fügt einen Zielbetrag in Sats für diesen Beitrag hinzu. Unterstützende Clients können dies als Fortschrittsbalken anzeigen, um Spenden zu fördern Zielbetrag in Sats - Zapraiser bei %1$s. %2$s Sats bis zum Ziel Von Relay lesen In Relay schreiben Ein Fehler ist beim Abrufen von Relay-Informationen von %1$s aufgetreten Inhaber - Version - Software Kontakt Unterstützte NIPs Eintrittsgebühren @@ -401,7 +379,6 @@ anz der Bedingungen ist erforderlich Einschränkungen Länder Sprachen - Tags Veröffentlichungsrichtlinie Nachrichtenlänge Abonnements @@ -417,23 +394,14 @@ anz der Bedingungen ist erforderlich Einlösen Keine Lightning-Adresse festgelegt Token in die Zwischenablage kopiert - - LIVE - OFFLINE BEENDET GEPLANT - Livestream ist offline Livestream beendet Das Abmelden löscht alle Ihre lokalen Informationen. Stellen Sie sicher, dass Sie Ihre privaten Schlüssel gesichert haben, um einen Kontoverlust zu vermeiden. Möchten Sie fortfahren? Gefolgte Tags Relais - - Live - Community - Chats Genehmigte Beiträge - Diese Gruppe hat keine Beschreibung oder Regeln. Sprechen Sie mit dem Eigentümer, um eine hinzuzufügen. Diese Community hat keine Beschreibung. Sprechen Sie mit dem Eigentümer, um eine hinzuzufügen. Sensibler Inhalt @@ -442,8 +410,6 @@ anz der Bedingungen ist erforderlich Immer Nur WLAN Nie - - System Hell Dunkel Anwendungseinstellungen @@ -486,19 +452,13 @@ anz der Bedingungen ist erforderlich Videos und GIFs automatisch abspielen URL-Vorschauen anzeigen Wann Bilder geladen werden sollen - URL in die Zwischenablage kopieren Notiz-ID in die Zwischenablage kopieren - Erstellt am Regeln - Status aktualisieren - Fehler beim Verarbeiten der Fehlermeldung Die Abstimmungen werden nach der Höhe des Zaps gewichtet. Sie können einen Mindestbetrag festlegen, um Spam zu verhindern, und einen Höchstbetrag, um zu verhindern, dass große Zapper die Abstimmung dominieren. Verwenden Sie denselben Betrag in beiden Feldern, um sicherzustellen, dass jeder Stimme der gleiche Wert zukommt. Lassen Sie es leer, um jeden Betrag zu akzeptieren. - Zap konnte nicht gesendet werden Mit dem Benutzer kommunizieren - Ok diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 4d9ec0a96..292521640 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -123,6 +123,7 @@ 发送直接消息 编辑用户元数据 关注 + 回关 不隐藏 复制用户ID 隐藏该用户 @@ -273,13 +274,35 @@ 发送方和接收方能互相看到并读取消息 匿名 非打闪 + Tor/Orbot 设置 + 通过你的 Orbot 设置连接 断开与你的 Orbot/Tor 连接? + 关注列表 + 所有关注 全球 + Orbot Socks 端口 使用 Orbot + 断开 Tor/Orbot 连接 + 私信 + 收到私信时通知你 + 收到打闪 + 收到打闪时通知你 + 通知: + 用户或群组 ID + npub、nevent 或 hex + 创建 + 加入 今天 + 内容警告 + 始终隐藏敏感内容 + 始终显示敏感内容 + 始终显示内容警告 推荐: + 过滤来自陌生人的垃圾信息 + 从中继器读取 + 写入到继电器 版本 软件 联络 @@ -293,8 +316,17 @@ 消息长度 订阅 筛选器 + 内容长度 + 认证 + 支付 + 兑换 + 未设置闪电地址 + 已复制令牌至剪贴板 + 直播 离线 已结束 + 直播处于离线状态 + 直播已结束 已关注的标签 中继器 直播 @@ -310,11 +342,25 @@ 深色 语言 主题 + 图像预览 加载图像 Nostr 地址已验证 Nostr 地址验证失败 正在检查 Nostr 地址 默认 公开 + 此群组成员 + 暗色、亮色或系统主题 + 自动加载图像和 GIF + 自动播放视频和 GIF + 显示 URL 预览 + 何时加载图像 + 复制链接到剪贴板 + 复制笔记 ID 到剪贴板 创建于 + 规则 + 更新你的状态 + 无法发送打闪 + 向用户发送消息 + From 4e8ecff67bc8aa953592b716187703c14b0a8ccb Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Thu, 7 Sep 2023 09:55:43 -0400 Subject: [PATCH 09/21] Fixes the missing channel header in conversations. --- .../amethyst/ui/note/NoteCompose.kt | 55 ++++++++++--------- .../com/vitorpamplona/quartz/events/Event.kt | 2 +- .../quartz/events/EventInterface.kt | 2 + 3 files changed, 31 insertions(+), 28 deletions(-) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt index d13e3468f..66aaa30c1 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt @@ -2397,40 +2397,41 @@ private fun ReplyRow( } } + val showChannelInfo by remember(note) { + derivedStateOf { + if (noteEvent is ChannelMessageEvent || noteEvent is LiveActivitiesChatMessageEvent) { + note.channelHex() + } else { + null + } + } + } + + showChannelInfo?.let { + ChannelHeader( + channelHex = it, + showVideo = false, + showBottomDiviser = false, + sendToChannel = true, + modifier = MaterialTheme.colors.replyModifier.padding(10.dp), + accountViewModel = accountViewModel, + nav = nav + ) + } + if (showReply) { val replyingDirectlyTo = remember { note.replyTo?.lastOrNull { it.event?.kind() != CommunityDefinitionEvent.kind } } if (replyingDirectlyTo != null && unPackReply) { ReplyNoteComposition(replyingDirectlyTo, backgroundColor, accountViewModel, nav) Spacer(modifier = StdVertSpacer) - } else { - // ReplyInformation(note.replyTo, noteEvent.mentions(), accountViewModel, nav) - } - } else { - val showChannelReply by remember { - derivedStateOf { - (noteEvent is ChannelMessageEvent && (note.replyTo != null || noteEvent.hasAnyTaggedUser())) || - (noteEvent is LiveActivitiesChatMessageEvent && (note.replyTo != null || noteEvent.hasAnyTaggedUser())) + } else if (showChannelInfo != null) { + val replies = remember { note.replyTo?.toImmutableList() } + val mentions = remember { + (note.event as? BaseTextNoteEvent)?.mentions()?.toImmutableList() + ?: persistentListOf() } - } - if (showChannelReply) { - val channelHex = note.channelHex() - channelHex?.let { - ChannelHeader( - channelHex = channelHex, - showVideo = false, - showBottomDiviser = false, - sendToChannel = true, - modifier = remember { Modifier.padding(vertical = 5.dp) }, - accountViewModel = accountViewModel, - nav = nav - ) - - val replies = remember { note.replyTo?.toImmutableList() } - val mentions = remember { (note.event as? BaseTextNoteEvent)?.mentions()?.toImmutableList() ?: persistentListOf() } - - ReplyInformationChannel(replies, mentions, accountViewModel, nav) - } + ReplyInformationChannel(replies, mentions, accountViewModel, nav) } } } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/events/Event.kt b/quartz/src/main/java/com/vitorpamplona/quartz/events/Event.kt index 5aa91ebf3..ae78d7bc4 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/events/Event.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/events/Event.kt @@ -62,7 +62,7 @@ open class Event( override fun toJson(): String = mapper.writeValueAsString(toJsonObject()) - fun hasAnyTaggedUser() = tags.any { it.size > 1 && it[0] == "p" } + override fun hasAnyTaggedUser() = tags.any { it.size > 1 && it[0] == "p" } override fun taggedUsers() = tags.filter { it.size > 1 && it[0] == "p" }.map { it[1] } override fun taggedEvents() = tags.filter { it.size > 1 && it[0] == "e" }.map { it[1] } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/events/EventInterface.kt b/quartz/src/main/java/com/vitorpamplona/quartz/events/EventInterface.kt index 61ba9dbc6..64d308777 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/events/EventInterface.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/events/EventInterface.kt @@ -63,6 +63,8 @@ interface EventInterface { fun subject(): String? fun zapraiserAmount(): Long? + fun hasAnyTaggedUser(): Boolean + fun taggedAddresses(): List fun taggedUsers(): List fun taggedEvents(): List From 8c51c4be0cd3d2ef304e82695de8032c52e6a09c Mon Sep 17 00:00:00 2001 From: Crowdin Bot Date: Thu, 7 Sep 2023 13:57:52 +0000 Subject: [PATCH 10/21] New Crowdin translations by GitHub Action --- app/src/main/res/values-sw-rKE/strings.xml | 67 +++++++++++++++++++++- app/src/main/res/values-zh-rCN/strings.xml | 1 + app/src/main/res/values-zh-rTW/strings.xml | 48 ++++++++++++++++ 3 files changed, 115 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/values-sw-rKE/strings.xml b/app/src/main/res/values-sw-rKE/strings.xml index 66a570486..48a0a8b40 100644 --- a/app/src/main/res/values-sw-rKE/strings.xml +++ b/app/src/main/res/values-sw-rKE/strings.xml @@ -1,2 +1,67 @@ - + + Elekeza kwa Msimbo wa QR + Onyesha QR + Picha ya Wasifu + Changanua QR + Onyesha Hata hivyo + Chapisho liliripotiwa na + Tukio linapakia au halipatikani kwenye orodha yako ya upeanaji + Picha ya Kituo + Tukio linalorejelewa halijapatikana + Haikuweza kusimbua ujumbe + Picha ya Kikundi + Maudhui Dhahiri + Barua taka + Uigaji + Tabia Haramu + Haijulikani + Aikoni ya Relay + Mwandishi asiyejulikana + Nakili Maandishi + Nakili Kitambulisho cha Mwandishi + Nakili Kitambulisho cha Dokezo + Tangaza + Omba Kufutwa + Zuia / Ripoti + + Ripoti Barua Taka / Ulaghai + Ripoti Uigaji + Ripoti Maudhui Dhahiri + Ripoti Tabia Haramu + Ingia kwa kutumia Ufunguo wa Faragha ili uweze kujibu + Ingia kwa kutumia ufunguo wa Faragha ili uweze kuboresha machapisho + Ingia kwa kutumia ufunguo wa Faragha ili kupenda Machapisho + Hakuna Usanidi wa Kiasi cha Zap. Bonyeza kwa Muda mrefu ili kubadilisha + Ingia kwa kutumia ufunguo wa Faragha ili uweze kutuma Zaps + Ingia kwa kutumia Ufunguo wa Faragha ili uweze Kufuata + Ingia kwa kutumia Ufunguo wa Faragha ili uweze Kuacha Kufuata + Zaps + Idadi ya maoni + Kuongeza + imeimarishwa + Nukuu + Kiasi Kipya kwa Sats + Ongeza + "kujibu " + " na " + "katika chaneli " + Bango la Wasifu + Malipo Yamefaulu + Hitilafu katika kuchanganua ujumbe wa hitilafu + " Kufuatia" + Mtumiaji hana anwani ya umeme iliyowekwa ili kupokea sati + "jibu hapa.. " + Hunakili Kitambulisho cha Dokezo kwenye ubao wa kunakili ili kushiriki katika Nostr + Nakili Kitambulisho cha Kituo (Kumbuka) kwenye Ubao wa kunakili + Huhariri Metadata ya Kituo + Jiunge + Inajulikana + Maombi Mapya + Watumiaji Waliozuiwa + Nyuzi Mpya + Mazungumzo + Vidokezo + Majibu + "Anafuata" + diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 292521640..911d09c6a 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -283,6 +283,7 @@ 所有关注 全球 Orbot Socks 端口 + 无效端口 使用 Orbot 断开 Tor/Orbot 连接 私信 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index eaa2a1c33..9bc465cc5 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -233,8 +233,10 @@ 添加視頻 添加文件 添加到消息 + 內容描述 打閃種類 公開 + 私人 匿名 非打閃 Tor/Orbot 設置 @@ -242,6 +244,32 @@ 斷開與你的 Orbot/Tor 連接? + 關注列表 + 所有關注 + 全球 + Orbot Socks 端口 + 無效端口 + 使用 Orbot + 斷連 Tor/Orbot + 私信 + npub,、nevent 或 hex + 創建 + 加入 + 今天 + 內容警告 + 推薦: + 版本 + 軟體 + 聯絡 + 限制 + 國家 + 語言 + 標籤 + 訂閱 + 認證 + 付款 + 兌換 + 未設定閃電地址 直播 離線 已結束 @@ -250,6 +278,8 @@ 中繼器 直播 社群 + 聊天 + 敏感內容 設置 始終 永不 @@ -261,5 +291,23 @@ 圖像預覽 鏈接預覽 加載圖像 + Nostr 地址已驗證 + Nostr 地址驗證失敗 + 正在檢查 Nostr 地址 默認 + 將打閃轉發到: + 新功能 + 啟用 + 公開 + 私人 + + 主題 + 何時加載圖像 + 將 URL 複製到剪貼簿 + 將筆記 ID 複製到剪貼簿 + 創建於 + 規則 + 更新你的狀態 + 無法發送打閃 + 確定 From 1465ce112c964bb1b9d8b1fde879a98b03177b9f Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Thu, 7 Sep 2023 11:11:10 -0400 Subject: [PATCH 11/21] Refines relay icon row compose --- .../vitorpamplona/amethyst/model/Account.kt | 4 ++ .../com/vitorpamplona/amethyst/model/Note.kt | 13 ++++ .../amethyst/ui/actions/NewMediaView.kt | 10 +-- .../amethyst/ui/actions/NewPostView.kt | 10 +-- .../ui/actions/RelaySelectionDialog.kt | 27 +++----- .../amethyst/ui/note/NoteCompose.kt | 11 ++-- .../amethyst/ui/note/RelayListBox.kt | 61 +++---------------- .../amethyst/ui/note/RelayListRow.kt | 26 +++----- .../ui/screen/loggedIn/VideoScreen.kt | 9 +-- 9 files changed, 59 insertions(+), 112 deletions(-) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt index 04851d456..2d4199ea6 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt @@ -1686,6 +1686,10 @@ class Account( .toTypedArray() } + fun activeWriteRelays(): List { + return (activeRelays() ?: convertLocalRelays()).filter { it.write } + } + fun reconnectIfRelaysHaveChanged() { val newRelaySet = activeRelays() ?: convertLocalRelays() if (!Client.isSameRelaySetConfig(newRelaySet)) { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/Note.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/Note.kt index 63788310a..35ebae885 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/Note.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/Note.kt @@ -734,6 +734,12 @@ class NoteLiveSet(u: Note) { it.note.boosts.toImmutableList() }.distinctUntilChanged() + val relayInfo = innerRelays.map { + it.note.relays.map { + RelayBriefInfo(it) + }.toImmutableList() + } + fun isInUse(): Boolean { return metadata.hasObservers() || reactions.hasObservers() || @@ -812,3 +818,10 @@ class NoteLoadingLiveData(val note: Note, initialValue: Y?) : MediatorLiveDat @Immutable class NoteState(val note: Note) + +@Immutable +data class RelayBriefInfo( + val url: String, + val displayUrl: String = url.trim().removePrefix("wss://").removePrefix("ws://").removeSuffix("/").intern(), + val favIcon: String = "https://$displayUrl/favicon.ico".intern() +) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaView.kt index b4344132c..9a9900b2c 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaView.kt @@ -79,13 +79,7 @@ fun NewMediaView(uri: Uri, onClose: () -> Unit, postViewModel: NewMediaModel, ac var showRelaysDialog by remember { mutableStateOf(false) } - var relayList = account.activeRelays()?.filter { - it.write - }?.map { - it - } ?: account.convertLocalRelays().filter { - it.write - } + var relayList = account.activeWriteRelays() Dialog( onDismissRequest = { onClose() }, @@ -101,7 +95,7 @@ fun NewMediaView(uri: Uri, onClose: () -> Unit, postViewModel: NewMediaModel, ac ) { if (showRelaysDialog) { RelaySelectionDialog( - list = relayList, + preSelectedList = relayList, onClose = { showRelaysDialog = false }, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostView.kt index fc43a4293..5e6bdacb5 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostView.kt @@ -133,13 +133,7 @@ fun NewPostView( var showRelaysDialog by remember { mutableStateOf(false) } - var relayList = account.activeRelays()?.filter { - it.write - }?.map { - it - } ?: account.convertLocalRelays().filter { - it.write - } + var relayList = account.activeWriteRelays() LaunchedEffect(Unit) { postViewModel.load(account, baseReplyTo, quote) @@ -169,7 +163,7 @@ fun NewPostView( ) { if (showRelaysDialog) { RelaySelectionDialog( - list = relayList, + preSelectedList = relayList, onClose = { showRelaysDialog = false }, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/RelaySelectionDialog.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/RelaySelectionDialog.kt index e2d4af042..b979dc2fe 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/RelaySelectionDialog.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/RelaySelectionDialog.kt @@ -29,6 +29,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.DialogProperties import com.vitorpamplona.amethyst.R +import com.vitorpamplona.amethyst.model.RelayBriefInfo import com.vitorpamplona.amethyst.model.RelayInformation import com.vitorpamplona.amethyst.service.relays.Relay import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel @@ -36,12 +37,13 @@ import kotlinx.coroutines.launch data class RelayList( val relay: Relay, + val relayInfo: RelayBriefInfo, val isSelected: Boolean ) @Composable fun RelaySelectionDialog( - list: List, + preSelectedList: List, onClose: () -> Unit, onPost: (list: List) -> Unit, accountViewModel: AccountViewModel, @@ -49,20 +51,14 @@ fun RelaySelectionDialog( ) { val scope = rememberCoroutineScope() val context = LocalContext.current - val relayList = accountViewModel.account.activeRelays()?.filter { - it.write - }?.map { - it - } ?: accountViewModel.account.convertLocalRelays().filter { - it.write - } var relays by remember { mutableStateOf( - relayList.map { + accountViewModel.account.activeWriteRelays().map { RelayList( - it, - list.any { relay -> it.url == relay.url } + relay = it, + relayInfo = RelayBriefInfo(it.url), + isSelected = preSelectedList.any { relay -> it.url == relay.url } ) } ) @@ -115,14 +111,14 @@ fun RelaySelectionDialog( } ) - PostButton( + SaveButton( onPost = { val selectedRelays = relays.filter { it.isSelected } if (selectedRelays.isEmpty()) { scope.launch { Toast.makeText(context, context.getString(R.string.select_a_relay_to_continue), Toast.LENGTH_SHORT).show() } - return@PostButton + return@SaveButton } onPost(selectedRelays.map { it.relay }) onClose() @@ -153,10 +149,7 @@ fun RelaySelectionDialog( key = { _, item -> item.relay.url } ) { index, item -> RelaySwitch( - text = item.relay.url - .removePrefix("ws://") - .removePrefix("wss://") - .removeSuffix("/"), + text = item.relayInfo.displayUrl, checked = item.isSelected, onClick = { relays = relays.mapIndexed { j, item -> diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt index 66aaa30c1..0ce393707 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt @@ -82,6 +82,7 @@ import com.vitorpamplona.amethyst.model.Channel import com.vitorpamplona.amethyst.model.ConnectivityType import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.Note +import com.vitorpamplona.amethyst.model.RelayBriefInfo import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.service.OnlineChecker import com.vitorpamplona.amethyst.service.ReverseGeoLocationUtil @@ -1624,9 +1625,9 @@ fun DisplayRelaySet( ) { val noteEvent = baseNote.event as? RelaySetEvent ?: return - val relays by remember { - mutableStateOf>( - noteEvent.relays().toImmutableList() + val relays by remember(baseNote) { + mutableStateOf( + noteEvent.relays().map { RelayBriefInfo(it) }.toImmutableList() ) } @@ -1681,7 +1682,7 @@ fun DisplayRelaySet( toMembersShow.forEach { relay -> Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = CenterVertically) { Text( - relay.trim().removePrefix("wss://").removePrefix("ws://").removeSuffix("/"), + text = relay.displayUrl, fontWeight = FontWeight.Bold, maxLines = 1, overflow = TextOverflow.Ellipsis, @@ -1691,7 +1692,7 @@ fun DisplayRelaySet( ) Column(modifier = Modifier.padding(start = 10.dp)) { - RelayOptionsAction(relay, accountViewModel, nav) + RelayOptionsAction(relay.url, accountViewModel, nav) } } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/RelayListBox.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/RelayListBox.kt index 1796f8171..58bac1021 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/RelayListBox.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/RelayListBox.kt @@ -11,17 +11,16 @@ import androidx.compose.material.MaterialTheme import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ExpandMore import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.Stable +import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import com.vitorpamplona.amethyst.model.Note -import com.vitorpamplona.amethyst.ui.screen.equalImmutableLists +import com.vitorpamplona.amethyst.model.RelayBriefInfo import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer import com.vitorpamplona.amethyst.ui.theme.ShowMoreRelaysButtonBoxModifer @@ -29,80 +28,40 @@ import com.vitorpamplona.amethyst.ui.theme.ShowMoreRelaysButtonIconButtonModifie import com.vitorpamplona.amethyst.ui.theme.ShowMoreRelaysButtonIconModifier import com.vitorpamplona.amethyst.ui.theme.placeholderText import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch @Composable public fun RelayBadges(baseNote: Note, accountViewModel: AccountViewModel, nav: (String) -> Unit) { var expanded by remember { mutableStateOf(false) } - var showShowMore by remember { mutableStateOf(false) } - var lazyRelayList by remember { - val baseNumber = baseNote.relays.map { - it.removePrefix("wss://").removePrefix("ws://") - }.toImmutableList() - - mutableStateOf(baseNumber) - } - var shortRelayList by remember { - mutableStateOf(lazyRelayList.take(3).toImmutableList()) - } - - val scope = rememberCoroutineScope() - - WatchRelayLists(baseNote) { relayList -> - if (!equalImmutableLists(relayList, lazyRelayList)) { - scope.launch(Dispatchers.Main) { - lazyRelayList = relayList - shortRelayList = relayList.take(3).toImmutableList() - } - } - - val nextShowMore = relayList.size > 3 - if (nextShowMore != showShowMore) { - scope.launch(Dispatchers.Main) { - // only triggers recomposition when actually different - showShowMore = nextShowMore - } + val relayList by baseNote.live().relayInfo.observeAsState(persistentListOf()) + val shortRelayList by remember { + derivedStateOf { + relayList.take(3).toImmutableList() } } Spacer(DoubleVertSpacer) if (expanded) { - VerticalRelayPanelWithFlow(lazyRelayList, accountViewModel, nav) + VerticalRelayPanelWithFlow(relayList, accountViewModel, nav) } else { VerticalRelayPanelWithFlow(shortRelayList, accountViewModel, nav) } - if (showShowMore && !expanded) { + if (relayList.size > 3 && !expanded) { ShowMoreRelaysButton { expanded = true } } } -@Composable -private fun WatchRelayLists(baseNote: Note, onListChanges: (ImmutableList) -> Unit) { - val noteRelaysState by baseNote.live().relays.observeAsState() - - LaunchedEffect(key1 = noteRelaysState) { - launch(Dispatchers.IO) { - val relayList = noteRelaysState?.note?.relays?.map { - it.removePrefix("wss://").removePrefix("ws://") - } ?: emptyList() - - onListChanges(relayList.toImmutableList()) - } - } -} - @OptIn(ExperimentalLayoutApi::class) @Composable @Stable private fun VerticalRelayPanelWithFlow( - relays: ImmutableList, + relays: ImmutableList, accountViewModel: AccountViewModel, nav: (String) -> Unit ) { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/RelayListRow.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/RelayListRow.kt index 4c738c091..8c3736791 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/RelayListRow.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/RelayListRow.kt @@ -17,7 +17,6 @@ import androidx.compose.material.icons.filled.ChevronRight import androidx.compose.material.ripple.rememberRipple import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState -import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.mutableStateOf @@ -33,6 +32,7 @@ import androidx.compose.ui.unit.dp import androidx.lifecycle.map import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.Note +import com.vitorpamplona.amethyst.model.RelayBriefInfo import com.vitorpamplona.amethyst.model.RelayInformation import com.vitorpamplona.amethyst.ui.actions.RelayInformationDialog import com.vitorpamplona.amethyst.ui.actions.loadRelayInfo @@ -44,6 +44,7 @@ import com.vitorpamplona.amethyst.ui.theme.Size15Modifier import com.vitorpamplona.amethyst.ui.theme.Size15dp import com.vitorpamplona.amethyst.ui.theme.StdStartPadding import com.vitorpamplona.amethyst.ui.theme.placeholderText +import kotlinx.collections.immutable.toImmutableList @Composable public fun RelayBadgesHorizontal(baseNote: Note, accountViewModel: AccountViewModel, nav: (String) -> Unit) { @@ -59,15 +60,13 @@ public fun RelayBadgesHorizontal(baseNote: Note, accountViewModel: AccountViewMo @OptIn(ExperimentalLayoutApi::class) @Composable fun RenderRelayList(baseNote: Note, expanded: MutableState, accountViewModel: AccountViewModel, nav: (String) -> Unit) { - val noteRelays by baseNote.live().relays.map { - it.note.relays - }.observeAsState(baseNote.relays) + val noteRelays by baseNote.live().relayInfo.observeAsState() FlowRow(StdStartPadding) { val relaysToDisplay = remember(noteRelays, expanded.value) { - if (expanded.value) noteRelays else noteRelays.take(3) + if (expanded.value) noteRelays else noteRelays?.take(3)?.toImmutableList() } - relaysToDisplay.forEach { + relaysToDisplay?.forEach { RenderRelay(it, accountViewModel, nav) } } @@ -104,14 +103,7 @@ fun ChatRelayExpandButton(onClick: () -> Unit) { } @Composable -fun RenderRelay(dirtyUrl: String, accountViewModel: AccountViewModel, nav: (String) -> Unit) { - val iconUrl by remember(dirtyUrl) { - derivedStateOf { - val cleanUrl = dirtyUrl.trim().removePrefix("wss://").removePrefix("ws://").removeSuffix("/") - "https://$cleanUrl/favicon.ico" - } - } - +fun RenderRelay(relay: RelayBriefInfo, accountViewModel: AccountViewModel, nav: (String) -> Unit) { var relayInfo: RelayInformation? by remember { mutableStateOf(null) } if (relayInfo != null) { @@ -130,7 +122,7 @@ fun RenderRelay(dirtyUrl: String, accountViewModel: AccountViewModel, nav: (Stri val interactionSource = remember { MutableInteractionSource() } val ripple = rememberRipple(bounded = false, radius = Size15dp) - val clickableModifier = remember(dirtyUrl) { + val clickableModifier = remember(relay) { Modifier .padding(1.dp) .size(Size15dp) @@ -139,7 +131,7 @@ fun RenderRelay(dirtyUrl: String, accountViewModel: AccountViewModel, nav: (Stri interactionSource = interactionSource, indication = ripple, onClick = { - loadRelayInfo(dirtyUrl, context, scope) { + loadRelayInfo(relay.url, context, scope) { relayInfo = it } } @@ -149,7 +141,7 @@ fun RenderRelay(dirtyUrl: String, accountViewModel: AccountViewModel, nav: (Stri Box( modifier = clickableModifier ) { - RenderRelayIcon(iconUrl) + RenderRelayIcon(relay.favIcon) } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/VideoScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/VideoScreen.kt index 615b06c11..9073c78ad 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/VideoScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/VideoScreen.kt @@ -416,14 +416,11 @@ private fun VideoUserOptionAction( @OptIn(ExperimentalLayoutApi::class) @Composable private fun RelayBadges(baseNote: Note, accountViewModel: AccountViewModel, nav: (String) -> Unit) { - val noteRelaysState by baseNote.live().relays.observeAsState() - val noteRelays = remember(noteRelaysState) { - noteRelaysState?.note?.relays ?: emptySet() - } + val noteRelays by baseNote.live().relayInfo.observeAsState() FlowRow() { - noteRelays.forEach { dirtyUrl -> - RenderRelay(dirtyUrl, accountViewModel, nav) + noteRelays?.forEach { relayInfo -> + RenderRelay(relayInfo, accountViewModel, nav) } } } From 3085353729eeadd9e04596b9ca2e1a9b9e6f6299 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Thu, 7 Sep 2023 12:51:55 -0400 Subject: [PATCH 12/21] Adds SeenOn relays when successfully broadcasting a note. --- .../com/vitorpamplona/amethyst/service/NostrDataSource.kt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrDataSource.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrDataSource.kt index 4b230adee..373b8aa65 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrDataSource.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrDataSource.kt @@ -69,6 +69,9 @@ abstract class NostrDataSource(val debugName: String) { } override fun onSendResponse(eventId: String, success: Boolean, message: String, relay: Relay) { + if (success) { + markAsSeenOnRelay(eventId, relay) + } } override fun onAuth(relay: Relay, challenge: String) { @@ -182,6 +185,10 @@ abstract class NostrDataSource(val debugName: String) { LocalCache.verifyAndConsume(event, relay) } + open fun markAsSeenOnRelay(eventId: String, relay: Relay) { + LocalCache.getNoteIfExists(eventId)?.addRelay(relay) + } + abstract fun updateChannelFilters() open fun auth(relay: Relay, challenge: String) = Unit } From 8eb89f0fe95e7cd5b0c7e037d107682fe1404069 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Thu, 7 Sep 2023 12:52:44 -0400 Subject: [PATCH 13/21] Adds nostr.wine and noswhere.com as two new NIP-50 relays. --- .../vitorpamplona/amethyst/model/Account.kt | 20 +++++++++++-------- .../amethyst/service/relays/Constants.kt | 16 ++++++++++----- .../ui/actions/NewRelayListViewModel.kt | 12 ++++++----- 3 files changed, 30 insertions(+), 18 deletions(-) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt index 2d4199ea6..1f112ba0f 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt @@ -1661,14 +1661,18 @@ class Account( // Ugly, but forces nostr.band as the only search-supporting relay today. // TODO: Remove when search becomes more available. - if (usersRelayList.none { it.activeTypes.contains(FeedType.SEARCH) }) { - usersRelayList = usersRelayList + Relay( - Constants.forcedRelayForSearch.url, - Constants.forcedRelayForSearch.read, - Constants.forcedRelayForSearch.write, - Constants.forcedRelayForSearch.feedTypes, - proxy - ) + val searchRelays = usersRelayList.filter { it.url.removeSuffix("/") in Constants.forcedRelaysForSearchSet } + val hasSearchRelay = usersRelayList.any { it.activeTypes.contains(FeedType.SEARCH) } + if (!hasSearchRelay && searchRelays.isEmpty()) { + usersRelayList = usersRelayList + Constants.forcedRelayForSearch.map { + Relay( + it.url, + it.read, + it.write, + it.feedTypes, + proxy + ) + } } return usersRelayList.toTypedArray() diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/relays/Constants.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/relays/Constants.kt index d62179f8d..186bc3db5 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/relays/Constants.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/relays/Constants.kt @@ -16,9 +16,7 @@ object Constants { } val defaultRelays = arrayOf( - // Free relays for DMs and Follows - RelaySetupInfo("wss://no.str.cr", read = true, write = true, feedTypes = activeTypes), - RelaySetupInfo("wss://relay.snort.social", read = true, write = true, feedTypes = activeTypes), + // Free relays for only DMs and Follows due to the amount of spam RelaySetupInfo("wss://relay.damus.io", read = true, write = true, feedTypes = activeTypes), // Chats @@ -40,6 +38,7 @@ object Constants { // NewRelayListViewModel.Relay("wss://brb.io", read = true, write = true, feedTypes = activeTypes), // Paid relays + RelaySetupInfo("wss://relay.snort.social", read = true, write = false, feedTypes = activeTypesGlobalChats), RelaySetupInfo("wss://relay.nostr.com.au", read = true, write = false, feedTypes = activeTypesGlobalChats), RelaySetupInfo("wss://eden.nostr.land", read = true, write = false, feedTypes = activeTypesGlobalChats), RelaySetupInfo("wss://nostr.milou.lol", read = true, write = false, feedTypes = activeTypesGlobalChats), @@ -51,8 +50,15 @@ object Constants { RelaySetupInfo("wss://relay.nostrati.com", read = true, write = false, feedTypes = activeTypesGlobalChats), // Supporting NIP-50 - RelaySetupInfo("wss://relay.nostr.band", read = true, write = false, feedTypes = activeTypesSearch) + RelaySetupInfo("wss://relay.nostr.band", read = true, write = false, feedTypes = activeTypesSearch), + RelaySetupInfo("wss://filter.nostr.wine", read = true, write = false, feedTypes = activeTypesSearch), + RelaySetupInfo("wss://relay.noswhere.com", read = true, write = false, feedTypes = activeTypesSearch) ) - val forcedRelayForSearch = RelaySetupInfo("wss://relay.nostr.band", read = true, write = false, feedTypes = activeTypesSearch) + val forcedRelayForSearch = arrayOf( + RelaySetupInfo("wss://relay.nostr.band", read = true, write = false, feedTypes = activeTypesSearch), + RelaySetupInfo("wss://filter.nostr.wine", read = true, write = false, feedTypes = activeTypesSearch), + RelaySetupInfo("wss://relay.noswhere.com", read = true, write = false, feedTypes = activeTypesSearch) + ) + val forcedRelaysForSearchSet = forcedRelayForSearch.map { it.url } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewRelayListViewModel.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewRelayListViewModel.kt index ef787703a..f6f03f0fe 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewRelayListViewModel.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewRelayListViewModel.kt @@ -42,11 +42,13 @@ class NewRelayListViewModel : ViewModel() { // Ugly, but forces nostr.band as the only search-supporting relay today. // TODO: Remove when search becomes more available. - if (relayFile?.none { it.key == Constants.forcedRelayForSearch.url } == true) { - relayFile = relayFile + Pair( - Constants.forcedRelayForSearch.url, - ContactListEvent.ReadWrite(Constants.forcedRelayForSearch.read, Constants.forcedRelayForSearch.write) - ) + if (relayFile?.none { it.key.removeSuffix("/") in Constants.forcedRelaysForSearchSet } == true) { + relayFile = relayFile + Constants.forcedRelayForSearch.map { + Pair( + it.url, + ContactListEvent.ReadWrite(it.read, it.write) + ) + } } if (relayFile != null) { From 190b7fe8197c896af66089c34827c084da9bfcdb Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Thu, 7 Sep 2023 12:53:19 -0400 Subject: [PATCH 14/21] Using the correct observer for picture profile in the Top bar. --- .../com/vitorpamplona/amethyst/ui/navigation/AppTopBar.kt | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppTopBar.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppTopBar.kt index 406eeca5c..a9793d2d4 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppTopBar.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppTopBar.kt @@ -451,8 +451,6 @@ fun GenericMainTopBar( nav: (String) -> Unit, content: @Composable (AccountViewModel) -> Unit ) { - val coroutineScope = rememberCoroutineScope() - Column(modifier = BottomTopHeight) { MyTopAppBar( elevation = 0.dp, @@ -476,6 +474,7 @@ fun GenericMainTopBar( } }, navigationIcon = { + val coroutineScope = rememberCoroutineScope() LoggedInUserPictureDrawer(accountViewModel) { coroutineScope.launch { scaffoldState.drawerState.open() @@ -506,10 +505,9 @@ private fun LoggedInUserPictureDrawer( accountViewModel: AccountViewModel, onClick: () -> Unit ) { - val accountUserState by accountViewModel.account.userProfile().live().metadata.observeAsState() + val profilePicture by accountViewModel.account.userProfile().live().profilePictureChanges.observeAsState() - val pubkeyHex = remember { accountUserState?.user?.pubkeyHex ?: "" } - val profilePicture = remember(accountUserState) { accountUserState?.user?.profilePicture() } + val pubkeyHex = remember { accountViewModel.userProfile().pubkeyHex } IconButton( onClick = onClick From 44b4b0cdb9c0f4a5b28f43cfbc95679270da1dfa Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Thu, 7 Sep 2023 14:40:42 -0400 Subject: [PATCH 15/21] Makes sure only valid lnurl withdraws get highlighted. --- .../java/com/vitorpamplona/quartz/encoders/LnWithdrawalUtil.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/encoders/LnWithdrawalUtil.kt b/quartz/src/main/java/com/vitorpamplona/quartz/encoders/LnWithdrawalUtil.kt index bedf72d65..c93209059 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/encoders/LnWithdrawalUtil.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/encoders/LnWithdrawalUtil.kt @@ -4,7 +4,7 @@ import java.util.regex.Pattern object LnWithdrawalUtil { private val withdrawalPattern = Pattern.compile( - "lnurl.+", + "lnurl1[02-9ac-hj-np-z]+", Pattern.CASE_INSENSITIVE ) From da97b5593b4c7a92ed51b26b859522a77ee9de2d Mon Sep 17 00:00:00 2001 From: Crowdin Bot Date: Thu, 7 Sep 2023 18:42:45 +0000 Subject: [PATCH 16/21] New Crowdin translations by GitHub Action --- app/src/main/res/values-zh-rCN/strings.xml | 33 ++++++++++++++++++++++ app/src/main/res/values-zh-rTW/strings.xml | 2 ++ 2 files changed, 35 insertions(+) diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 911d09c6a..bd6a9a69b 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -252,6 +252,9 @@ 显示私钥 nsec / 十六进制私钥 聪数量 + 必填字段: + 打闪接收人 + 选项 %s 可选字段: 打闪最低金额 打闪最高金额 @@ -260,6 +263,7 @@ 打闪金额 "“正在查找事件%1$s”" 添加公开消息 + 添加私信 添加发票消息 创建并添加 添加图片 @@ -269,11 +273,14 @@ 内容描述 一艘蓝色船,位于白沙滩的日落点 打闪种类 + 所有选项的打闪类型 公开 所有人都能看到交易和消息 + 私人 发送方和接收方能互相看到并读取消息 匿名 非打闪 + 你的中继器 (NIP-95) Tor/Orbot 设置 通过你的 Orbot 设置连接 断开与你的 Orbot/Tor 连接? @@ -290,7 +297,9 @@ 收到私信时通知你 收到打闪 收到打闪时通知你 + %1$s聪 通知: + 加入对话 用户或群组 ID npub、nevent 或 hex 创建 @@ -302,6 +311,10 @@ 始终显示内容警告 推荐: 过滤来自陌生人的垃圾信息 + 新反应符号 + Zapraiser + 目标聪金额 + Zapraiser 位于 %1$s。距离目标%2$s聪 从中继器读取 写入到继电器 版本 @@ -317,6 +330,7 @@ 消息长度 订阅 筛选器 + 订阅 ID 长度 内容长度 认证 支付 @@ -333,6 +347,7 @@ 直播 社区 聊天 + 批准帖子 敏感内容 设置 始终 @@ -341,16 +356,33 @@ 系统 浅色 深色 + 应用程序首选项 语言 主题 图像预览 + 视频播放 + URL 预览 加载图像 + 静音。点击取消静音 + 声音开启。点击静音 Nostr 地址已验证 Nostr 地址验证失败 正在检查 Nostr 地址 默认 + 选择中继器以继续 + 将打闪转发到: + 将位置显示为 + 新功能 + 启用 公开 + 私人 + + 主题 + 对话主题 + "\@User1、@User2、@User3" 此群组成员 + 对成员的解释 + 用于应用程序界面 暗色、亮色或系统主题 自动加载图像和 GIF 自动播放视频和 GIF @@ -361,6 +393,7 @@ 创建于 规则 更新你的状态 + 错误解析错误消息 无法发送打闪 向用户发送消息 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 9bc465cc5..6fdb0b002 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -252,6 +252,8 @@ 使用 Orbot 斷連 Tor/Orbot 私信 + %1$s聰 + 來自%1$s npub,、nevent 或 hex 創建 加入 From c2a4c9bbb319335221c517f511b7a6ec63efca23 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Thu, 7 Sep 2023 19:07:33 -0400 Subject: [PATCH 17/21] Improvements to the relay settings page. --- .../amethyst/model/RelaySetupInfo.kt | 4 +- .../amethyst/service/Nip11Retriever.kt | 103 ++ .../amethyst/ui/actions/NewRelayListView.kt | 928 ++++++++++-------- .../ui/actions/NewRelayListViewModel.kt | 17 + .../ui/actions/RelayInformationDialog.kt | 142 +-- .../ui/actions/RelaySelectionDialog.kt | 44 +- .../amethyst/ui/note/RelayListRow.kt | 41 +- .../ui/screen/loggedIn/AccountViewModel.kt | 13 + .../vitorpamplona/amethyst/ui/theme/Color.kt | 6 + .../vitorpamplona/amethyst/ui/theme/Theme.kt | 6 + app/src/main/res/values/strings.xml | 12 + 11 files changed, 787 insertions(+), 529 deletions(-) create mode 100644 app/src/main/java/com/vitorpamplona/amethyst/service/Nip11Retriever.kt diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/RelaySetupInfo.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/RelaySetupInfo.kt index c9f099ed6..18782e29f 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/RelaySetupInfo.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/RelaySetupInfo.kt @@ -14,4 +14,6 @@ data class RelaySetupInfo( val spamCount: Int = 0, val feedTypes: Set, val paidRelay: Boolean = false -) +) { + val briefInfo: RelayBriefInfo = RelayBriefInfo(url) +} diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/Nip11Retriever.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/Nip11Retriever.kt new file mode 100644 index 000000000..58e88e207 --- /dev/null +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/Nip11Retriever.kt @@ -0,0 +1,103 @@ +package com.vitorpamplona.amethyst.service + +import android.util.Log +import android.util.LruCache +import com.vitorpamplona.amethyst.model.RelayInformation +import okhttp3.Call +import okhttp3.Callback +import okhttp3.Request +import okhttp3.Response +import java.io.IOException + +object Nip11CachedRetriever { + val relayInformationDocumentCache = LruCache(100) + val retriever = Nip11Retriever() + + suspend fun loadRelayInfo( + dirtyUrl: String, + onInfo: (RelayInformation) -> Unit, + onError: (String, Nip11Retriever.ErrorCode, String?) -> Unit + ) { + val url = retriever.cleanUrl(dirtyUrl) + val doc = relayInformationDocumentCache.get(url) + + if (doc != null) { + onInfo(doc) + } else { + Nip11Retriever().loadRelayInfo( + url, + dirtyUrl, + onInfo = { + relayInformationDocumentCache.put(url, it) + onInfo(it) + }, + onError + ) + } + } +} + +class Nip11Retriever { + enum class ErrorCode { + FAIL_TO_ASSEMBLE_URL, + FAIL_TO_REACH_SERVER, + FAIL_TO_PARSE_RESULT, + FAIL_WITH_HTTP_STATUS + } + + fun cleanUrl(dirtyUrl: String): String { + return if (dirtyUrl.contains("://")) { + dirtyUrl + .replace("wss://", "https://") + .replace("ws://", "http://") + } else { + "https://$dirtyUrl" + } + } + + suspend fun loadRelayInfo( + url: String, + dirtyUrl: String, + onInfo: (RelayInformation) -> Unit, + onError: (String, ErrorCode, String?) -> Unit + ) { + try { + val request: Request = Request + .Builder() + .header("Accept", "application/nostr+json") + .url(url) + .build() + + HttpClient.getHttpClient() + .newCall(request) + .enqueue( + object : Callback { + override fun onResponse(call: Call, response: Response) { + checkNotInMainThread() + response.use { + val body = it.body.string() + try { + if (it.isSuccessful) { + onInfo(RelayInformation.fromJson(body)) + } else { + onError(dirtyUrl, ErrorCode.FAIL_WITH_HTTP_STATUS, it.code.toString()) + } + } catch (e: Exception) { + Log.e("RelayInfoFail", "Resulting Message from Relay $dirtyUrl in not parseable: $body", e) + onError(dirtyUrl, ErrorCode.FAIL_TO_PARSE_RESULT, e.message) + } + } + } + + override fun onFailure(call: Call, e: IOException) { + Log.e("RelayInfoFail", "$dirtyUrl unavailable", e) + onError(dirtyUrl, ErrorCode.FAIL_TO_REACH_SERVER, e.message) + } + } + ) + } catch (e: Exception) { + Log.e("RelayInfoFail", "Invalid URL $dirtyUrl", e) + onError(dirtyUrl, ErrorCode.FAIL_TO_ASSEMBLE_URL, e.message) + } + } +} diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewRelayListView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewRelayListView.kt index 3abfba9ef..1f58535f6 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewRelayListView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewRelayListView.kt @@ -1,6 +1,5 @@ package com.vitorpamplona.amethyst.ui.actions -import android.content.Context import android.widget.Toast import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.clickable @@ -50,24 +49,35 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.DialogProperties import androidx.lifecycle.viewmodel.compose.viewModel import com.vitorpamplona.amethyst.R -import com.vitorpamplona.amethyst.model.RelayInformation +import com.vitorpamplona.amethyst.model.RelayBriefInfo import com.vitorpamplona.amethyst.model.RelaySetupInfo +import com.vitorpamplona.amethyst.service.Nip11Retriever +import com.vitorpamplona.amethyst.service.relays.Constants import com.vitorpamplona.amethyst.service.relays.Constants.defaultRelays import com.vitorpamplona.amethyst.service.relays.FeedType +import com.vitorpamplona.amethyst.ui.note.RenderRelayIcon import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.theme.ButtonBorder import com.vitorpamplona.amethyst.ui.theme.Font14SP +import com.vitorpamplona.amethyst.ui.theme.HalfHorzPadding +import com.vitorpamplona.amethyst.ui.theme.HalfStartPadding +import com.vitorpamplona.amethyst.ui.theme.ReactionRowHeightChat +import com.vitorpamplona.amethyst.ui.theme.Size30Modifier import com.vitorpamplona.amethyst.ui.theme.Size35dp +import com.vitorpamplona.amethyst.ui.theme.Size55dp import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer +import com.vitorpamplona.amethyst.ui.theme.WarningColor +import com.vitorpamplona.amethyst.ui.theme.allGoodColor import com.vitorpamplona.amethyst.ui.theme.placeholderText -import kotlinx.coroutines.CoroutineScope +import com.vitorpamplona.amethyst.ui.theme.warningColor import kotlinx.coroutines.launch import java.lang.Math.round @@ -75,16 +85,9 @@ import java.lang.Math.round fun NewRelayListView(onClose: () -> Unit, accountViewModel: AccountViewModel, relayToAdd: String = "", nav: (String) -> Unit) { val postViewModel: NewRelayListViewModel = viewModel() val feedState by postViewModel.relays.collectAsState() - val context = LocalContext.current - val scope = rememberCoroutineScope() LaunchedEffect(Unit) { postViewModel.load(accountViewModel.account) - postViewModel.relays.value.forEach { item -> - loadRelayInfo(item.url, context, scope) { - postViewModel.togglePaidRelay(item, it.limitation?.payment_required ?: false) - } - } } Dialog( @@ -96,7 +99,9 @@ fun NewRelayListView(onClose: () -> Unit, accountViewModel: AccountViewModel, re TopAppBar( title = { Row( - modifier = Modifier.fillMaxWidth().padding(end = 10.dp), + modifier = Modifier + .fillMaxWidth() + .padding(end = 10.dp), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { @@ -108,11 +113,7 @@ fun NewRelayListView(onClose: () -> Unit, accountViewModel: AccountViewModel, re defaultRelays.forEach { postViewModel.addRelay(it) } - postViewModel.relays.value.forEach { item -> - loadRelayInfo(item.url, context, scope) { - postViewModel.togglePaidRelay(item, it.limitation?.payment_required ?: false) - } - } + postViewModel.loadRelayDocuments() } ) { Text(stringResource(R.string.default_relays)) @@ -139,8 +140,6 @@ fun NewRelayListView(onClose: () -> Unit, accountViewModel: AccountViewModel, re ) } ) { pad -> - val scope = rememberCoroutineScope() - Column( modifier = Modifier.padding( 16.dp, @@ -158,10 +157,6 @@ fun NewRelayListView(onClose: () -> Unit, accountViewModel: AccountViewModel, re ) ) { itemsIndexed(feedState, key = { _, item -> item.url }) { index, item -> - if (index == 0) { - ServerConfigHeader() - } - ServerConfig( item, onToggleDownload = { postViewModel.toggleDownload(it) }, @@ -175,9 +170,7 @@ fun NewRelayListView(onClose: () -> Unit, accountViewModel: AccountViewModel, re onDelete = { postViewModel.deleteRelay(it) }, accountViewModel = accountViewModel, - nav = nav, - scope = scope, - context = context + nav = nav ) } } @@ -261,13 +254,38 @@ fun ServerConfigHeader() { } } -@OptIn(ExperimentalFoundationApi::class) +@Preview +@Composable +fun ServerConfigPreview() { + ServerConfigClickableLine( + item = RelaySetupInfo( + url = "nostr.mom", + read = true, + write = true, + errorCount = 23, + downloadCountInBytes = 10000, + uploadCountInBytes = 10000000, + spamCount = 10, + feedTypes = Constants.activeTypesGlobalChats, + paidRelay = true + ), + onDelete = {}, + onToggleDownload = {}, + onToggleUpload = {}, + onToggleFollows = {}, + onTogglePrivateDMs = {}, + onTogglePublicChats = {}, + onToggleGlobal = {}, + onToggleSearch = {}, + onClick = {} + ) +} + @Composable fun ServerConfig( item: RelaySetupInfo, onToggleDownload: (RelaySetupInfo) -> Unit, onToggleUpload: (RelaySetupInfo) -> Unit, - onToggleFollows: (RelaySetupInfo) -> Unit, onTogglePrivateDMs: (RelaySetupInfo) -> Unit, onTogglePublicChats: (RelaySetupInfo) -> Unit, @@ -276,393 +294,112 @@ fun ServerConfig( onDelete: (RelaySetupInfo) -> Unit, accountViewModel: AccountViewModel, - nav: (String) -> Unit, - context: Context, - scope: CoroutineScope + nav: (String) -> Unit ) { - var relayInfo: RelayInformation? by remember { mutableStateOf(null) } + var relayInfo: RelayInfoDialog? by remember { mutableStateOf(null) } + val scope = rememberCoroutineScope() + val context = LocalContext.current - if (relayInfo != null) { + relayInfo?.let { RelayInformationDialog( - onClose = { - relayInfo = null - }, - relayInfo = relayInfo!!, - accountViewModel, - nav + onClose = { relayInfo = null }, + relayInfo = it.relayInfo, + relayBriefInfo = it.relayBriefInfo, + accountViewModel = accountViewModel, + nav = nav ) } + ServerConfigClickableLine( + item = item, + onToggleDownload = onToggleDownload, + onToggleUpload = onToggleUpload, + onToggleFollows = onToggleFollows, + onTogglePrivateDMs = onTogglePrivateDMs, + onTogglePublicChats = onTogglePublicChats, + onToggleGlobal = onToggleGlobal, + onToggleSearch = onToggleSearch, + onDelete = onDelete, + onClick = { + accountViewModel.retrieveRelayDocument( + item.url, + onInfo = { + relayInfo = RelayInfoDialog(RelayBriefInfo(item.url), it) + }, + onError = { url, errorCode, exceptionMessage -> + val msg = when (errorCode) { + Nip11Retriever.ErrorCode.FAIL_TO_ASSEMBLE_URL -> context.getString(R.string.relay_information_document_error_assemble_url, url, exceptionMessage) + Nip11Retriever.ErrorCode.FAIL_TO_REACH_SERVER -> context.getString(R.string.relay_information_document_error_assemble_url, url, exceptionMessage) + Nip11Retriever.ErrorCode.FAIL_TO_PARSE_RESULT -> context.getString(R.string.relay_information_document_error_assemble_url, url, exceptionMessage) + Nip11Retriever.ErrorCode.FAIL_WITH_HTTP_STATUS -> context.getString(R.string.relay_information_document_error_assemble_url, url, exceptionMessage) + } + + scope.launch { + Toast + .makeText( + context, + msg, + Toast.LENGTH_SHORT + ) + .show() + } + } + ) + } + ) +} + +@Composable +fun ServerConfigClickableLine( + item: RelaySetupInfo, + onToggleDownload: (RelaySetupInfo) -> Unit, + onToggleUpload: (RelaySetupInfo) -> Unit, + onToggleFollows: (RelaySetupInfo) -> Unit, + onTogglePrivateDMs: (RelaySetupInfo) -> Unit, + onTogglePublicChats: (RelaySetupInfo) -> Unit, + onToggleGlobal: (RelaySetupInfo) -> Unit, + onToggleSearch: (RelaySetupInfo) -> Unit, + onDelete: (RelaySetupInfo) -> Unit, + onClick: () -> Unit +) { Column(Modifier.fillMaxWidth()) { Row( verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(vertical = 5.dp) ) { - Column() { - IconButton( - modifier = Modifier.size(30.dp), - onClick = { onDelete(item) } - ) { - Icon( - imageVector = Icons.Default.Cancel, - null, - modifier = Modifier - .padding(end = 5.dp) - .size(15.dp), - tint = Color.Red - ) - } + Column(Modifier.clickable(onClick = onClick)) { + RenderRelayIcon(iconUrl = item.briefInfo.favIcon, Size55dp) } - Column(Modifier.weight(1f)) { - Row(verticalAlignment = Alignment.CenterVertically) { - if (item.paidRelay) { - Icon( - imageVector = Icons.Default.Paid, - null, - modifier = Modifier - .padding(end = 5.dp) - .size(14.dp), - tint = Color.Green - ) - } + Spacer(modifier = HalfHorzPadding) - Text( - text = item.url.removePrefix("wss://").removeSuffix("/"), - modifier = Modifier - .weight(1f) - .clickable { - loadRelayInfo(item.url, context, scope) { - relayInfo = it - } - }, - maxLines = 1, - overflow = TextOverflow.Ellipsis + Column(Modifier.weight(1f)) { + FirstLine(item, onClick, onDelete, ReactionRowHeightChat.fillMaxWidth()) + + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = ReactionRowHeightChat.fillMaxWidth() + ) { + RenderActiveToggles( + item = item, + onToggleFollows = onToggleFollows, + onTogglePrivateDMs = onTogglePrivateDMs, + onTogglePublicChats = onTogglePublicChats, + onToggleGlobal = onToggleGlobal, + onToggleSearch = onToggleSearch ) } - Row(verticalAlignment = Alignment.CenterVertically) { - Column(Modifier.weight(1f)) { - Row(verticalAlignment = Alignment.CenterVertically) { - IconButton( - modifier = Modifier - .size(30.dp), - onClick = { } - ) { - Icon( - painterResource(R.drawable.ic_home), - stringResource(R.string.home_feed), - modifier = Modifier - .padding(end = 5.dp) - .size(15.dp) - .combinedClickable( - onClick = { onToggleFollows(item) }, - onLongClick = { - scope.launch { - Toast - .makeText( - context, - context.getString(R.string.home_feed), - Toast.LENGTH_SHORT - ) - .show() - } - } - ), - tint = if (item.feedTypes.contains(FeedType.FOLLOWS)) { - Color.Green - } else { - MaterialTheme.colors.onSurface.copy( - alpha = 0.32f - ) - } - ) - } - IconButton( - modifier = Modifier.size(30.dp), - onClick = { } - ) { - Icon( - painterResource(R.drawable.ic_dm), - stringResource(R.string.private_message_feed), - modifier = Modifier - .padding(horizontal = 5.dp) - .size(15.dp) - .combinedClickable( - onClick = { onTogglePrivateDMs(item) }, - onLongClick = { - scope.launch { - Toast - .makeText( - context, - context.getString(R.string.private_message_feed), - Toast.LENGTH_SHORT - ) - .show() - } - } - ), - tint = if (item.feedTypes.contains(FeedType.PRIVATE_DMS)) { - Color.Green - } else { - MaterialTheme.colors.onSurface.copy( - alpha = 0.32f - ) - } - ) - } - IconButton( - modifier = Modifier.size(30.dp), - onClick = { } - ) { - Icon( - imageVector = Icons.Default.Groups, - stringResource(R.string.public_chat_feed), - modifier = Modifier - .padding(horizontal = 5.dp) - .size(15.dp) - .combinedClickable( - onClick = { onTogglePublicChats(item) }, - onLongClick = { - scope.launch { - Toast - .makeText( - context, - context.getString(R.string.public_chat_feed), - Toast.LENGTH_SHORT - ) - .show() - } - } - ), - tint = if (item.feedTypes.contains(FeedType.PUBLIC_CHATS)) { - Color.Green - } else { - MaterialTheme.colors.onSurface.copy( - alpha = 0.32f - ) - } - ) - } - IconButton( - modifier = Modifier.size(30.dp), - onClick = { } - ) { - Icon( - imageVector = Icons.Default.Public, - stringResource(R.string.global_feed), - modifier = Modifier - .padding(horizontal = 5.dp) - .size(15.dp) - .combinedClickable( - onClick = { onToggleGlobal(item) }, - onLongClick = { - scope.launch { - Toast - .makeText( - context, - context.getString(R.string.global_feed), - Toast.LENGTH_SHORT - ) - .show() - } - } - ), - tint = if (item.feedTypes.contains(FeedType.GLOBAL)) { - Color.Green - } else { - MaterialTheme.colors.onSurface.copy( - alpha = 0.32f - ) - } - ) - } - - IconButton( - modifier = Modifier.size(30.dp), - onClick = { onToggleSearch(item) } - ) { - Icon( - imageVector = Icons.Default.Search, - stringResource(R.string.search_feed), - modifier = Modifier - .padding(horizontal = 5.dp) - .size(15.dp) - .combinedClickable( - onClick = { onToggleSearch(item) }, - onLongClick = { - scope.launch { - Toast - .makeText( - context, - context.getString(R.string.search_feed), - Toast.LENGTH_SHORT - ) - .show() - } - } - ), - tint = if (item.feedTypes.contains(FeedType.SEARCH)) { - Color.Green - } else { - MaterialTheme.colors.onSurface.copy( - alpha = 0.32f - ) - } - ) - } - } - } - - Column(Modifier.weight(1.4f)) { - Row(verticalAlignment = Alignment.CenterVertically) { - IconButton( - modifier = Modifier.size(30.dp), - onClick = { } - ) { - Icon( - imageVector = Icons.Default.Download, - stringResource(R.string.read_from_relay), - modifier = Modifier - .padding(horizontal = 5.dp) - .size(15.dp) - .combinedClickable( - onClick = { onToggleDownload(item) }, - onLongClick = { - scope.launch { - Toast - .makeText( - context, - context.getString(R.string.read_from_relay), - Toast.LENGTH_SHORT - ) - .show() - } - } - ), - tint = if (item.read) { - Color.Green - } else { - MaterialTheme.colors.onSurface.copy( - alpha = 0.32f - ) - } - ) - } - - Text( - text = "${countToHumanReadable(item.downloadCountInBytes)}", - maxLines = 1, - fontSize = 12.sp, - modifier = Modifier.weight(1.2f), - color = MaterialTheme.colors.placeholderText - ) - - IconButton( - modifier = Modifier.size(30.dp), - onClick = { } - ) { - Icon( - imageVector = Icons.Default.Upload, - stringResource(R.string.write_to_relay), - modifier = Modifier - .padding(horizontal = 5.dp) - .size(15.dp) - .combinedClickable( - onClick = { onToggleUpload(item) }, - onLongClick = { - scope.launch { - Toast - .makeText( - context, - context.getString(R.string.write_to_relay), - Toast.LENGTH_SHORT - ) - .show() - } - } - ), - tint = if (item.write) { - Color.Green - } else { - MaterialTheme.colors.onSurface.copy( - alpha = 0.32f - ) - } - ) - } - - Text( - text = "${countToHumanReadable(item.uploadCountInBytes)}", - maxLines = 1, - fontSize = 12.sp, - modifier = Modifier.weight(1.2f), - color = MaterialTheme.colors.placeholderText - ) - - Icon( - imageVector = Icons.Default.SyncProblem, - stringResource(R.string.errors), - modifier = Modifier - .padding(horizontal = 5.dp) - .size(15.dp) - .combinedClickable( - onClick = { }, - onLongClick = { - scope.launch { - Toast - .makeText( - context, - context.getString(R.string.errors), - Toast.LENGTH_SHORT - ) - .show() - } - } - ), - tint = if (item.errorCount > 0) Color.Yellow else Color.Green - ) - - Text( - text = "${countToHumanReadable(item.errorCount)}", - maxLines = 1, - fontSize = 12.sp, - modifier = Modifier.weight(1f), - color = MaterialTheme.colors.placeholderText - ) - - Icon( - imageVector = Icons.Default.DeleteSweep, - stringResource(R.string.spam), - modifier = Modifier - .padding(horizontal = 5.dp) - .size(15.dp) - .combinedClickable( - onClick = { }, - onLongClick = { - scope.launch { - Toast - .makeText( - context, - context.getString(R.string.spam), - Toast.LENGTH_SHORT - ) - .show() - } - } - ), - tint = if (item.spamCount > 0) Color.Yellow else Color.Green - ) - - Text( - text = "${countToHumanReadable(item.spamCount)}", - maxLines = 1, - fontSize = 12.sp, - modifier = Modifier.weight(1f), - color = MaterialTheme.colors.placeholderText - ) - } - } + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = ReactionRowHeightChat.fillMaxWidth() + ) { + RenderStatusRow( + item = item, + onToggleDownload = onToggleDownload, + onToggleUpload = onToggleUpload, + modifier = HalfStartPadding.weight(1f) + ) } } } @@ -673,6 +410,384 @@ fun ServerConfig( } } +@Composable +@OptIn(ExperimentalFoundationApi::class) +private fun RenderStatusRow( + item: RelaySetupInfo, + onToggleDownload: (RelaySetupInfo) -> Unit, + onToggleUpload: (RelaySetupInfo) -> Unit, + modifier: Modifier +) { + val scope = rememberCoroutineScope() + val context = LocalContext.current + + Icon( + imageVector = Icons.Default.Download, + contentDescription = stringResource(R.string.read_from_relay), + modifier = Modifier + .size(15.dp) + .combinedClickable( + onClick = { onToggleDownload(item) }, + onLongClick = { + scope.launch { + Toast + .makeText( + context, + context.getString(R.string.read_from_relay), + Toast.LENGTH_SHORT + ) + .show() + } + } + ), + tint = if (item.read) { + MaterialTheme.colors.allGoodColor + } else { + MaterialTheme.colors.onSurface.copy( + alpha = 0.32f + ) + } + ) + + Text( + text = countToHumanReadableBytes(item.downloadCountInBytes), + maxLines = 1, + fontSize = 12.sp, + modifier = modifier, + color = MaterialTheme.colors.placeholderText + ) + + Icon( + imageVector = Icons.Default.Upload, + stringResource(R.string.write_to_relay), + modifier = Modifier + .size(15.dp) + .combinedClickable( + onClick = { onToggleUpload(item) }, + onLongClick = { + scope.launch { + Toast + .makeText( + context, + context.getString(R.string.write_to_relay), + Toast.LENGTH_SHORT + ) + .show() + } + } + ), + tint = if (item.write) { + MaterialTheme.colors.allGoodColor + } else { + MaterialTheme.colors.onSurface.copy( + alpha = 0.32f + ) + } + ) + + Text( + text = countToHumanReadableBytes(item.uploadCountInBytes), + maxLines = 1, + fontSize = 12.sp, + modifier = modifier, + color = MaterialTheme.colors.placeholderText + ) + + Icon( + imageVector = Icons.Default.SyncProblem, + stringResource(R.string.errors), + modifier = Modifier + .size(15.dp) + .combinedClickable( + onClick = { }, + onLongClick = { + scope.launch { + Toast + .makeText( + context, + context.getString(R.string.errors), + Toast.LENGTH_SHORT + ) + .show() + } + } + ), + tint = if (item.errorCount > 0) MaterialTheme.colors.warningColor else MaterialTheme.colors.allGoodColor + ) + + Text( + text = countToHumanReadable(item.errorCount, "errors"), + maxLines = 1, + fontSize = 12.sp, + modifier = modifier, + color = MaterialTheme.colors.placeholderText + ) + + Icon( + imageVector = Icons.Default.DeleteSweep, + stringResource(R.string.spam), + modifier = Modifier + .size(15.dp) + .combinedClickable( + onClick = { }, + onLongClick = { + scope.launch { + Toast + .makeText( + context, + context.getString(R.string.spam), + Toast.LENGTH_SHORT + ) + .show() + } + } + ), + tint = if (item.spamCount > 0) MaterialTheme.colors.warningColor else MaterialTheme.colors.allGoodColor + ) + + Text( + text = countToHumanReadable(item.spamCount, "spam"), + maxLines = 1, + fontSize = 12.sp, + modifier = modifier, + color = MaterialTheme.colors.placeholderText + ) +} + +@Composable +@OptIn(ExperimentalFoundationApi::class) +private fun RenderActiveToggles( + item: RelaySetupInfo, + onToggleFollows: (RelaySetupInfo) -> Unit, + onTogglePrivateDMs: (RelaySetupInfo) -> Unit, + onTogglePublicChats: (RelaySetupInfo) -> Unit, + onToggleGlobal: (RelaySetupInfo) -> Unit, + onToggleSearch: (RelaySetupInfo) -> Unit +) { + val scope = rememberCoroutineScope() + val context = LocalContext.current + + Text( + text = stringResource(id = R.string.active_for), + maxLines = 1, + overflow = TextOverflow.Ellipsis, + color = MaterialTheme.colors.placeholderText, + modifier = Modifier.padding(start = 2.dp, end = 5.dp), + fontSize = 14.sp + ) + + IconButton( + modifier = Size30Modifier, + onClick = { onToggleFollows(item) } + ) { + Icon( + painterResource(R.drawable.ic_home), + stringResource(R.string.home_feed), + modifier = Modifier + .padding(horizontal = 5.dp) + .size(15.dp) + .combinedClickable( + onClick = { onToggleFollows(item) }, + onLongClick = { + scope.launch { + Toast + .makeText( + context, + context.getString(R.string.home_feed), + Toast.LENGTH_SHORT + ) + .show() + } + } + ), + tint = if (item.feedTypes.contains(FeedType.FOLLOWS)) { + MaterialTheme.colors.allGoodColor + } else { + MaterialTheme.colors.onSurface.copy( + alpha = 0.32f + ) + } + ) + } + IconButton( + modifier = Size30Modifier, + onClick = { onTogglePrivateDMs(item) } + ) { + Icon( + painterResource(R.drawable.ic_dm), + stringResource(R.string.private_message_feed), + modifier = Modifier + .padding(horizontal = 5.dp) + .size(15.dp) + .combinedClickable( + onClick = { onTogglePrivateDMs(item) }, + onLongClick = { + scope.launch { + Toast + .makeText( + context, + context.getString(R.string.private_message_feed), + Toast.LENGTH_SHORT + ) + .show() + } + } + ), + tint = if (item.feedTypes.contains(FeedType.PRIVATE_DMS)) { + MaterialTheme.colors.allGoodColor + } else { + MaterialTheme.colors.onSurface.copy( + alpha = 0.32f + ) + } + ) + } + IconButton( + modifier = Size30Modifier, + onClick = { onTogglePublicChats(item) } + ) { + Icon( + imageVector = Icons.Default.Groups, + contentDescription = stringResource(R.string.public_chat_feed), + modifier = Modifier + .padding(horizontal = 5.dp) + .size(15.dp) + .combinedClickable( + onClick = { onTogglePublicChats(item) }, + onLongClick = { + scope.launch { + Toast + .makeText( + context, + context.getString(R.string.public_chat_feed), + Toast.LENGTH_SHORT + ) + .show() + } + } + ), + tint = if (item.feedTypes.contains(FeedType.PUBLIC_CHATS)) { + MaterialTheme.colors.allGoodColor + } else { + MaterialTheme.colors.onSurface.copy( + alpha = 0.32f + ) + } + ) + } + IconButton( + modifier = Size30Modifier, + onClick = { onToggleGlobal(item) } + ) { + Icon( + imageVector = Icons.Default.Public, + stringResource(R.string.global_feed), + modifier = Modifier + .padding(horizontal = 5.dp) + .size(15.dp) + .combinedClickable( + onClick = { onToggleGlobal(item) }, + onLongClick = { + scope.launch { + Toast + .makeText( + context, + context.getString(R.string.global_feed), + Toast.LENGTH_SHORT + ) + .show() + } + } + ), + tint = if (item.feedTypes.contains(FeedType.GLOBAL)) { + MaterialTheme.colors.allGoodColor + } else { + MaterialTheme.colors.onSurface.copy( + alpha = 0.32f + ) + } + ) + } + + IconButton( + modifier = Size30Modifier, + onClick = { onToggleSearch(item) } + ) { + Icon( + imageVector = Icons.Default.Search, + stringResource(R.string.search_feed), + modifier = Modifier + .padding(horizontal = 5.dp) + .size(15.dp) + .combinedClickable( + onClick = { onToggleSearch(item) }, + onLongClick = { + scope.launch { + Toast + .makeText( + context, + context.getString(R.string.search_feed), + Toast.LENGTH_SHORT + ) + .show() + } + } + ), + tint = if (item.feedTypes.contains(FeedType.SEARCH)) { + MaterialTheme.colors.allGoodColor + } else { + MaterialTheme.colors.onSurface.copy( + alpha = 0.32f + ) + } + ) + } +} + +@Composable +private fun FirstLine( + item: RelaySetupInfo, + onClick: () -> Unit, + onDelete: (RelaySetupInfo) -> Unit, + modifier: Modifier +) { + Row(verticalAlignment = Alignment.CenterVertically, modifier = modifier) { + Row(Modifier.weight(1f), verticalAlignment = Alignment.CenterVertically) { + Text( + text = item.briefInfo.displayUrl, + modifier = Modifier.clickable(onClick = onClick), + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + + if (item.paidRelay) { + Icon( + imageVector = Icons.Default.Paid, + null, + modifier = Modifier + .padding(start = 5.dp, top = 1.dp) + .size(14.dp), + tint = MaterialTheme.colors.allGoodColor + ) + } + } + + IconButton( + modifier = Modifier.size(30.dp), + onClick = { onDelete(item) } + ) { + Icon( + imageVector = Icons.Default.Cancel, + null, + modifier = Modifier + .padding(start = 10.dp) + .size(15.dp), + tint = WarningColor + ) + } + } +} + @Composable fun EditableServerConfig(relayToAdd: String, onNewRelay: (RelaySetupInfo) -> Unit) { var url by remember { mutableStateOf(relayToAdd) } @@ -702,7 +817,7 @@ fun EditableServerConfig(relayToAdd: String, onNewRelay: (RelaySetupInfo) -> Uni modifier = Modifier .size(Size35dp) .padding(horizontal = 5.dp), - tint = if (read) Color.Green else MaterialTheme.colors.placeholderText + tint = if (read) MaterialTheme.colors.allGoodColor else MaterialTheme.colors.placeholderText ) } @@ -713,7 +828,7 @@ fun EditableServerConfig(relayToAdd: String, onNewRelay: (RelaySetupInfo) -> Uni modifier = Modifier .size(Size35dp) .padding(horizontal = 5.dp), - tint = if (write) Color.Green else MaterialTheme.colors.placeholderText + tint = if (write) MaterialTheme.colors.allGoodColor else MaterialTheme.colors.placeholderText ) } @@ -739,9 +854,16 @@ fun EditableServerConfig(relayToAdd: String, onNewRelay: (RelaySetupInfo) -> Uni } } -fun countToHumanReadable(counter: Int) = when { - counter >= 1000000000 -> "${round(counter / 1000000000f)}G" - counter >= 1000000 -> "${round(counter / 1000000f)}M" - counter >= 1000 -> "${round(counter / 1000f)}k" +fun countToHumanReadableBytes(counter: Int) = when { + counter >= 1000000000 -> "${round(counter / 1000000000f)} GB" + counter >= 1000000 -> "${round(counter / 1000000f)} MB" + counter >= 1000 -> "${round(counter / 1000f)} KB" else -> "$counter" } + +fun countToHumanReadable(counter: Int, str: String) = when { + counter >= 1000000000 -> "${round(counter / 1000000000f)}G $str" + counter >= 1000000 -> "${round(counter / 1000000f)}M $str" + counter >= 1000 -> "${round(counter / 1000f)}K $str" + else -> "$counter $str" +} diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewRelayListViewModel.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewRelayListViewModel.kt index f6f03f0fe..885b28b07 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewRelayListViewModel.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewRelayListViewModel.kt @@ -4,6 +4,7 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.RelaySetupInfo +import com.vitorpamplona.amethyst.service.Nip11CachedRetriever import com.vitorpamplona.amethyst.service.relays.Constants import com.vitorpamplona.amethyst.service.relays.FeedType import com.vitorpamplona.amethyst.service.relays.RelayPool @@ -24,6 +25,7 @@ class NewRelayListViewModel : ViewModel() { fun load(account: Account) { this.account = account clear() + loadRelayDocuments() } fun create() { @@ -36,6 +38,21 @@ class NewRelayListViewModel : ViewModel() { clear() } + fun loadRelayDocuments() { + viewModelScope.launch(Dispatchers.IO) { + _relays.value.forEach { item -> + Nip11CachedRetriever.loadRelayInfo( + dirtyUrl = item.url, + onInfo = { + togglePaidRelay(item, it.limitation?.payment_required ?: false) + }, + onError = { url, errorCode, exceptionMessage -> + } + ) + } + } + } + fun clear() { _relays.update { var relayFile = account.userProfile().latestContactList?.relays() diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/RelayInformationDialog.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/RelayInformationDialog.kt index 952f455e0..9fbaf2463 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/RelayInformationDialog.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/RelayInformationDialog.kt @@ -1,8 +1,5 @@ package com.vitorpamplona.amethyst.ui.actions -import android.content.Context -import android.util.Log -import android.widget.Toast import androidx.compose.animation.Crossfade import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -28,27 +25,24 @@ import androidx.compose.ui.unit.sp import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.DialogProperties import com.vitorpamplona.amethyst.R +import com.vitorpamplona.amethyst.model.RelayBriefInfo import com.vitorpamplona.amethyst.model.RelayInformation -import com.vitorpamplona.amethyst.service.HttpClient -import com.vitorpamplona.amethyst.service.checkNotInMainThread import com.vitorpamplona.amethyst.ui.components.ClickableEmail import com.vitorpamplona.amethyst.ui.components.ClickableUrl import com.vitorpamplona.amethyst.ui.note.LoadUser +import com.vitorpamplona.amethyst.ui.note.RenderRelayIcon import com.vitorpamplona.amethyst.ui.note.UserCompose import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.theme.DoubleHorzSpacer import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.launch -import okhttp3.Call -import okhttp3.Callback -import okhttp3.Request -import okhttp3.Response -import java.io.IOException +import com.vitorpamplona.amethyst.ui.theme.Size55dp +import com.vitorpamplona.amethyst.ui.theme.StdPadding @OptIn(ExperimentalLayoutApi::class) @Composable fun RelayInformationDialog( onClose: () -> Unit, + relayBriefInfo: RelayBriefInfo, relayInfo: RelayInformation, accountViewModel: AccountViewModel, nav: (String) -> Unit @@ -79,18 +73,25 @@ fun RelayInformationDialog( }) } - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.Center - ) { - Title(relayInfo.name ?: "") - } + Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Center, modifier = StdPadding.fillMaxWidth()) { + Column() { + RenderRelayIcon( + relayBriefInfo.favIcon, + Size55dp + ) + } - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.Center - ) { - SectionContent(relayInfo.description ?: "") + Spacer(modifier = DoubleHorzSpacer) + + Column(horizontalAlignment = Alignment.CenterHorizontally) { + Row() { + Title(relayInfo.name?.trim() ?: "") + } + + Row() { + SubtitleContent(relayInfo.description?.trim() ?: "") + } + } } Section(stringResource(R.string.owner)) @@ -212,7 +213,7 @@ private fun DisplaySupportedNips(relayInfo: RelayInformation) { val text = item.toString().padStart(2, '0') Box(Modifier.padding(10.dp)) { ClickableUrl( - urlText = "$text", + urlText = text, url = "https://github.com/nostr-protocol/nips/blob/master/$text.md" ) } @@ -222,7 +223,7 @@ private fun DisplaySupportedNips(relayInfo: RelayInformation) { val text = item.padStart(2, '0') Box(Modifier.padding(10.dp)) { ClickableUrl( - urlText = "$text", + urlText = text, url = "https://github.com/nostr-protocol/nips/blob/master/$text.md" ) } @@ -258,13 +259,18 @@ private fun DisplayOwnerInformation( @Composable fun Title(text: String) { - Spacer(modifier = DoubleVertSpacer) Text( text = text, fontWeight = FontWeight.Bold, fontSize = 24.sp ) - Spacer(modifier = DoubleVertSpacer) +} + +@Composable +fun SubtitleContent(text: String) { + Text( + text = text + ) } @Composable @@ -285,85 +291,3 @@ fun SectionContent(text: String) { text = text ) } - -fun loadRelayInfo( - dirtyUrl: String, - context: Context, - scope: CoroutineScope, - onInfo: (RelayInformation) -> Unit -) { - try { - val url = if (dirtyUrl.contains("://")) { - dirtyUrl - .replace("wss://", "https://") - .replace("ws://", "http://") - } else { - "https://$dirtyUrl" - } - - val request: Request = Request - .Builder() - .header("Accept", "application/nostr+json") - .url(url) - .build() - - HttpClient.getHttpClient() - .newCall(request) - .enqueue( - object : Callback { - override fun onResponse(call: Call, response: Response) { - checkNotInMainThread() - response.use { - val body = it.body.string() - try { - if (it.isSuccessful) { - onInfo(RelayInformation.fromJson(body)) - } else { - scope.launch { - Toast - .makeText( - context, - context.getString(R.string.an_error_occurred_trying_to_get_relay_information, dirtyUrl), - Toast.LENGTH_SHORT - ).show() - } - } - } catch (e: Exception) { - Log.e("RelayInfoFail", "Resulting Message from Relay $dirtyUrl in not parseable: $body", e) - scope.launch { - Toast - .makeText( - context, - context.getString(R.string.an_error_occurred_trying_to_get_relay_information, dirtyUrl), - Toast.LENGTH_SHORT - ).show() - } - } - } - } - - override fun onFailure(call: Call, e: IOException) { - Log.e("RelayInfoFail", "$dirtyUrl unavailable", e) - scope.launch { - Toast - .makeText( - context, - context.getString(R.string.an_error_occurred_trying_to_get_relay_information, dirtyUrl), - Toast.LENGTH_SHORT - ).show() - } - } - } - ) - } catch (e: Exception) { - Log.e("RelayInfoFail", "Invalid URL $dirtyUrl", e) - scope.launch { - Toast - .makeText( - context, - context.getString(R.string.an_error_occurred_trying_to_get_relay_information, dirtyUrl), - Toast.LENGTH_SHORT - ).show() - } - } -} diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/RelaySelectionDialog.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/RelaySelectionDialog.kt index b979dc2fe..31028d215 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/RelaySelectionDialog.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/RelaySelectionDialog.kt @@ -31,6 +31,7 @@ import androidx.compose.ui.window.DialogProperties import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.RelayBriefInfo import com.vitorpamplona.amethyst.model.RelayInformation +import com.vitorpamplona.amethyst.service.Nip11Retriever import com.vitorpamplona.amethyst.service.relays.Relay import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import kotlinx.coroutines.launch @@ -41,6 +42,11 @@ data class RelayList( val isSelected: Boolean ) +data class RelayInfoDialog( + val relayBriefInfo: RelayBriefInfo, + val relayInfo: RelayInformation +) + @Composable fun RelaySelectionDialog( preSelectedList: List, @@ -63,16 +69,17 @@ fun RelaySelectionDialog( } ) } - var relayInfo: RelayInformation? by remember { mutableStateOf(null) } + var relayInfo: RelayInfoDialog? by remember { mutableStateOf(null) } - if (relayInfo != null) { + relayInfo?.let { RelayInformationDialog( onClose = { relayInfo = null }, - relayInfo = relayInfo!!, - accountViewModel, - nav + relayInfo = it.relayInfo, + relayBriefInfo = it.relayBriefInfo, + accountViewModel = accountViewModel, + nav = nav ) } @@ -161,9 +168,30 @@ fun RelaySelectionDialog( } }, onLongPress = { - loadRelayInfo(item.relay.url, context, scope) { - relayInfo = it - } + accountViewModel.retrieveRelayDocument( + item.relay.url, + onInfo = { + relayInfo = RelayInfoDialog(RelayBriefInfo(item.relay.url), it) + }, + onError = { url, errorCode, exceptionMessage -> + val msg = when (errorCode) { + Nip11Retriever.ErrorCode.FAIL_TO_ASSEMBLE_URL -> context.getString(R.string.relay_information_document_error_assemble_url, url, exceptionMessage) + Nip11Retriever.ErrorCode.FAIL_TO_REACH_SERVER -> context.getString(R.string.relay_information_document_error_assemble_url, url, exceptionMessage) + Nip11Retriever.ErrorCode.FAIL_TO_PARSE_RESULT -> context.getString(R.string.relay_information_document_error_assemble_url, url, exceptionMessage) + Nip11Retriever.ErrorCode.FAIL_WITH_HTTP_STATUS -> context.getString(R.string.relay_information_document_error_assemble_url, url, exceptionMessage) + } + + scope.launch { + Toast + .makeText( + context, + msg, + Toast.LENGTH_SHORT + ) + .show() + } + } + ) } ) } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/RelayListRow.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/RelayListRow.kt index 8c3736791..c0a97c319 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/RelayListRow.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/RelayListRow.kt @@ -1,5 +1,6 @@ package com.vitorpamplona.amethyst.ui.note +import android.widget.Toast import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource @@ -28,14 +29,15 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.Role +import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.lifecycle.map import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.RelayBriefInfo import com.vitorpamplona.amethyst.model.RelayInformation +import com.vitorpamplona.amethyst.service.Nip11Retriever import com.vitorpamplona.amethyst.ui.actions.RelayInformationDialog -import com.vitorpamplona.amethyst.ui.actions.loadRelayInfo import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.theme.RelayIconFilter @@ -45,6 +47,7 @@ import com.vitorpamplona.amethyst.ui.theme.Size15dp import com.vitorpamplona.amethyst.ui.theme.StdStartPadding import com.vitorpamplona.amethyst.ui.theme.placeholderText import kotlinx.collections.immutable.toImmutableList +import kotlinx.coroutines.launch @Composable public fun RelayBadgesHorizontal(baseNote: Note, accountViewModel: AccountViewModel, nav: (String) -> Unit) { @@ -112,8 +115,9 @@ fun RenderRelay(relay: RelayBriefInfo, accountViewModel: AccountViewModel, nav: relayInfo = null }, relayInfo = relayInfo!!, - accountViewModel, - nav + relayBriefInfo = relay, + accountViewModel = accountViewModel, + nav = nav ) } @@ -131,9 +135,30 @@ fun RenderRelay(relay: RelayBriefInfo, accountViewModel: AccountViewModel, nav: interactionSource = interactionSource, indication = ripple, onClick = { - loadRelayInfo(relay.url, context, scope) { - relayInfo = it - } + accountViewModel.retrieveRelayDocument( + relay.url, + onInfo = { + relayInfo = it + }, + onError = { url, errorCode, exceptionMessage -> + val msg = when (errorCode) { + Nip11Retriever.ErrorCode.FAIL_TO_ASSEMBLE_URL -> context.getString(R.string.relay_information_document_error_assemble_url, url, exceptionMessage) + Nip11Retriever.ErrorCode.FAIL_TO_REACH_SERVER -> context.getString(R.string.relay_information_document_error_assemble_url, url, exceptionMessage) + Nip11Retriever.ErrorCode.FAIL_TO_PARSE_RESULT -> context.getString(R.string.relay_information_document_error_assemble_url, url, exceptionMessage) + Nip11Retriever.ErrorCode.FAIL_WITH_HTTP_STATUS -> context.getString(R.string.relay_information_document_error_assemble_url, url, exceptionMessage) + } + + scope.launch { + Toast + .makeText( + context, + msg, + Toast.LENGTH_SHORT + ) + .show() + } + } + ) } ) } @@ -146,12 +171,12 @@ fun RenderRelay(relay: RelayBriefInfo, accountViewModel: AccountViewModel, nav: } @Composable -private fun RenderRelayIcon(iconUrl: String) { +fun RenderRelayIcon(iconUrl: String, size: Dp = Size13dp) { val backgroundColor = MaterialTheme.colors.background val iconModifier = remember { Modifier - .size(Size13dp) + .size(size) .clip(shape = CircleShape) .background(backgroundColor) } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt index 76bb6ab2d..b105b5216 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt @@ -19,10 +19,13 @@ import com.vitorpamplona.amethyst.model.AddressableNote import com.vitorpamplona.amethyst.model.ConnectivityType import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.Note +import com.vitorpamplona.amethyst.model.RelayInformation import com.vitorpamplona.amethyst.model.UrlCachedPreviewer import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.model.UserState import com.vitorpamplona.amethyst.service.Nip05Verifier +import com.vitorpamplona.amethyst.service.Nip11CachedRetriever +import com.vitorpamplona.amethyst.service.Nip11Retriever import com.vitorpamplona.amethyst.service.OnlineChecker import com.vitorpamplona.amethyst.service.lnurl.LightningAddressResolver import com.vitorpamplona.amethyst.ui.components.UrlPreviewState @@ -551,6 +554,16 @@ class AccountViewModel(val account: Account) : ViewModel() { } } + fun retrieveRelayDocument( + dirtyUrl: String, + onInfo: (RelayInformation) -> Unit, + onError: (String, Nip11Retriever.ErrorCode, String?) -> Unit + ) { + viewModelScope.launch(Dispatchers.IO) { + Nip11CachedRetriever.loadRelayInfo(dirtyUrl, onInfo, onError) + } + } + class Factory(val account: Account) : ViewModelProvider.Factory { override fun create(modelClass: Class): AccountViewModel { return AccountViewModel(account) as AccountViewModel diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/theme/Color.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/theme/Color.kt index e4b1d22bb..538442536 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/theme/Color.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/theme/Color.kt @@ -24,3 +24,9 @@ val DarkerGreen = Color.Green.copy(alpha = 0.32f) val WarningColor = Color(0xFFC62828) val RelayIconFilter = ColorFilter.colorMatrix(ColorMatrix().apply { setToSaturation(0.5f) }) + +val LightWarningColor = Color(0xFFffcc00) +val DarkWarningColor = Color(0xFFF8DE22) + +val LightAllGoodColor = Color(0xFF339900) +val DarkAllGoodColor = Color(0xFF99cc33) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/theme/Theme.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/theme/Theme.kt index 42a6331c6..541420948 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/theme/Theme.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/theme/Theme.kt @@ -286,6 +286,12 @@ val Colors.overPictureBackground: Color val Colors.bitcoinColor: Color get() = if (isLight) BitcoinLight else BitcoinDark +val Colors.warningColor: Color + get() = if (isLight) LightWarningColor else DarkWarningColor + +val Colors.allGoodColor: Color + get() = if (isLight) LightAllGoodColor else DarkAllGoodColor + val Colors.markdownStyle: RichTextStyle get() = if (isLight) MarkDownStyleOnLight else MarkDownStyleOnDark diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e127e7523..33f12e712 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -558,4 +558,16 @@ Unable to send zap Message the User Ok + + Failed to reach %1$s: %2$s + Failed to reach %1$s: %2$s + Failed to parse result from %1$s: %2$s + %1$s failed with code %2$s + Active for: + + Home + DMs + Chats + Global + Search From 65e4a766ab8df25e305da48b269a9b857242f3ab Mon Sep 17 00:00:00 2001 From: Rif'at Ahdi R <10791791+atrifat@users.noreply.github.com> Date: Fri, 8 Sep 2023 11:38:05 +0800 Subject: [PATCH 18/21] Add Indonesian Translation --- app/build.gradle | 2 +- app/src/main/res/values-in-rID/strings.xml | 2 - app/src/main/res/values-in/strings.xml | 530 +++++++++++++++++++++ app/src/main/res/xml/locales_config.xml | 1 + 4 files changed, 532 insertions(+), 3 deletions(-) delete mode 100644 app/src/main/res/values-in-rID/strings.xml create mode 100644 app/src/main/res/values-in/strings.xml diff --git a/app/build.gradle b/app/build.gradle index 6df3bf15f..219aa8c2c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -20,7 +20,7 @@ android { vectorDrawables { useSupportLibrary true } - resourceConfigurations += ['ar', 'cs', 'de', 'eo', 'es', 'fa', 'fr', 'hu', 'ja', 'night', 'nl', 'pt-rBR', 'ru', 'sv-rSE', 'ta', 'th', 'tr', 'uk', 'zh', 'sh-rHK', 'zh-rTW'] + resourceConfigurations += ['ar', 'cs', 'de', 'eo', 'es', 'fa', 'fr', 'hu', 'in', 'ja', 'night', 'nl', 'pt-rBR', 'ru', 'sv-rSE', 'ta', 'th', 'tr', 'uk', 'zh', 'sh-rHK', 'zh-rTW'] } buildTypes { diff --git a/app/src/main/res/values-in-rID/strings.xml b/app/src/main/res/values-in-rID/strings.xml deleted file mode 100644 index 66a570486..000000000 --- a/app/src/main/res/values-in-rID/strings.xml +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml new file mode 100644 index 000000000..8209fb01d --- /dev/null +++ b/app/src/main/res/values-in/strings.xml @@ -0,0 +1,530 @@ + + + Arahkan ke Kode QR + Tampilkan QR + Foto Profil + Pindai QR + Tampilkan Saja + Postingan dilaporkan oleh + Data sedang dimuat atau tidak dapat ditemukan di daftar relai Anda + Gambar Kanal + Data yang direferensikan tidak ditemukan + Tidak dapat mendeskripsikan pesan + Gambar Grup + Konten Eksplisit + Spam + Peniruan/penipuan + Perilaku Ilegal + Tidak dikenal + Ikon Relai + Penulis Tidak Dikenal + Salin Teks + Salin ID Penulis (Author ID) + Salin ID Tulisan (Note ID) + Siarkan + Minta Penghapusan + Blokir / Laporkan + + Laporkan Spam / Penipuan + Laporkan Peniruan + Laporkan Konten Eksplisit + Laporkan Perilaku Ilegal + Masuk dengan Kunci pribadi (nsec) untuk dapat membalas tulisan + Masuk dengan Kunci pribadi (nsec) untuk dapat mempromosikan tulisan + Masuk dengan Kunci pribadi (nsec) untuk dapat menyukai tulisan + Tidak Ada Pengaturan Jumlah Zap. Tekan dan tahan tombol untuk mengubah + Masuk dengan Kunci pribadi (nsec) untuk dapat mengirim Zaps + Masuk dengan Kunci pribadi (nsec) untuk dapat mengikuti + Masuk dengan Kunci pribadi (nsec) untuk berhenti mengikuti + Zaps + Jumlah pengamat + Promosi + dipromosikan + Kutipan + Jumlah Baru dalam Sats + Tambah + "membalas kepada " + " dan " + dalam kanal + Spanduk Profil + Pembayaran Berhasil + Gagal menguraikan pesan kesalahan + " Mengikuti" + " Pengikut" + Profil + Filter Keamanan + Keluar + Tampilkan lebih banyak + Faktur Tagihan Lightning + Bayar + Lightning Tip + Catatan ke Penerima + Terima kasih banyak! + Jumlah dalam Sats + Kirim Sats + Gagal menampilkan pratinjau untuk %1$s : %2$s + Pratinjau Gambar Kartu untuk %1$s + Kanal Baru + Nama Kanal + Grupku yang Hebat + Alamat Url Gambar + Deskripsi + "Tentang kami.. " + Apa yang anda pikirkan? + Kirim + Simpan + Buat + Batal + Gagal mengunggah gambar + Alamat Relai + Kiriman + Bytes + Errors + Beranda + Area Pesan Pribadi + Area Percakapan Publik + Area Global + Area Pencarian + Tambah Relai + Nama ditampilkan + Namaku yg ditampilkan + Nama pengguna + Nama penggunaku + Tentang saya + URL Avatar + "URL Spanduk/Banner " + URL Situs Web + Alamat LN + LN URL (versi lama) + Gambar disimpan ke galeri + Gagal menyimpan gambar + Unggah Gambar + Mengunggah… + Pengguna tidak memiliki alamat lightning (LN) yang diatur untuk menerima sats + "balas di sini.. " + Menyalin ID Catatan ke papan klip untuk dibagikan di Nostr + Salin ID Kanal (Catatan) ke Papan Klip + Ubah Metadata Kanal + Gabung + Dikenali + Permintaan Baru + Pengguna diblokir + Utas Baru + Percakapan + Catatan + Balasan + Mengikuti + Laporan + Lebih banyak pilihan + " Relai" + Situs Web + Alamat Lightning + Salin Nsec ID (kata sandi anda) ke papan klip sebagai cadangan + Salin Kunci Privat ke papan klip + Salin Kunci Publik ke papan klip untuk dibagikan + Salin Kunci Publik (NPub) ke papan klip + Kirim Pesan Langsung + Ubah Metadata Pengguna + Ikuti + Ikuti balik + Batalkan blokir + Salin ID Pengguna + Batalkan blokir pengguna + npub, nama pengguna, teks + Bersihkan + Logo Aplikasi + nsec.. or npub.. + Tampilkan Kata Sandi + Sembunyikan Kata Sandi + Kunci tidak sah + Saya menerima + syarat penggunaan + Penerimaan persyaratan diperlukan + Kunci diperlukan + Masuk + Buat kunci baru + Memuat data umpan + "Gagal memuat balasan: " + Coba lagi + Data umpan kosong. + Perbarui + dibuat + "dengan deskripsi " + dan gambar + mengubah nama percakapan menjadi + deskripsi untuk + dan gambar untuk + Tinggalkan + Berhenti mengikuti + Kanal dibuat + Informasi kanal diubah menjadi + Percakapan Publik + kiriman diterima + Hapus + Otomatis + diterjemahkan dari + ke + Tampilkan dalam %1$s (prioritas) + Selalu terjemahkan ke %1$s + Jangan terjemahkan dari %1$s + Alamat Nostr + tidak pernah + sekarang + jam + menit + hari + Ketelanjangan + Ujaran kotor/kebencian + Laporkan ujaran kebencian + Laporkan Ketelanjangan / Pornografi + lainnya + Tandai semua yg dikenal telah dibaca + Tandai pesan baru telah dibaca + Tandai semua telah dibaca + Cadangkan Kunci + "\n ## Tip Pencadangan dan Keamanan Utama\n \n\nAkun Anda diamankan dengan kunci rahasia. Kuncinya adalah string acak panjang yang dimulai dengan **nsec1**.. Siapa pun yang memiliki akses ke kunci rahasia Anda dapat mempublikasikan konten menggunakan identitas Anda.\n \n\n- **Jangan** letakkan kunci rahasia Anda di situs web atau perangkat lunak apa pun yang tidak Anda percayai.\n \n- Pengembang Aplikasi Amethyst **tidak akan pernah** meminta kunci rahasia Anda.\n \n- **Pastikan** simpan cadangan aman kunci rahasia Anda untuk pemulihan akun. Kami merekomendasikan menggunakan aplikasi khusus pengelola kata sandi.\n " + Kunci rahasia (nsec) disalin ke clipboard + Salin kunci rahasia saya + Autentikasi gagal + Kesalahan + Dibuat oleh %1$s + Gambar penghargaan lencana untuk %1$s + Anda Menerima Penghargaan Lencana baru + Penghargaan lencana diberikan kepada + Teks catatan disalin ke papan klip + \@npub Penulis disalin ke papan klip + Note ID (@note1) disalin ke papan klip + Pilih Teks + <Gagal mendeskripsikan pesan pribadi>\n\nAnda dikutip dalam percakapan pribadi/terenkripsi antara %1$s dan %2$s. + Tambah Akun Baru + Akun + Pilih Akun + Tambah Akun Baru + Akun Aktif + Memiliki kunci pribadi + Baca saja, tidak ada kunci pribadi + Kembali + Pilih + Bagikan Tauatan ke Peramban + Bagikan + ID Penulis + ID Catatan + Salin Teks + Hapus + Berhenti mengikuti + Ikuti + Meminta Penghapusan + Amethyst akan meminta agar catatan Anda dihapus dari relai yang saat ini Anda sambungkan. Tidak ada jaminan bahwa catatan Anda akan dihapus secara permanen dari relai tersebut, atau dari relai lain tempat catatan itu disimpan. + Blokir + Hapus + Blokir + Laporkan + Hapus + Jangan tampilkan lagi + Spam atau penipuan + Perilaku tidak senonoh atau penuh kebencian + Peniruan identitas yang berbahaya + Konten ketelanjangan atau grafis + Perilaku Ilegal + Memblokir pengguna akan menyembunyikan konten mereka di aplikasi Anda. Catatan Anda masih dapat dilihat oleh publik, termasuk oleh orang yang Anda blokir. Pengguna yang diblokir terdaftar di layar Filter Keamanan. + + Laporkan Penyalahgunaan + Semua laporan yang diposting akan terlihat oleh publik. + Secara opsional, berikan konteks tambahan tentang laporan Anda… + Konteks Tambahan + Alasan + Pilih alasan… + Kirim Laporan + Blokir dan Laporkan + Blokir + + Penanda + Penanda Pribadi + Penanda Publik + Tambahkan Penanda Pribadi + Tambahkan Penanda Publik + Hapus dari Penanda Pribadi + Hapus dari Penanda Publik + + Layanan Wallet Connect + Mengijinkan Kunci Rahasia Nostr untuk membayar zaps tanpa meninggalkan aplikasi. Jaga kunci tetap aman dan gunakan relai pribadi jika memungkinkan + Kunci Publik Wallet Connect + Relai Wallet Connect + Kunci Rahasia Wallet Connect + Tampilkan kunci rahasia + nsec / hex (kunci pribadi) + + Jumlah Dijanjikan dalam Sats + Kirim Pemungutan Suara + Bidang yang wajib diisi: + Penerima Zap + Deskripsi Utama Pemungutan Suara… + Pilihan %s + Deskripsi pilihan pemungutan suara + Bidang pilihan (tidak wajib): + Zap terendah + Zap tertinggi + Konsensus + (0–100)% + Berakhir pada + hari + Pemungutan suara ditutup untuk voting baru + Jumlah Zap + Hanya satu suara per pengguna yang diperbolehkan pada jenis pemungutan suara ini + + Mencari data event %1$s + + Tambah pesan publik + Tambah pesan pribadi + Tambah pesan faktur tagihan + + Terima kasih atas semua pekerjaan Anda! + + Buat dan Tambah + Pembuat Pemungutan Suara tidak dapat memberikan suara dalam pemungutan suara sendiri. + + Konten ini sama sejak kiriman tersebut dibuat + Konten ini telah berubah. Penulis mungkin tidak melihat atau menyetujui perubahan tersebut + + Tambah Gambar + Tambah Video + Tambah Dokumen + + Tambahkan ke Pesan + Deskripsi Konten + Perahu berwarna biru di pantai berpasir putih saat matahari terbenam + + Tipe Zap + Tipe Zap untuk semua pilihan + + Publik + Semua orang dapat melihat transaksi dan pesannya + + Pribadi + Pengirim dan Penerima dapat saling melihat dan membaca pesan + + Anonim + Penerima dan semua orang tidak dapat mengetahui siapa pengirim pembayaran + + Bukan Zap + Tidak ada jejak di Nostr, hanya di Lightning Network + + + Server Berkas + Alamat LN atau @Pengguna + + imgur.com - terpercaya + Imgur dapat memodifikasi berkas + + nostrimg.com - terpercaya + NostrImg dapat memodifikasi berkas + + nostr.build - terpercaya + Nostr.build dapat memodifikasi berkas + + nostrfiles.dev - terpercaya + Nostrfiles.dev dapat memodifikasi berkas + + nostrcheck.me - terpercaya + nostrcheck.me dapat memodifikasi berkas + + + Imgur yg dapat diverifikasi (NIP-94) + Periksa jika Imgur mengubah berkas. NIP baru: aplikasi Nostr lain mungkin belum mendukung + + NostrImg yg dapat diverifikasi (NIP-94) + Periksa jika NostrImg mengubah berkas. NIP baru: aplikasi Nostr lain mungkin belum mendukung + + Nostr.build yg dapat diverifikasi (NIP-94) + Periksa jika Nostr.build mengubah berkas. NIP baru: aplikasi Nostr lain mungkin belum mendukung + + Nostrfiles.dev yg dapat diverifikasi (NIP-94) + Periksa jika Nostrfiles.dev mengubah berkas. NIP baru: aplikasi Nostr lain mungkin belum mendukung + + Nostrcheck.me yg dapat diverifikasi (NIP-94) + Periksa jika Nostrcheck.me mengubah berkas. NIP baru: aplikasi Nostr lain mungkin belum mendukung + + Relai anda (NIP-95) + Berkas disimpan di relai anda. NIP baru: periksa apakah relai mendukung fitur + + Pengaturan Tor/Orbot + Hubungkan melalui pengaturan Orbot Anda + + Putuskan sambungan dari Orbot/Tor Anda? + Data Anda akan segera ditransfer di jaringan reguler (bukan TOR) + Ya + Tidak + + + Daftar yang Diikuti + Semua yang diikuti + Global + "\n ## Terhubung melalui Tor menggunakan Orbot\n \n\n1. Pasang [Orbot](https://play.google.com/store/apps/details?id=org.torproject.android)\n \n2. Jalankan Orbot\n \n3. Di dalam Orbot, periksa nomor port Socks. Secara bawaan menggunakan nomor 9050\n \n4. Jika diperlukan, ubah nomor port Orbot\n \n5. Atur nomor port Socks pada layar ini\n \n6. Tekan tombol Aktifkan untuk menggunakan Orbot sebagai proxy\n " + Port Orbot Socks + Nomor port tidak sah + Gunakan Orbot + Putuskan Tor/Orbot + + Pesan Pribadi + Memberi tahu Anda ketika pesan pribadi tiba + + Zaps diterima + Memberi tahu Anda ketika seseorang mengirimkan anda zap + %1$s sats + Dari %1$s + untuk %1$s + + "Beritahu: " + + Gabung Percakapan + ID Pengguna atau Grup + npub, nevent atau kode heksa + Buat + Gabung + + Hari ini + + Peringatan konten + Postingan ini berisi konten sensitif yang mungkin dianggap menyinggung atau mengganggu oleh sebagian orang + Selalu sembunyikan konten sensitif + Selalu tampilkan konten sensitif + Selalu tampilkan peringatan konten + + "Rekomendasi: " + Saring spam dari orang asing (tak diikuti) + Peringatkan ketika kiriman memiliki laporan dari pengikut Anda + + Simbol Reaksi Baru + Tidak ada jenis reaksi yang dipilih. Tekan dan tahan untuk mengubah + + Galang Dana Zap + Menambahkan jumlah target sats yang ingin dikumpulkan untuk kiriman ini. Klien yang mendukung mungkin menunjukkan hal ini sebagai indikator kemajuan dalam memberi insentif pada donasi + Jumlah Target dalam Sats + + Galang Dana Zap mencapai %1$s. %2$s sats untuk memenuhi target + Baca dari Relai + Tulis ke Relai + Terjadi kesalahan saat mencoba mendapatkan informasi relai %1$s + Pemilik + Versi + Perangkat Lunak + Kontak + NIP yg didukung + Biaya Pendaftaran + Alamat Pembayaran + Batasan + Negara + Bahasa + Tags + Kebijakan kiriman + Panjang pesan + Langganan + Penyaringan + Panjang ID Subscription + Awalan minimal + Jumlah Tags maksimum + Panjang konten + PoW terendah + Autentikasi + Pembayaran + Token Cashu + Tukarkan + Tidak ada alamat Lightning yang diatur + Token disalin ke papan klip + + DARING (ON) + LURING (OFF) + BERAKHIR + DIJADWALKAN + + Siaran langsung sedang luring + Siaran langsung berakhir + Keluar akan menghapus semua informasi lokal Anda. Pastikan kunci pribadi Anda dicadangkan untuk menghindari kehilangan akun Anda. Apakah Anda ingin melanjutkan? + Tags yg diikuti + + Relai + + Siaran Langsung + Komunitas + Percakapan + Kiriman yg disetujui + + Grup ini tidak memiliki deskripsi atau aturan. Bicaralah dengan pemiliknya untuk menambahkannya + Komunitas ini tidak memiliki deskripsi. Bicaralah dengan pemiliknya untuk menambahkannya + + Konten Sensitif + Menambahkan peringatan konten sensitif sebelum menampilkan konten ini + Pengaturan + Selalu + Hanya Wifi + Tidak Pernah + + Sistem + Terang + Gelap + Pengaturan Aplikasi + Bahasa + Tema + Pratinjau Gambar + Pemutaran Video + Pratinjau URL + Muat Gambar + + Pengirim Spam + + Diredam. Klik untuk bersuara + Bersuara. Kilik untuk meredam + Cari catatan lokal dan jarak jauh + + Alamat Nostr diverifikasi + Alamat Nostr gagal diverifikasi + Memeriksa Alamat Nostr + Pilih / Hapus Semua + Bawaan + Pilih Relai untuk melanjutkan + + Teruskan Zaps ke: + Klien yg mendukung akan meneruskan zaps ke Alamat LN atau Profil Pengguna di bawah ini, bukan ke alamat LN Anda + + Paparkan Lokasi sebagai + Menambahkan Geohash lokasi Anda ke postingan. Publik akan mengetahui bahwa Anda berada dalam jarak 5 km (3 mil) dari lokasi saat ini + + Menambahkan peringatan konten sensitif sebelum menampilkan konten Anda. Ini ideal untuk konten NSFW apa pun atau konten yang mungkin dianggap menyinggung atau mengganggu oleh sebagian orang + + Fitur Baru + Mengaktifkan mode ini mengharuskan Amethyst mengirim pesan NIP-24 (GiftWrapped, Sealed Direct, dan Group Messages). NIP-24 masih baru dan sebagian besar klien belum menerapkannya. Pastikan penerima menggunakan klien yang kompatibel. + Aktifkan + + Publik + Pribadi + Ke + Subjek + Topik percakapan + \@Pengguna1, @Pengguna2, @Pengguna3 + + Anggota grup ini + Penjelasan kepada anggota + Mengubah nama untuk tujuan baru. + + Untuk Antarmuka Aplikasi + Tema Gelap, Terang, atau Sistem + Memuat gambar dan GIF secara otomatis + Memutar video dan GIF secara otomatis + Tampilkan pratinjau URL + Kapan memuat gambar + + Salin URL ke papan klip + Salin ID Catatan ke papan klip + + Dibuat pada + Aturan + + Perbaharui statusmu + + Kesalahan menguraikan pesan kesalahan + Suara ditimbang berdasarkan jumlah zap. Anda dapat menetapkan jumlah minimum untuk menghindari pembuat spam dan jumlah maksimum untuk menghindari pengirim zap besar mengambil alih pemungutan suara. Gunakan jumlah yang sama di kedua kolom untuk memastikan setiap suara bernilai sama. Biarkan kosong untuk menerima jumlah berapa pun. + + Tidak dapat mengirim zap + Kirim Pesan ke Pengguna + Ok + diff --git a/app/src/main/res/xml/locales_config.xml b/app/src/main/res/xml/locales_config.xml index b1ffdebbd..22d8cc370 100644 --- a/app/src/main/res/xml/locales_config.xml +++ b/app/src/main/res/xml/locales_config.xml @@ -10,6 +10,7 @@ + From 6cae89d276fadd21c2cc7395c8ef7de899cd1453 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Fri, 8 Sep 2023 08:41:07 -0400 Subject: [PATCH 19/21] Adds indonesian to crowdin --- crowdin.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/crowdin.yml b/crowdin.yml index 22413f7a3..c822c312a 100644 --- a/crowdin.yml +++ b/crowdin.yml @@ -19,6 +19,7 @@ files: fa: fa fr: fr hu: hu + in: in ja: ja nl: nl ru: ru From 338ae41baa675cbbacc434c8eb1446bb7e2622d1 Mon Sep 17 00:00:00 2001 From: Crowdin Bot Date: Fri, 8 Sep 2023 12:42:58 +0000 Subject: [PATCH 20/21] New Crowdin translations by GitHub Action --- app/src/main/res/values-in-rID/strings.xml | 2 + app/src/main/res/values-sw-rKE/strings.xml | 411 ++++++++++++++++++++- 2 files changed, 412 insertions(+), 1 deletion(-) create mode 100644 app/src/main/res/values-in-rID/strings.xml diff --git a/app/src/main/res/values-in-rID/strings.xml b/app/src/main/res/values-in-rID/strings.xml new file mode 100644 index 000000000..66a570486 --- /dev/null +++ b/app/src/main/res/values-in-rID/strings.xml @@ -0,0 +1,2 @@ + + diff --git a/app/src/main/res/values-sw-rKE/strings.xml b/app/src/main/res/values-sw-rKE/strings.xml index 48a0a8b40..453d80492 100644 --- a/app/src/main/res/values-sw-rKE/strings.xml +++ b/app/src/main/res/values-sw-rKE/strings.xml @@ -50,7 +50,57 @@ Malipo Yamefaulu Hitilafu katika kuchanganua ujumbe wa hitilafu " Kufuatia" - Mtumiaji hana anwani ya umeme iliyowekwa ili kupokea sati + " Wafuasi" + Wasifu + Vichujio vya Usalama + Ondoka + Onyesha Zaidi + Ankara ya umeme + Lipa + Vidokezo vya Umeme + Kumbuka kwa Mpokeaji + Asante sana! + Kiasi katika Sats + Tuma Sats + "Hitilafu katika kuchanganua onyesho la kukagua %1$s : %2$s" + "Hakiki Picha ya Kadi ya %1$s" + Kituo Kipya + Kituo Kipya + Kundi Langu la Ajabu + Url ya picha + Maelezo + "Kuhusu sisi.. " + Unafikiria nini? + Tuma + Hifadhi + Unda + Ghairi + Kushindwa kupakia picha + Anwani ya Relay + Machapisho + Bayti + Makosa + Malisho ya Nyumbani + Malisho ya Ujumbe Binafsi + Malisho ya Mazungumzo ya Umma + Malisho ya Kimataifa + Malisho ya Utafutaji + Ongeza Relay + Jina la Kuonyesha + Jina langu la kuonyesha + Jina la mtumiaji + Jina langu la mtumiaji + Kuhusu mimi + URL ya Picha ya Profaili + URL ya Bango + URL ya Tovuti + Anwani ya LN + URL ya LN (imepitwa na wakati) + Picha imesave kwenye galeria + Kushindwa kuhifadhi picha + Pakia Picha + Inapakia… + Mtumiaji hana anwani ya umeme iliyoandaliwa kupokea sati "jibu hapa.. " Hunakili Kitambulisho cha Dokezo kwenye ubao wa kunakili ili kushiriki katika Nostr Nakili Kitambulisho cha Kituo (Kumbuka) kwenye Ubao wa kunakili @@ -64,4 +114,363 @@ Vidokezo Majibu "Anafuata" + "Reports" + Chaguo Zaidi + " Relays" + Tovuti + Anwani ya Umeme + Hunakili Kitambulisho cha Nsec (nywila yako) kwenye ubao wa kunakili kwa ajili ya nakala rudufu + Nakili Ufunguo wa Siri kwenye Ubao wa Kunakili + Hunakili ufunguo wa umma kwenye ubao wa kunakili kwa ajili ya kushiriki + Nakili Ufunguo wa Umma (NPub) kwenye Ubao wa Kunakili + Tuma Ujumbe wa Moja kwa Moja + Huhariri Metadata ya Mtumiaji + Fuata + Fuata tena + Fungua + Nakili Kitambulisho cha Mtumiaji + Fungua Mtumiaji + "npub, jina la mtumiaji, maandishi" + Ondoa + Nembo ya Programu + nsec.. au npub.. + Onyesha Nywila + Ficha Nywila + Ufunguo usiofaa + "Nakubali " + masharti ya matumizi + Kukubaliana na masharti ni lazima + Ufunguo unahitajika + Ingia + Tengeneza Ufunguo Mpya + Inapakia malisho + "Kosa katika kupakia majibu: " + Jaribu tena + Malisho ni tupu. + Sasisha + imeundwa + na maelezo ya + na picha + ilisitisha jina la mazungumzo hadi + maelezo ya + na picha ya + Ondoka + Acha kufuata + Kituo kimeundwa + "Maelezo ya Kituo yamebadilika hadi" + Mazungumzo ya Umma + machapisho yamepokelewa + Ondoa + Auto + imetafsiriwa kutoka + kwa + Onyesha kwa %1$s kwanza + Daima itafsiriwe kwa %1$s + Kamwe usitafsiri kutoka %1$s + Anwani ya Nostr + kamwe + sasa + h + m + d + Uchi + Lugha chafu / Maneno yenye chuki + Ripoti Lugha yenye chuki + Ripoti Uchi / Pornografia + wengine + Funga kama Zimejulikana + Funga kama Mpya + Funga kama Zimejulikana + Nakili Nakala za Ufunguo + ## Ufundi wa Nakala ya Ufunguo na Usalama + \n\nAkaunti yako inalindwa na ufunguo wa siri. Ufunguo ni mfululizo mrefu wa herufi zisizo na mpangilio, ukitangulia na **nsec1**. Mtu yeyote aliye na ufikiaji wa ufunguo wako wa siri anaweza kuchapisha maudhui kwa kutumia kitambulisho chako. + \n\n- **Usiweke** ufunguo wako wa siri kwenye tovuti au programu usio na imani. + \n- Watengenezaji wa Amethyst **hawata** kuuliza ufunguo wako wa siri. + \n- **Tunashauri** kuwa na nakala rudufu salama ya ufunguo wako wa siri kwa ajili ya kupata akaunti. Tunapendekeza kutumia meneja wa nywila. + + Ufunguo wa siri (nsec) umeinakiliwa kwenye ubao wa kunakili + Nakili Ufunguo Wangu wa Siri + Uthibitishaji umeshindwa + Kosa + "Imeundwa na %1$s" + "Picha ya tuzo ya alama kwa %1$s" + Umepokea Tuzo Mpya ya Alama + Tuzo ya alama imetolewa kwa + Maudhui ya dokezo yamenakiliwa kwenye ubao wa kunakili + Imesakinishwa @npub ya mwandishi kwa ubao wa kunakili + Imesakinishwa ID ya andiko (@note1) kwa ubao wa kunakili + Chagua Matini + "<Haiwezi kuifanyia utawala ujumbe binafsi uliofichwa>\n\nUlinukuliwa katika mazungumzo ya faragha/yaliyofichwa kati ya %1$s na %2$s." + Ongeza Akaunti Mpya + Akaunti + Chagua Akaunti + Ongeza Akaunti Mpya + Akaunti Iliyo Hai + Ina ufunguo wa faragha + Soma tu, hakuna ufunguo wa faragha + Rudi + Chagua + Shiriki Kiungo cha Kivinjari + Shiriki + ID ya Mwandishi + ID ya Andiko + Nakili Matini + Futa + Acha Kufuata + Fuata + Omba Kufutwa + Amethyst itaomba kwamba andiko lako lifutwe kutoka kwa relays ambazo umekuwa umeunganishwa. Hakuna dhamana kwamba andiko lako litafutwa kabisa kutoka kwa relays hizo, au kutoka kwa relays nyingine ambapo linaweza kuwa limehifadhiwa. + Zuia + Futa + Zuia + Ripoti + Futa + Usionyeshe Tena + Barua Taka au Udanganyifu + Lugha chafu au tabia yenye chuki + Uigaaji mbaya + Uchi au yaliyomo ya kushangaza + Tabia Haramu + Kuzuia mtumiaji kutaficha maudhui yao kwenye programu yako. Andiko zako bado zinaweza kuonekana kwa umma, pamoja na watu wanaokuzuia. Watumiaji waliozuiliwa wameorodheshwa kwenye skrini ya Filta za Usalama. + + Ripoti Matusi + Ripoti zote zilizopostiwa zitaonekana kwa umma. + Toa muktadha wa ziada ikiwa unataka… + Muktadha wa Ziada + Sababu + Chagua sababu… + Chapisha Ripoti + Zuia na Ripoti + Zuia + Vialamisho + Vialamisho Binafsi + Vialamisho vya Umma + Ongeza kwenye Vialamisho Binafsi + Ongeza kwenye Vialamisho vya Umma + Ondoa kutoka kwenye Vialamisho Binafsi + Ondoa kutoka kwenye Vialamisho vya Umma + Huduma ya Kuchanganya Pochi + Inaidhinisha Nostr Secret kulipa zaps bila kuacha programu. Weka siri salama na tumia relay ya faragha ikiwezekana + Ufunguo wa Umma wa Kuchanganya Pochi + Kuchanganya Relay + Siri ya Kuchanganya Pochi + Onyesha ufunguo wa siri + nsec / ufunguo wa siri wa hex + Kiasi cha Ahadi katika Sats + Chapisha Kura + Sehemu zinazohitajika: + Wapokeaji wa Zaps + Maelezo ya msingi ya kura… + Chaguo %s + Maelezo ya chaguo cha kura + Sehemu za hiari: + Kima cha Chini cha Zaps + Kima cha Juu cha Zaps + Uamuzi + (0–100)% + Funga baada ya + siku + Kura imefungwa kwa kura mpya + Kiasi cha Zaps + Kura moja tu kwa mtumiaji inaruhusiwa kwenye aina hii ya kura + "Inatafuta Tukio %1$s" + Ongeza ujumbe wa umma + Ongeza ujumbe wa faragha + Ongeza ujumbe wa ankra + Asante kwa kazi yako yote! + Tengeneza na Ongeza + Waandishi wa kura hawawezi kupiga kura kwenye kura zao wenyewe. + Yaliyomo haya ni sawa tangu chapisho + Yaliyomo haya yamebadilika. Mwandishi huenda hakuyapata au kuyaidhinisha mabadiliko + Ongeza Picha + Ongeza Video + Ongeza Hati + Ongeza kwenye Ujumbe + Maelezo ya yaliyomo + Boti ya buluu kwenye ufukwe mweupe wa mchanga wakati wa jua + Aina ya Zap + Aina ya Zap kwa chaguo zote + Umma + Kila mtu anaweza kuona shughuli na ujumbe + Faragha + Mpokeaji na Mjumbe wanaweza kuona kila mmoja na kusoma ujumbe + Bila Kutambuliwa + Mpokeaji na umma hawajui ni nani aliyetuma malipo + Bila Zap + Hakuna alama katika Nostr, tu katika Lightning + Seva ya Faili + LnAddress au @Mtumiaji + imgur.com - inaaminika + Imgur inaweza kubadilisha faili + nostrimg.com - inaaminika + NostrImg inaweza kubadilisha faili + nostr.build - inaaminika + Nostr.build inaweza kubadilisha faili + nostrfiles.dev - inaaminika + Nostrfiles.dev inaweza kubadilisha faili + nostrcheck.me - inaaminika + nostrcheck.me inaweza kubadilisha faili + Imgur inayoweza kuthibitika (NIP-94) + Inachunguza ikiwa Imgur imebadilisha faili. NIP mpya: wateja wengine huenda wasiione + NostrImg inayoweza kuthibitika (NIP-94) + Inachunguza ikiwa NostrImg imebadilisha faili. NIP mpya: wateja wengine huenda wasiione + Nostr.build inayoweza kuthibitika (NIP-94) + Inachunguza ikiwa Nostr.build imebadilisha faili. NIP mpya: wateja wengine huenda wasiione + Nostrfiles.dev inayoweza kuthibitika (NIP-94) + Inachunguza ikiwa Nostrfiles.dev imebadilisha faili. NIP mpya: wateja wengine huenda wasiione + Nostrcheck.me inayoweza kuthibitika (NIP-94) + Inachunguza ikiwa Nostrcheck.me imebadilisha faili. NIP mpya: wateja wengine huenda wasiione + Relays Zako (NIP-95) + Faili zinahifadhiwa na relays zako. NIP mpya: hakikisha zinazisaidia + Usanidi wa Tor/Orbot + Unaweza kuunganisha kupitia usanidi wako wa Orbot + Kujitenga na Orbot/Tor yako? + Data yako itahamishwa mara moja kwenye mtandao wa kawaida + Ndio + Hapana + Orodha ya Kufuatilia + Yafuatayo Yote + Kote + ## Kuungana kupitia Tor na Orbot +\n\n1. Sakinisha [Orbot](https://play.google.com/store/apps/details?id=org.torproject.android) +\n2. Anza Orbot +\n3. Katika Orbot, angalia bandari ya Socks. Chaguo-msingi hutumia 9050 +\n4. Ikiwa ni lazima, badilisha bandari katika Orbot +\n5. Sanidi bandari ya Socks kwenye skrini hii +\n6. Bonyeza kitufe cha Kuamilisha kutumia Orbot kama mbadala + Bandari ya Socks ya Orbot + Nambari ya bandari sio sahihi + Tumia Orbot + Jitenge na Tor/Orbot yako + Ujumbe Binafsi + Inakuarifu wakati ujumbe binafsi unapowasili + Zaps Zilizopokelewa + Inakuarifu wakati mtu anapokuzap + %1$s sats + Kutoka kwa %1$s + kwa %1$s + Taarifa: + jiunge kwenye mazungumzo + Kitambulisho cha Mtumiaji au Kikundi + npub, nevent au hex + Unda + jiunge + Leo + Onyo la Yaliyomo + Chapisho hili lina yaliyomo nyeti ambayo baadhi ya watu wanaweza kuona kama ya kuchukiza au kusumbua + Daima ficha yaliyomo nyeti + Daima onyesha yaliyomo nyeti + Daima onyesha onyo za yaliyomo + Inapendekeza: + Chuja barua taka kutoka kwa wageni + Onya wakati machapisho yana ripoti kutoka kwa wafuatavyo wako + Alama Mpya ya Majibu + Hakuna aina za majibu zilizochaguliwa. Bonyeza kwa muda mrefu kubadilisha + Mzushi wa Zaps + Inaongeza kiasi lengwa cha sats kwa chapisho hili. Wateja wanaosaidia wanaweza kuonyesha hii kama bar ya maendeleo ili kuhimiza michango + Kiasi Lengo katika Sats + Mzushi wa Zaps kwa %1$s. Sats %2$s hadi kufikia lengo + Soma kutoka kwa Relay + Andika kwa Relay + Kuna kosa lililotokea jaribu kupata habari za Relay kutoka kwa %1$s + Mmiliki + Toleo + Programu + Wasiliana + NIPs Zinazoungwa mkono + Ada za Kuingia + URL ya Malipo + Vikwazo + Nchi + Lugha + Alama + Sera ya Kutuma + Urefu wa Ujumbe + Michango + Vichujio + Urefu wa Kitambulisho cha Michango + Awamu ya Chini + Vichwa vya Tukio vya Juu + Urefu wa Yaliyomo + PoW ya Chini + Uthibitishaji + Malipo + Cashu Token + Kukomboa + Hakuna Anwani ya Lightning Imewekwa + Kitufe kimekopwa kwenye ubao wa kunakili + Moja kwa Moja + Nje ya Mtandao + Imemalizika + Imepangwa + Moja kwa Moja Imekwama + Moja kwa Moja Imekwisha + Kutoka kunaondoa taarifa zako za eneo la kuhifadhia data. Hakikisha una nakala za funguo zako binafsi ili kuepuka kupoteza akaunti yako. Je, unataka kuendelea? + Mada Zinazofuatwa + Usanidi wa Relays + Moja kwa Moja + Jumuiya + Mazungumzo + Machapisho Yaliyoidhinishwa + Kikundi hiki hakitumii maelezo au kanuni. Ongea na mmiliki ili aongeze. + Jumuiya hii haitumii maelezo. Ongea na mmiliki ili aongeze. + Yaliyomo Nyeti + Inaongeza onyo la yaliyomo nyeti kabla ya kuonyesha yaliyomo hii + Mipangilio + Daima + Wi-Fi tu + Kamwe + Mfumo + Mwanga + Giza + Mapendeleo ya Programu + Lugha + Mandhari + Onesha Picha + Cheza Video + Onesha Hakikisho za URL + Soma Picha + Wahasibu + Walemavu. Bonyeza kuwasha sauti + Sauti imewashwa. Bonyeza kulegeza + Tafuta rekodi za eneo la kuhifadhia data na rekodi za mbali + Anwani ya Nostr ilithibitishwa + Anwani ya Nostr ilishindwa kuthibitishwa + Kuthibitisha Anwani ya Nostr + Chagua/Chagua Vingine Vyote + Chaguo-msingi + Chagua kiotomatiki kuendelea + Tuma Zaps kwa: + Wateja wanaosaidia watatuma Zaps kwa LNAddress au Wasifu wa Mtumiaji hapa chini badala ya yako + Tumia Mahali kama + Inaongeza Geohash ya eneo lako kwenye chapisho. Umma utajua kuwa uko ndani ya kilometa 5 (maili 3) ya eneo la sasa + Inaongeza onyo la yaliyomo nyeti kabla ya kuonyesha yaliyomo yako. Hii ni nzuri kwa yaliyomo yoyote ya NSFW au yaliyomo ambayo baadhi ya watu wanaweza kuona kama ya kuchukiza au kusumbua + Kipengee Kipya + Kuwezesha hali hii kunahitaji Amethyst kutuma ujumbe wa NIP-24 (Ufungaji wa Zawadi, Ujumbe wa Moja kwa Moja Uliosajiliwa, na Ujumbe wa Kikundi). NIP-24 ni mpya na wateja wengi bado hawajaijumuisha. Hakikisha mpokeaji anatumia mteja unaokubaliana. + Tumia + Umma + Binafsi + Kwa + Somo + Mada ya mazungumzo + "\@User1, @User2, @User3" + Wanachama wa kikundi hiki + Maelezo kwa wanachama + Kubadilisha jina kwa malengo mapya. + Kwa Interface ya Programu + Mada ya Giza, Mada ya Mwanga, au Mada ya Mfumo + Lisha picha na GIFs kiotomatiki + Cheza video na GIFs kiotomatiki + Onyesha hakikisho za URL kiotomatiki + Lini kusoma picha + Nakili URL kwa ubao wa kunakili + Nakili Kitambulisho cha Andiko kwa ubao wa kunakili + Imetengenezwa saa + Miongozo + Sasisha hali yako + Kosa katika uchambuzi wa ujumbe wa kosa + Kura zina uzito kulingana na kiasi cha zap. Unaweza kuweka kiasi cha chini ili kuepuka wahasibu na kiasi cha juu ili kuepuka wahasibu wakubwa kuchukua kura ya maoni. Tumia kiasi sawa katika sehemu zote mbili ili kuhakikisha kila kura inathaminiwa kwa kiasi sawa. Acha tupu ili kukubali kiasi chochote. + Haiwezekani kutuma zap + Tuma ujumbe kwa mtumiaji + Sawa From 939eb1bd8da0f8bf90410bfec8afed26494de725 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Fri, 8 Sep 2023 11:39:14 -0400 Subject: [PATCH 21/21] Fixes the lack of permission when using Android's PhotoPicker to upload videos (thumbnail fails to load and videos don't play because the Playback service doesn't have permission to run) --- .../vitorpamplona/amethyst/ui/MainActivity.kt | 20 +++++++++++++++---- .../amethyst/ui/actions/NewMediaView.kt | 4 +++- .../amethyst/ui/actions/NewPostView.kt | 4 ++-- 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/MainActivity.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/MainActivity.kt index d86fd04b3..26975f7c9 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/MainActivity.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/MainActivity.kt @@ -1,5 +1,6 @@ package com.vitorpamplona.amethyst.ui +import android.annotation.SuppressLint import android.content.Context import android.content.Intent import android.net.ConnectivityManager @@ -65,7 +66,10 @@ class MainActivity : AppCompatActivity() { themeViewModel.onChange(LocalPreferences.getTheme()) AmethystTheme(themeViewModel) { // A surface container using the 'background' color from the theme - Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colors.background) { + Surface( + modifier = Modifier.fillMaxSize(), + color = MaterialTheme.colors.background + ) { val accountStateViewModel: AccountStateViewModel = viewModel { AccountStateViewModel(this@MainActivity) } @@ -81,7 +85,8 @@ class MainActivity : AppCompatActivity() { .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) .build() - val connectivityManager = getSystemService(ConnectivityManager::class.java) as ConnectivityManager + val connectivityManager = + getSystemService(ConnectivityManager::class.java) as ConnectivityManager connectivityManager.requestNetwork(networkRequest, networkCallback) } @@ -153,7 +158,8 @@ class MainActivity : AppCompatActivity() { super.onCapabilitiesChanged(network, networkCapabilities) GlobalScope.launch(Dispatchers.IO) { - val hasMobileData = networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) + val hasMobileData = + networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) val hasWifi = networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) Log.d("NETWORKCALLBACK", "onCapabilitiesChanged: hasMobileData $hasMobileData") Log.d("NETWORKCALLBACK", "onCapabilitiesChanged: hasWifi $hasWifi") @@ -178,9 +184,15 @@ class MainActivity : AppCompatActivity() { class GetMediaActivityResultContract : ActivityResultContracts.GetContent() { + @SuppressLint("MissingSuperCall") override fun createIntent(context: Context, input: String): Intent { - return super.createIntent(context, input).apply { + // Force only images and videos to be selectable + // Force OPEN Document because of the resulting URI must be passed to the + // Playback service and the picker's permissions only allow the activity to read the URI + return Intent(Intent.ACTION_OPEN_DOCUMENT).apply { + addCategory(Intent.CATEGORY_OPENABLE) // Force only images and videos to be selectable + type = "*/*" putExtra(Intent.EXTRA_MIME_TYPES, arrayOf("image/*", "video/*")) } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaView.kt index 9a9900b2c..ab3a29fc6 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaView.kt @@ -3,6 +3,7 @@ package com.vitorpamplona.amethyst.ui.actions import android.graphics.Bitmap import android.net.Uri import android.os.Build +import android.util.Log import android.util.Size import android.widget.Toast import androidx.compose.foundation.Image @@ -215,7 +216,8 @@ fun ImageVideoPost(postViewModel: NewMediaModel, accountViewModel: AccountViewMo try { bitmap = resolver.loadThumbnail(it, Size(1200, 1000), null) } catch (e: Exception) { - postViewModel.imageUploadingError.emit("Unable to load file") + postViewModel.imageUploadingError.emit("Unable to load thumbnail, but the video can be uploaded") + Log.w("NewPostView", "Couldn't create thumbnail, but the video can be uploaded", e) } } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostView.kt index 5e6bdacb5..daf7d30dd 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostView.kt @@ -1370,8 +1370,8 @@ fun ImageVideoDescription( try { bitmap = resolver.loadThumbnail(uri, Size(1200, 1000), null) } catch (e: Exception) { - onError("Unable to load file") - Log.e("NewPostView", "Couldn't create thumbnail for $uri") + onError("Unable to load thumbnail") + Log.w("NewPostView", "Couldn't create thumbnail, but the video can be uploaded", e) } } }