From 36e309d58a70fe37165cbd40ceae8efe3f9aa204 Mon Sep 17 00:00:00 2001 From: otsmr Date: Wed, 14 Jan 2026 22:32:04 +0100 Subject: [PATCH] add new workflow for additional users --- .gitmodules | 4 + l10n.yaml | 4 +- lib/src/localization/app_de.arb | 468 ----- lib/src/localization/app_en.arb | 498 ----- .../generated/app_localizations.dart | 76 +- .../generated/app_localizations_de.dart | 43 +- .../generated/app_localizations_en.dart | 42 +- .../generated/app_localizations_sv.dart | 1615 +++++++++++++++++ lib/src/localization/translations | 1 + .../api/websocket/client_to_server.pb.dart | 86 +- .../websocket/client_to_server.pbjson.dart | 125 +- .../protobuf/api/websocket/error.pbenum.dart | 3 + .../protobuf/api/websocket/error.pbjson.dart | 3 +- .../api/websocket/server_to_client.pb.dart | 37 +- .../websocket/server_to_client.pbjson.dart | 148 +- lib/src/providers/purchases.provider.dart | 40 +- lib/src/services/api.service.dart | 21 +- lib/src/views/settings/account.view.dart | 33 +- .../settings/developer/developer.view.dart | 19 + .../subscription/additional_users.view.dart | 161 +- .../select_additional_users.view.dart | 256 +++ .../subscription/subscription.view.dart | 229 +-- .../subscription.view.dart | 103 +- 23 files changed, 2452 insertions(+), 1563 deletions(-) delete mode 100644 lib/src/localization/app_de.arb delete mode 100644 lib/src/localization/app_en.arb create mode 100644 lib/src/localization/generated/app_localizations_sv.dart create mode 160000 lib/src/localization/translations create mode 100644 lib/src/views/settings/subscription/select_additional_users.view.dart diff --git a/.gitmodules b/.gitmodules index 4a27db4..310936e 100644 --- a/.gitmodules +++ b/.gitmodules @@ -2,3 +2,7 @@ [submodule "dependencies"] path = dependencies url = https://github.com/twonlyapp/twonly-app-dependencies.git +[submodule "lib/src/localization/translations"] + path = lib/src/localization/translations + url = ssh://git@git.twonly.eu:22222/twonly/twonly-translations.git + # https://git.twonly.eu/twonly/twonly-translations diff --git a/l10n.yaml b/l10n.yaml index 438687a..22e4c52 100644 --- a/l10n.yaml +++ b/l10n.yaml @@ -1,5 +1,5 @@ -arb-dir: lib/src/localization -template-arb-file: app_en.arb +arb-dir: lib/src/localization/translations +template-arb-file: en.arb output-localization-file: app_localizations.dart untranslated-messages-file: build/l10n.log output-dir: lib/src/localization/generated diff --git a/lib/src/localization/app_de.arb b/lib/src/localization/app_de.arb deleted file mode 100644 index fd3a2c0..0000000 --- a/lib/src/localization/app_de.arb +++ /dev/null @@ -1,468 +0,0 @@ -{ - "@@locale": "de", - "registerTitle": "Willkommen bei twonly!", - "registerSlogan": "twonly, eine private und sichere Möglichkeit um mit Freunden in Kontakt zu bleiben.", - "onboardingWelcomeTitle": "Willkommen bei twonly!", - "onboardingWelcomeBody": "Erlebe eine private und sichere Möglichkeit mit Freunden in Kontakt zu bleiben, indem du spontane Bilder teilst.", - "onboardingE2eTitle": "Unbekümmert teilen", - "onboardingE2eBody": "Genieße durch die Ende-zu-Ende-Verschlüsselung die Gewissheit, dass nur du und deine Freunde die geteilten Momente sehen können.", - "onboardingFocusTitle": "Fokussiere dich auf das Teilen von Momenten", - "onboardingFocusBody": "Verabschiede dich von süchtig machenden Funktionen! twonly wurde für das Teilen von Momenten ohne nutzlose Ablenkungen oder Werbung entwickelt.", - "onboardingSendTwonliesTitle": "twonlies senden", - "onboardingSendTwonliesBody": "Teile Momente sicher mit deinem Partner. twonly stellt sicher, dass nur dein Partner sie öffnen kann, sodass deine Momente mit deinem Partner eine two(o)nly Sache bleiben!", - "onboardingNotProductTitle": "Du bist nicht das Produkt!", - "onboardingNotProductBody": "twonly wird durch Spenden und ein optionales Abonnement finanziert. Deine Daten werden niemals verkauft.", - "onboardingBuyOneGetTwoTitle": "Kaufe eins, bekomme zwei", - "onboardingBuyOneGetTwoBody": "twonly benötigt immer mindestens zwei Personen, daher erhältst du beim Kauf eine zweite kostenlose Lizenz für deinen twonly-Partner.", - "onboardingGetStartedTitle": "Auf geht's", - "onboardingGetStartedBody": "Du kannst twonly kostenlos im Preview-Modus testen. In diesem Modus kannst du von anderen gefunden werden und Bilder oder Videos empfangen, aber du kannst selbst keine senden.", - "onboardingTryForFree": "Jetzt registrieren", - "registerUsernameSlogan": "Bitte wähle einen Benutzernamen, damit dich andere finden können!", - "registerUsernameDecoration": "Benutzername", - "registerUsernameLimits": "Der Benutzername muss mindestens 3 Zeichen lang sein.", - "registerProofOfWorkFailed": "Beim Captcha-Test gab es ein Problem. Bitte versuche es erneut.", - "registerSubmitButton": "Jetzt registrieren!", - "registerTwonlyCodeText": "Hast du einen twonly-Code erhalten? Dann löse ihn entweder direkt hier oder später ein!", - "registerTwonlyCodeLabel": "twonly-Code", - "newMessageTitle": "Neue Nachricht", - "chatsTapToSend": "Klicke, um dein erstes Bild zu teilen.", - "cameraPreviewSendTo": "Senden an", - "shareImageTitle": "Teilen mit", - "shareImageBestFriends": "Beste Freunde", - "shareImagePinnedContacts": "Angeheftet", - "shareImagedEditorSendImage": "Senden", - "shareImagedEditorShareWith": "Teilen mit", - "shareImagedEditorSaveImage": "Speichern", - "shareImagedEditorSavedImage": "Gespeichert", - "shareImagedSelectAll": "Alle auswählen", - "shareImageAllUsers": "Alle Kontakte", - "shareImageAllTwonlyWarning": "twonlies können nur an verifizierte Kontakte gesendet werden!", - "shareImageSearchAllContacts": "Alle Kontakte durchsuchen", - "shareImageUserNotVerified": "Benutzer ist nicht verifiziert", - "shareImageUserNotVerifiedDesc": "twonlies können nur an verifizierte Nutzer gesendet werden. Um einen Nutzer zu verifizieren, gehe auf sein Profil und auf „Sicherheitsnummer verifizieren“.", - "shareImageShowArchived": "Archivierte Benutzer anzeigen", - "startNewChatSearchHint": "Name, Benutzername oder Gruppenname", - "searchUsernameInput": "Benutzername", - "searchUsernameTitle": "Benutzernamen suchen", - "searchUserNamePreview": "Um dich und andere twonly Benutzer vor Spam und Missbrauch zu schützen, ist es nicht möglich, im Preview-Modus nach anderen Personen zu suchen. Andere Benutzer können dich finden und deren Anfragen werden dann hier angezeigt!", - "selectSubscription": "Abo auswählen", - "searchUsernameNotFound": "Benutzername nicht gefunden", - "searchUsernameNotFoundBody": "Es wurde kein Benutzer mit dem Benutzernamen \"{username}\" gefunden.", - "searchUsernameNewFollowerTitle": "Folgeanfragen", - "searchUsernameQrCodeBtn": "QR-Code scannen", - "searchUserNamePending": "Ausstehend", - "searchUserNameBlockUserTooltip": "Benutzer ohne Benachrichtigung blockieren.", - "searchUserNameRejectUserTooltip": "Die Anfrage ablehnen und den Anfragenden informieren.", - "searchUserNameArchiveUserTooltip": "Benutzer archivieren. Du wirst informiert sobald er deine Anfrage akzeptiert.", - "userFound": "{username} gefunden", - "userFoundBody": "Möchtest du eine Folgeanfrage stellen?", - "chatListViewSearchUserNameBtn": "Füge deinen ersten twonly-Kontakt hinzu!", - "chatListViewSendFirstTwonly": "Sende dein erstes twonly!", - "chatListDetailInput": "Nachricht eingeben", - "userDeletedAccount": "Der Nutzer hat sein Konto gelöscht.", - "contextMenuUserProfile": "Userprofil", - "contextMenuVerifyUser": "Verifizieren", - "contextMenuArchiveUser": "Archivieren", - "contextMenuUndoArchiveUser": "Archivierung aufheben", - "startNewChatTitle": "Kontakt wählen", - "startNewChatNewContact": "Neuer Kontakt", - "startNewChatYourContacts": "Deine Kontakte", - "contextMenuOpenChat": "Chat", - "contextMenuPin": "Anheften", - "contextMenuUnpin": "Lösen", - "mediaViewerAuthReason": "Bitte authentifiziere dich, um diesen twonly zu sehen!", - "mediaViewerTwonlyTapToOpen": "Tippe um den twonly zu öffnen!", - "messageSendState_Received": "Empfangen", - "messageSendState_Opened": "Geöffnet", - "messageSendState_Send": "Gesendet", - "messageSendState_Sending": "Wird gesendet", - "messageSendState_TapToLoad": "Tippe zum Laden", - "messageSendState_Loading": "Herunterladen", - "messageStoredInGallery": "Gespeichert", - "messageReopened": "Erneut geöffnet", - "imageEditorDrawOk": "Zeichnung machen", - "settingsTitle": "Einstellungen", - "settingsChats": "Chats", - "settingsStorageData": "Daten und Speicher", - "settingsStorageDataStoreInGTitle": "In der Galerie speichern", - "settingsStorageDataStoreInGSubtitle": "Speichere Bilder zusätzlich in der Systemgalerie.", - "settingsStorageDataMediaAutoDownload": "Automatischer Mediendownload", - "settingsStorageDataAutoDownMobile": "Bei Nutzung mobiler Daten", - "settingsStorageDataAutoDownWifi": "Bei Nutzung von WLAN", - "settingsPreSelectedReactions": "Vorgewählte Reaktions-Emojis", - "settingsPreSelectedReactionsError": "Es können maximal 12 Reaktionen ausgewählt werden.", - "settingsProfile": "Profil", - "settingsProfileCustomizeAvatar": "Avatar anpassen", - "settingsProfileEditDisplayName": "Anzeigename", - "settingsProfileEditDisplayNameNew": "Neuer Anzeigename", - "settingsAccount": "Konto", - "settingsSubscription": "Abonnement", - "settingsAppearance": "Erscheinungsbild", - "settingsPrivacy": "Datenschutz", - "settingsPrivacyBlockUsers": "Benutzer blockieren", - "settingsPrivacyBlockUsersDesc": "Blockierte Benutzer können nicht mit dir kommunizieren. Du kannst einen blockierten Benutzer jederzeit wieder entsperren.", - "settingsPrivacyBlockUsersCount": "{len} Kontakt(e)", - "settingsNotification": "Benachrichtigung", - "settingsNotifyTroubleshooting": "Fehlersuche", - "settingsNotifyTroubleshootingDesc": "Hier klicken, wenn Probleme beim Empfang von Push-Benachrichtigungen auftreten.", - "settingsNotifyTroubleshootingNoProblem": "Kein Problem festgestellt", - "settingsNotifyTroubleshootingNoProblemDesc": "Klicke auf OK, um eine Testbenachrichtigung zu erhalten. Wenn du auch nach 10 Minuten warten keine Nachricht erhältst, sende uns bitte dein Diagnoseprotokoll unter Einstellungen > Hilfe > Diagnoseprotokoll, damit wir uns das Problem ansehen können.", - "settingsHelp": "Hilfe", - "settingsHelpFAQ": "FAQ", - "feedbackTooltip": "Feedback zur Verbesserung von twonly geben.", - "settingsHelpContactUs": "Kontaktiere uns", - "contactUsFaq": "FAQ schon gelesen?", - "contactUsEmojis": "Wie fühlst du dich? (optional)", - "contactUsSelectOption": "Bitte wähle eine Option", - "contactUsReason": "Sag uns, warum du uns kontaktierst", - "contactUsMessage": "Wenn du eine Antwort erhalten möchtest, füge bitte deine E-Mail-Adresse hinzu, damit wir dich kontaktieren können.", - "contactUsYourMessage": "Deine Nachricht", - "contactUsMessageTitle": "Erzähl uns, was los ist", - "contactUsReasonNotWorking": "Etwas funktioniert nicht", - "contactUsReasonFeatureRequest": "Funktionsanfrage", - "contactUsReasonQuestion": "Frage", - "contactUsReasonFeedback": "Feedback", - "contactUsReasonOther": "Sonstiges", - "contactUsIncludeLog": "Debug-Protokoll anhängen.", - "contactUsWhatsThat": "Was ist das?", - "contactUsLastWarning": "Dies sind die Informationen, die an uns gesendet werden. Bitte prüfen Sie sie und klicke dann auf „Abschicken“.", - "contactUsSuccess": "Feedback erfolgreich übermittelt!", - "contactUsShortcut": "Feedback-Symbol ausblenden", - "settingsHelpDiagnostics": "Diagnoseprotokoll", - "settingsHelpVersion": "Version", - "settingsHelpLicenses": "Lizenzen (Source-Code)", - "settingsHelpCredits": "Lizenzen (Bilder)", - "settingsHelpImprint": "Impressum & Datenschutzrichtlinie", - "settingsHelpTerms": "Nutzungsbedingungen", - "settingsAppearanceTheme": "Theme", - "settingsAccountDeleteAccount": "Konto löschen", - "settingsAccountDeleteAccountWithBallance": "Im nächsten Schritt kannst du auswählen, was du mit dem Restguthaben ({credit}) machen willst.", - "settingsAccountDeleteAccountNoInternet": "Zum Löschen deines Accounts ist eine Internetverbindung erforderlich.", - "settingsAccountDeleteAccountNoBallance": "Wenn du dein Konto gelöscht hast, gibt es keinen Weg zurück.", - "settingsAccountDeleteModalTitle": "Bist du sicher?", - "settingsAccountDeleteModalBody": "Dein Konto wird gelöscht. Es gibt keine Möglichkeit, es wiederherzustellen.", - "contactVerifyNumberTitle": "Sicherheitsnummer verifizieren", - "contactVerifyNumberTapToScan": "Zum Scannen tippen", - "contactVerifyNumberMarkAsVerified": "Als verifiziert markieren", - "contactVerifyNumberClearVerification": "Verifizierung aufheben", - "contactVerifyNumberLongDesc": "Um die Ende-zu-Ende-Verschlüsselung mit {username} zu verifizieren, vergleiche die Zahlen mit ihrem Gerät. Die Person kann auch deinen Code mit ihrem Gerät scannen.", - "contactNickname": "Spitzname", - "contactNicknameNew": "Neuer Spitzname", - "contactBlock": "Blockieren", - "contactRemove": "Benutzer löschen", - "contactRemoveTitle": "{username} löschen?", - "contactRemoveBody": "Entferne den Benutzer und lösche den Chat sowie alle zugehörigen Mediendateien dauerhaft. Dadurch wird auch DEIN KONTO VON DEM TELEFON DEINES KONTAKTS gelöscht.", - "deleteAllContactMessages": "Textnachrichten löschen", - "deleteAllContactMessagesBody": "Dadurch werden alle Nachrichten, ausgenommen gespeicherte Mediendateien, in deinem Chat mit {username} gelöscht. Dies löscht NICHT die auf dem Gerät von {username} gespeicherten Nachrichten!", - "contactBlockTitle": "Blockiere {username}", - "contactBlockBody": "Ein blockierter Benutzer kann dir keine Nachrichten mehr senden, und sein Profil ist nicht mehr sichtbar. Um die Blockierung eines Benutzers aufzuheben, navigiere einfach zu Einstellungen > Datenschutz > Blockierte Benutzer.", - "undo": "Rückgängig", - "redo": "Wiederholen", - "next": "Weiter", - "submit": "Abschicken", - "close": "Schließen", - "cancel": "Abbrechen", - "edit": "Bearbeiten", - "ok": "Ok", - "now": "Jetzt", - "you": "Du", - "minutesShort": "Min.", - "image": "Bild", - "video": "Video", - "react": "Reagieren", - "reply": "Antworten", - "copy": "Kopieren", - "delete": "Löschen", - "info": "Info", - "disable": "Deaktiviern", - "enable": "Aktivieren", - "switchFrontAndBackCamera": "Zwischen Front- und Rückkamera wechseln.", - "addTextItem": "Text", - "protectAsARealTwonly": "Als echtes twonly senden!", - "addDrawing": "Zeichnung", - "addEmoji": "Emoji", - "toggleFlashLight": "Taschenlampe umschalten", - "toggleHighQuality": "Bessere Auflösung umschalten", - "searchUsernameNotFoundLong": "\"{username}\" ist kein twonly-Benutzer. Bitte überprüfe den Benutzernamen und versuche es erneut.", - "errorUnknown": "Ein unerwarteter Fehler ist aufgetreten. Bitte versuche es später erneut.", - "errorBadRequest": "Die Anfrage konnte vom Server aufgrund einer fehlerhaften Syntax nicht verstanden werden. Bitte überprüfe deine Eingabe und versuche es erneut.", - "errorTooManyRequests": "Du hast in kurzer Zeit zu viele Anfragen gestellt. Bitte warte einen Moment, bevor du es erneut versuchst.", - "errorInternalError": "Der Server ist derzeit nicht verfügbar. Bitte versuche es später erneut.", - "errorInvalidInvitationCode": "Der von dir angegebene Einladungscode ist ungültig. Bitte überprüfe den Code und versuche es erneut.", - "errorUsernameAlreadyTaken": "Der Benutzername ist bereits vergeben.", - "errorSignatureNotValid": "Die bereitgestellte Signatur ist nicht gültig. Bitte überprüfe deine Anmeldeinformationen und versuche es erneut.", - "errorUsernameNotFound": "Der eingegebene Benutzername existiert nicht. Bitte überprüfe die Schreibweise oder erstelle ein neues Konto.", - "errorUsernameNotValid": "Der von dir angegebene Benutzername entspricht nicht den erforderlichen Kriterien. Bitte wähle einen gültigen Benutzernamen.", - "errorInvalidPublicKey": "Der von dir angegebene öffentliche Schlüssel ist ungültig. Bitte überprüfe den Schlüssel und versuche es erneut.", - "errorSessionAlreadyAuthenticated": "Du bist bereits angemeldet. Bitte melde dich ab, wenn du dich mit einem anderen Konto anmelden möchtest.", - "errorSessionNotAuthenticated": "Deine Sitzung ist nicht authentifiziert. Bitte melde dich an, um fortzufahren.", - "errorOnlyOneSessionAllowed": "Es ist nur eine aktive Sitzung pro Benutzer erlaubt. Bitte melde dich von anderen Geräten ab, um fortzufahren.", - "upgradeToPaidPlan": "Upgrade auf einen kostenpflichtigen Plan.", - "upgradeToPaidPlanButton": "Auf {planId} upgraden{sufix}", - "partOfPaidPlanOf": "Du bist Teil des bezahlten Plans von {username}!", - "errorNotEnoughCredit": "Du hast nicht genügend twonly-Guthaben.", - "errorPlanLimitReached": "Du hast das Limit deines Plans erreicht. Bitte upgrade deinen Plan.", - "errorPlanNotAllowed": "Dieses Feature ist in deinem aktuellen Plan nicht verfügbar.", - "errorVoucherInvalid": "Der eingegebene Gutschein-Code ist nicht gültig.", - "errorPlanUpgradeNotYearly": "Das Upgrade des Plans muss jährlich bezahlt werden, da der aktuelle Plan ebenfalls jährlich abgerechnet wird.", - "proFeature1": "✓ Unbegrenzte Medien-Datei-Uploads", - "proFeature2": "✓ 1 zusätzlicher Plus Benutzer", - "proFeature3": "✓ Flammen wiederherstellen", - "proFeature4": "✓ twonly unterstützen", - "year": "Jahr", - "month": "Monat", - "yearly": "Jährlich", - "monthly": "Monatlich", - "familyFeature1": "✓ Unbegrenzte Medien-Datei-Uploads", - "familyFeature2": "✓ 4 zusätzliche Plus Benutzer", - "familyFeature3": "✓ Flammen wiederherstellen", - "familyFeature4": "✓ twonly unterstützen", - "redeemUserInviteCode": "Oder löse einen twonly-Code ein.", - "freeFeature1": "✓ 10 Medien-Datei-Uploads pro Tag", - "plusFeature1": "✓ Unbegrenzte Medien-Datei-Uploads", - "plusFeature2": "✓ Zusatzfunktionen (coming-soon)", - "transactionHistory": "Transaktionshistorie", - "currentBalance": "Dein Guthaben", - "manageAdditionalUsers": "Zusätzliche Benutzer verwalten", - "manageSubscription": "Abonnement verwalten", - "nextPayment": "Nächste Zahlung", - "open": "Offene", - "buy": "Kaufen", - "createOrRedeemVoucher": "Gutschein erstellen oder einlösen", - "subscriptionRefund": "Wenn du ein Upgrade durchführst, erhältst du eine Rückerstattung von {refund} für dein aktuelles Abonnement.", - "createVoucher": "Gutschein kaufen", - "createVoucherDesc": "Wähle den Wert des Gutscheins. Der Wert des Gutschein wird von deinem twonly-Guthaben abgezogen.", - "redeemVoucher": "Gutschein einlösen", - "redeemUserInviteCodeTitle": "twonly-Code einlösen", - "redeemUserInviteCodeSuccess": "Dein Plan wurde erfolgreich angepasst.", - "voucherCreated": "Gutschein wurde erstellt", - "openVouchers": "Offene Gutscheine", - "enterVoucherCode": "Gutschein Code eingeben", - "voucherRedeemed": "Gutschein eingelöst", - "requestedVouchers": "Beantragte Gutscheine", - "redeemedVouchers": "Eingelöste Gutscheine", - "transactionCash": "Bargeldtransaktion", - "transactionPlanUpgrade": "Planupgrade", - "transactionRefund": "Rückerstattung", - "transactionAutoRenewal": "Automatische Verlängerung", - "refund": "Rückerstattung", - "transactionThanksForTesting": "Danke fürs Testen", - "transactionUnknown": "Unbekannte Transaktion", - "transactionVoucherCreated": "Gutschein erstellt", - "transactionVoucherRedeemed": "Gutschein eingelöst", - "checkoutOptions": "Optionen", - "checkoutPayYearly": "Jährlich bezahlen", - "checkoutTotal": "Gesamt", - "selectPaymentMethod": "Zahlungsmethode auswählen", - "twonlyCredit": "twonly-Guthaben", - "notEnoughCredit": "Du hast nicht genügend Guthaben!", - "chargeCredit": "Guthaben aufladen", - "autoRenewal": "Automatische Verlängerung", - "autoRenewalDesc": "Du kannst dies jederzeit ändern.", - "autoRenewalLongDesc": "Wenn dein Abonnement ausläuft, wirst du automatisch auf den Preview-Plan zurückgestuft. Wenn du die automatische Verlängerung aktivierst, vergewissere dich bitte, dass du über genügend Guthaben für die automatische Erneuerung verfügst. Wir werden dich rechtzeitig vor der automatischen Erneuerung benachrichtigen.", - "planSuccessUpgraded": "Dein Plan wurde erfolgreich aktualisiert.", - "checkoutSubmit": "Kostenpflichtig bestellen", - "additionalUsersList": "Ihre zusätzlichen Benutzer", - "additionalUsersPlusTokens": "twonly-Codes für \"Plus\"-Benutzer", - "additionalUsersFreeTokens": "twonly-Codes für \"Free\"-Benutzer", - "planNotAllowed": "In deinem aktuellen Plan kannst du keine Mediendateien versenden. Aktualisiere deinen Plan jetzt, um die Mediendatei zu senden.", - "planLimitReached": "Du hast dein Planlimit für heute erreicht. Aktualisiere deinen Plan jetzt, um die Mediendatei zu senden.", - "galleryDelete": "Datei löschen", - "galleryExport": "In Galerie exportieren", - "galleryExportSuccess": "Erfolgreich in der Gallery gespeichert.", - "galleryDetails": "Details anzeigen", - "settingsResetTutorials": "Tutorials erneut anzeigen", - "settingsResetTutorialsDesc": "Klicke hier, um bereits angezeigte Tutorials erneut anzuzeigen.", - "settingsResetTutorialsSuccess": "Tutorials werden erneut angezeigt.", - "tutorialChatListSearchUsersTitle": "Freunde finden und Freundschaftsanfragen verwalten", - "tutorialChatListSearchUsersDesc": "Wenn du die Benutzernamen deiner Freunde kennst, kannst du sie hier suchen und eine Freundschaftsanfrage senden. Außerdem siehst du hier alle Anfragen von anderen Nutzern, die du annehmen oder blockieren kannst.", - "tutorialChatListContextMenuTitle": "Klicke lange auf den Kontakt, um das Kontextmenü zu öffnen.", - "tutorialChatListContextMenuDesc": "Mit dem Kontextmenü kannst du deine Kontakte anheften, archivieren und verschiedene Aktionen durchführen. Halte dazu einfach den Kontakt lange gedrückt und bewege dann deinen Finger auf die gewünschte Option oder tippe direkt darauf.", - "tutorialChatMessagesVerifyShieldTitle": "Verifiziere deine Kontakte!", - "tutorialChatMessagesVerifyShieldDesc": "twonly nutzt das Signal-Protokoll für eine sichere Ende-zu-Ende Verschlüsselung. Bei der ersten Kontaktaufnahme wird dafür der öffentliche Identitätsschlüssel von deinem Kontakt heruntergeladen. Um sicherzustellen, dass dieser Schlüssel nicht von Dritten ausgetauscht wurde, solltest du ihn mit deinem Freund vergleichen, wenn ihr euch persönlich trefft. Sobald du den Benutzer verifiziert hast, kannst du auch beim verschicken von Bildern und Videos den twonly-Modus aktivieren.", - "tutorialChatMessagesReopenMessageTitle": "Bilder und Videos erneut öffnen", - "tutorialChatMessagesReopenMessageDesc": "Wenn dein Freund dir ein Bild oder Video mit unendlicher Anzeigezeit gesendet hat, kannst du es bis zum Neustart der App jederzeit erneut öffnen. Um dies zu tun, musst du einfach doppelt auf die Nachricht klicken. Dein Freund erhält dann eine Benachrichtigung, dass du das Bild erneut angesehen hast.", - "memoriesEmpty": "Sobald du Bilder oder Videos speicherst, landen sie hier in deinen Erinnerungen.", - "deleteTitle": "Bist du dir sicher?", - "deleteOkBtnForAll": "Für alle löschen", - "deleteOkBtnForMe": "Für mich löschen", - "deleteImageTitle": "Bist du dir sicher?", - "deleteImageBody": "Das Bild wird unwiderruflich gelöscht.", - "backupNoticeTitle": "Kein Backup konfiguriert", - "backupNoticeDesc": "Wenn du dein Gerät wechselst oder verlierst, kann ohne Backup niemand dein Account wiederherstellen. Sichere deshalb deine Daten.", - "backupNoticeLater": "Später erinnern", - "backupNoticeOpenBackup": "Backup erstellen", - "backupPending": "Ausstehend", - "backupFailed": "Fehlgeschlagen", - "backupSuccess": "Erfolgreich", - "backupTwonlySafeDesc": "Sichere deine twonly-Identität, da dies die einzige Möglichkeit ist, dein Konto wiederherzustellen, wenn du die App deinstallierst oder dein Handy verlierst.", - "backupServer": "Server", - "backupMaxBackupSize": "max. Backup-Größe", - "backupStorageRetention": "Speicheraufbewahrung", - "backupLastBackupDate": "Letztes Backup", - "backupLastBackupSize": "Backup-Größe", - "backupLastBackupResult": "Ergebnis", - "deleteBackupTitle": "Bist du sicher?", - "backupNoPasswordRecovery": "Aufgrund des Sicherheitssystems von twonly gibt es (derzeit) keine Funktion zur Wiederherstellung des Passworts. Daher musst du dir dein Passwort merken oder, besser noch, aufschreiben.", - "deleteBackupBody": "Ohne ein Backup kannst du dein Benutzerkonto nicht wiederherstellen.", - "backupData": "Daten-Backup", - "backupDataDesc": "Das Daten-Backup enthält neben deiner twonly-Identität auch alle deine Mediendateien. Dieses Backup ist ebenfalls verschlüsselt, wird jedoch lokal gespeichert. Du musst es dann manuell auf deinen Laptop oder ein Gerät deiner Wahl kopieren.", - "backupInsecurePassword": "Unsicheres Passwort", - "backupInsecurePasswordDesc": "Das gewählte Passwort ist sehr unsicher und kann daher leicht von Angreifern erraten werden. Bitte wähle ein sicheres Passwort.", - "backupInsecurePasswordOk": "Trotzdem fortfahren", - "backupInsecurePasswordCancel": "Erneut versuchen", - "backupTwonlySafeLongDesc": "twonly hat keine zentralen Benutzerkonten. Während der Installation wird ein Schlüsselpaar erstellt, das aus einem öffentlichen und einem privaten Schlüssel besteht. Der private Schlüssel wird nur auf deinem Gerät gespeichert, um ihn vor unbefugtem Zugriff zu schützen. Der öffentliche Schlüssel wird auf den Server hochgeladen und mit deinem gewählten Benutzernamen verknüpft, damit andere dich finden können.\n\ntwonly Backup erstellt regelmäßig ein verschlüsseltes, anonymes Backup deines privaten Schlüssels zusammen mit deinen Kontakten und Einstellungen. Dein Benutzername und das gewählte Passwort reichen aus, um diese Daten auf einem anderen Gerät wiederherzustellen.", - "backupSelectStrongPassword": "Wähle ein sicheres Passwort. Dies ist erforderlich, wenn du dein twonly Backup wiederherstellen möchtest.", - "password": "Passwort", - "passwordRepeated": "Passwort wiederholen", - "passwordRepeatedNotEqual": "Passwörter stimmen nicht überein.", - "backupPasswordRequirement": "Das Passwort muss mindestens 8 Zeichen lang sein.", - "backupExpertSettings": "Experteneinstellungen", - "backupEnableBackup": "Automatische Sicherung aktivieren", - "backupOwnServerDesc": "Speichere dein twonly Backup auf einem Server deiner Wahl.", - "backupUseOwnServer": "Server verwenden", - "backupResetServer": "Standardserver verwenden", - "backupTwonlySaveNow": "Jetzt speichern", - "backupChangePassword": "Password ändern", - "inviteFriends": "Freunde einladen", - "inviteFriendsShareBtn": "Teilen", - "inviteFriendsShareText": "Wechseln wir zu twonly: {url}", - "appOutdated": "Deine Version von twonly ist veraltet.", - "appOutdatedBtn": "Jetzt aktualisieren.", - "doubleClickToReopen": "Doppelklicken zum\nerneuten Öffnen.", - "uploadLimitReached": "Das Upload-Limit wurde\nerreicht. Upgrade auf Pro\noder warte bis morgen.", - "retransmissionRequested": "Wird erneut versucht.", - "testPaymentMethod": "Vielen Dank für dein Interesse an einem kostenpflichtigen Tarif. Die kostenpflichtigen Pläne sind derzeit noch deaktiviert. Sie werden aber bald aktiviert!", - "openChangeLog": "Changelog automatisch öffnen", - "reportUserTitle": "Melde {username}", - "reportUserReason": "Meldegrund", - "reportUser": "Benutzer melden", - "newDeviceRegistered": "Du hast dich auf einem anderen Gerät angemeldet. Daher wurdest du hier abgemeldet.", - "tabToRemoveEmoji": "Tippen um zu entfernen", - "quotedMessageWasDeleted": "Die zitierte Nachricht wurde gelöscht.", - "messageWasDeleted": "Nachricht wurde gelöscht.", - "messageWasDeletedShort": "Gelöscht", - "sent": "Versendet", - "sentTo": "Zugestellt an", - "received": "Empfangen", - "opened": "Geöffnet", - "waitingForInternet": "Warten auf Internet", - "editHistory": "Bearbeitungshistorie", - "archivedChats": "Archivierte Chats", - "durationShortSecond": "Sek.", - "durationShortMinute": "Min.", - "durationShortHour": "Std.", - "durationShortDays": "{count, plural, =1{1 Tag} other{{count} Tage}}", - "contacts": "Kontakte", - "groups": "Gruppen", - "newGroup": "Neue Gruppe", - "selectMembers": "Mitglieder auswählen", - "selectGroupName": "Gruppennamen wählen", - "groupNameInput": "Gruppennamen", - "groupMembers": "Mitglieder", - "createGroup": "Gruppe erstellen", - "addMember": "Mitglied hinzufügen", - "leaveGroup": "Gruppe verlassen", - "createContactRequest": "Kontaktanfrage erstellen", - "contactRequestSend": "Kontakanfrage gesendet", - "makeAdmin": "Zum Admin machen", - "removeAdmin": "Als Admin entfernen", - "removeFromGroup": "Aus Gruppe entfernen", - "admin": "Admin", - "revokeAdminRightsTitle": "Adminrechte von {username} entfernen?", - "revokeAdminRightsOkBtn": "Als Admin entfernen", - "makeAdminRightsTitle": "{username} zum Admin machen?", - "makeAdminRightsBody": "{username} wird diese Gruppe und ihre Mitglieder bearbeiten können.", - "makeAdminRightsOkBtn": "Zum Admin machen", - "updateGroup": "Gruppe aktualisieren", - "alreadyInGroup": "Bereits Mitglied", - "removeContactFromGroupTitle": "{username} aus dieser Gruppe entfernen?", - "youChangedGroupName": "Du hast den Gruppennamen zu „{newGroupName}“ geändert.", - "makerChangedGroupName": "{maker} hat den Gruppennamen zu „{newGroupName}“ geändert.", - "youCreatedGroup": "Du hast die Gruppe erstellt.", - "makerCreatedGroup": "{maker} hat die Gruppe erstellt.", - "youRemovedMember": "Du hast {affected} aus der Gruppe entfernt.", - "makerRemovedMember": "{maker} hat {affected} aus der Gruppe entfernt.", - "youAddedMember": "Du hast {affected} zur Gruppe hinzugefügt.", - "makerAddedMember": "{maker} hat {affected} zur Gruppe hinzugefügt.", - "youMadeAdmin": "Du hast {affected} zum Administrator gemacht.", - "makerMadeAdmin": "{maker} hat {affected} zum Administrator gemacht.", - "youRevokedAdminRights": "Du hast {affectedR} die Administratorrechte entzogen.", - "makerRevokedAdminRights": "{maker} hat {affectedR} die Administratorrechte entzogen.", - "youLeftGroup": "Du hast die Gruppe verlassen.", - "makerLeftGroup": "{maker} hat die Gruppe verlassen.", - "groupActionYou": "dich", - "groupActionYour": "deine", - "settingsBackup": "Backup", - "twonlySafeRecoverTitle": "Recovery", - "twonlySafeRecoverDesc": "Wenn du ein Backup mit twonly Backup erstellt hast, kannst du es hier wiederherstellen.", - "twonlySafeRecoverBtn": "Backup wiederherstellen", - "notificationFillerIn": "in", - "notificationText": "hat eine Nachricht{inGroup} gesendet.", - "notificationTwonly": "hat ein twonly{inGroup} gesendet.", - "notificationVideo": "hat ein Video{inGroup} gesendet.", - "notificationImage": "hat ein Bild{inGroup} gesendet.", - "notificationAudio": "hat eine Sprachnachricht{inGroup} gesendet.", - "notificationAddedToGroup": "hat dich zu \"{groupname}\" hinzugefügt.", - "notificationContactRequest": "möchte sich mit dir vernetzen.", - "notificationAcceptRequest": "ist jetzt mit dir vernetzt.", - "notificationStoredMediaFile": "hat dein Bild gespeichert.", - "notificationReaction": "hat auf dein Bild reagiert.", - "notificationReopenedMedia": "hat dein Bild erneut geöffnet.", - "notificationReactionToVideo": "hat mit {reaction} auf dein Video reagiert.", - "notificationReactionToText": "hat mit {reaction} auf deine Nachricht reagiert.", - "notificationReactionToImage": "hat mit {reaction} auf dein Bild reagiert.", - "notificationReactionToAudio": "hat mit {reaction} auf deine Sprachnachricht reagiert.", - "notificationResponse": "hat dir{inGroup} geantwortet.", - "notificationTitleUnknownUser": "[Unbekannt]", - "notificationCategoryMessageTitle": "Nachrichten", - "notificationCategoryMessageDesc": "Nachrichten von anderen Benutzern.", - "groupContextMenuDeleteGroup": "Dadurch werden alle Nachrichten in diesem Chat dauerhaft gelöscht.", - "groupYouAreNowLongerAMember": "Du bist nicht mehr Mitglied dieser Gruppe.", - "groupNetworkIssue": "Netzwerkproblem. Bitte probiere es später noch einmal.", - "leaveGroupSelectOtherAdminTitle": "Einen Admin auswählen", - "leaveGroupSelectOtherAdminBody": "Um die Gruppe zu verlassen, musst du zuerst einen neuen Administrator auswählen.", - "leaveGroupSureTitle": "Gruppe verlassen", - "leaveGroupSureBody": "Willst du die Gruppe wirklich verlassen?", - "leaveGroupSureOkBtn": "Gruppe verlassen", - "changeDisplayMaxTime": "Chats werden ab jetzt nach {time} gelöscht ({username}).", - "youChangedDisplayMaxTime": "Chats werden ab jetzt nach {time} gelöscht.", - "userGotReported": "Benutzer wurde gemeldet.", - "deleteChatAfter": "Chat löschen nach...", - "deleteChatAfterAnHour": "einer Stunde.", - "deleteChatAfterADay": "einem Tag.", - "deleteChatAfterAWeek": "einer Woche.", - "deleteChatAfterAMonth": "einem Monat.", - "deleteChatAfterAYear": "einem Jahr.", - "yourTwonlyScore": "Dein twonly-Score", - "registrationClosed": "Aufgrund des aktuell sehr hohen Aufkommens haben wir die Registrierung vorübergehend deaktiviert, damit der Dienst zuverlässig bleibt. Bitte versuche es in ein paar Tagen noch einmal.", - "dialogAskDeleteMediaFilePopTitle": "Bist du sicher, dass du dein Meisterwerk löschen möchtest?", - "dialogAskDeleteMediaFilePopDelete": "Löschen", - "allowErrorTracking": "Fehler und Crashes mit uns teilen", - "allowErrorTrackingSubtitle": "Wenn twonly abstürzt oder Fehler auftreten, werden diese automatisch an unsere selbst gehostete Glitchtip-Instanz gemeldet. Persönliche Daten wie Nachrichten oder Bilder werden niemals hochgeladen.", - "avatarSaveChanges": "Möchtest du die Änderungen speichern?", - "avatarSaveChangesStore": "Speichern", - "avatarSaveChangesDiscard": "Verwerfen", - "inProcess": "Wird verarbeitet", - "draftMessage": "Entwurf", - "exportMemories": "Memories exportieren (Beta)", - "importMemories": "Memories importieren (Beta)", - "voiceMessageSlideToCancel": "Zum Abbrechen ziehen", - "voiceMessageCancel": "Abbrechen", - "shareYourProfile": "Teile dein Profil", - "scanOtherProfile": "Scanne ein anderes Profil", - "skipForNow": "Vorerst überspringen", - "linkFromUsername": "Ist der Link von {username}?", - "linkFromUsernameLong": "Wenn du den Link von der Person direkt erhalten hast, kannst du den Kontakt als verifiziert markieren, da der öffentliche Schlüssel im Link mit dem bereits für diesen Benutzer gespeicherten öffentlichen Schlüssel übereinstimmt.", - "gotLinkFromFriend": "Ja, der Link kommt direkt von der Person.", - "couldNotVerifyUsername": "{username} konnte nicht verifiziert werden", - "linkPubkeyDoesNotMatch": "Der öffentliche Schlüssel im Link stimmt nicht mit dem für diesen Kontakt gespeicherten öffentlichen Schlüssel überein. Triff die Person persönlich und scanne den QR-Code direkt!", - "startWithCameraOpen": "Mit geöffneter Kamera starten", - "showImagePreviewWhenSending": "Bildvorschau bei der Auswahl von Empfängern anzeigen", - "verifiedPublicKey": "Der öffentliche Schlüssel von {username} wurde überprüft und ist gültig.", - "memoriesAYearAgo": "Vor einem Jahr", - "memoriesXYearsAgo": "Vor {years} Jahren", - "migrationOfMemories": "Migration von Mediendateien: {open} noch offen.", - "autoStoreAllSendUnlimitedMediaFiles": "Alle gesendeten Medien speichern", - "autoStoreAllSendUnlimitedMediaFilesSubtitle": "Wenn du diese Option aktivierst, werden alle Bilder, die du sendest, gespeichert, sofern sie mit einem unendlichen Countdown und nicht im twonly-Modus gesendet wurden." -} \ No newline at end of file diff --git a/lib/src/localization/app_en.arb b/lib/src/localization/app_en.arb deleted file mode 100644 index 7a340dc..0000000 --- a/lib/src/localization/app_en.arb +++ /dev/null @@ -1,498 +0,0 @@ -{ - "@@locale": "en", - "registerTitle": "Welcome to twonly!", - "registerSlogan": "twonly, a privacy friendly way to connect with friends through secure, spontaneous image sharing", - "onboardingWelcomeTitle": "Welcome to twonly!", - "onboardingWelcomeBody": "Experience a private and secure way to stay in touch with friends by sharing instant pictures.", - "onboardingE2eTitle": "Carefree sharing", - "onboardingE2eBody": "With end-to-end encryption, enjoy the peace of mind that only you and your friends can see the moments you share.", - "onboardingFocusTitle": "Focus on sharing moments", - "onboardingFocusBody": "Say goodbye to addictive features! twonly was created for sharing moments, free from useless distractions or ads.", - "onboardingSendTwonliesTitle": "Send twonlies", - "onboardingSendTwonliesBody": "Share moments securely with your partner. twonly ensures that only your partner can open it, keeping your moments with your partner a two(o)nly thing!", - "onboardingNotProductTitle": "You are not the product!", - "onboardingNotProductBody": "twonly is financed by donations and an optional subscription. Your data will never be sold.", - "onboardingBuyOneGetTwoTitle": "Buy one get two", - "onboardingBuyOneGetTwoBody": "twonly always requires at least two people, which is why you receive a second free license for your twonly partner with your purchase.", - "onboardingGetStartedTitle": "Let's go!", - "onboardingGetStartedBody": "You can test twonly free of charge in preview mode. In this mode you can be found by others and receive pictures or videos but you cannot send any yourself.", - "onboardingTryForFree": "Try for free", - "registerUsernameSlogan": "Please select a username so others can find you!", - "registerUsernameDecoration": "Username", - "registerUsernameLimits": "Your username must be at least 3 characters long.", - "registerProofOfWorkFailed": "There was an issue with the captcha test. Please try again.", - "registerSubmitButton": "Register now!", - "registerTwonlyCodeText": "Have you received a twonly code? Then redeem it either directly here or later!", - "registerTwonlyCodeLabel": "twonly-Code", - "newMessageTitle": "New message", - "chatsTapToSend": "Click to send your first image", - "cameraPreviewSendTo": "Send to", - "shareImageTitle": "Share with", - "shareImageBestFriends": "Best friends", - "shareImagePinnedContacts": "Pinnded", - "shareImagedEditorSendImage": "Send", - "shareImagedEditorShareWith": "Share with", - "shareImagedEditorSaveImage": "Save", - "shareImagedEditorSavedImage": "Saved", - "shareImageSearchAllContacts": "Search all contacts", - "startNewChatSearchHint": "Name, username or groupname", - "shareImagedSelectAll": "Select all", - "startNewChatTitle": "Select Contact", - "startNewChatNewContact": "New Contact", - "startNewChatYourContacts": "Your Contacts", - "shareImageAllUsers": "All contacts", - "shareImageAllTwonlyWarning": "twonlies can only be send to verified contacts!", - "shareImageUserNotVerified": "User is not verified", - "shareImageUserNotVerifiedDesc": "twonlies can only be sent to verified users. To verify a user, go to their profile and to verify security number.", - "shareImageShowArchived": "Show archived users", - "searchUsernameInput": "Username", - "searchUsernameTitle": "Search username", - "searchUserNamePreview": "To protect you and other twonly users from spam and abuse, it is not possible to search for other people in preview mode. Other users can find you and their requests will be displayed here!", - "selectSubscription": "Select subscription", - "searchUserNamePending": "Pending", - "searchUserNameBlockUserTooltip": "Block the user without informing.", - "searchUserNameRejectUserTooltip": "Reject the request and let the requester know.", - "searchUserNameArchiveUserTooltip": "Archive the user. He will appear again as soon as he accepts your request.", - "searchUsernameNotFound": "Username not found", - "searchUsernameNotFoundBody": "There is no user with the username \"{username}\" registered", - "@searchUsernameNotFoundBody": { - "placeholders": { - "username": {} - } - }, - "searchUsernameNewFollowerTitle": "Follow requests", - "searchUsernameQrCodeBtn": "Scan QR code", - "chatListViewSearchUserNameBtn": "Add your first twonly contact!", - "chatListViewSendFirstTwonly": "Send your first twonly!", - "chatListDetailInput": "Type a message", - "userDeletedAccount": "The user has deleted its account.", - "contextMenuUserProfile": "User profile", - "contextMenuVerifyUser": "Verify", - "contextMenuArchiveUser": "Archive", - "contextMenuUndoArchiveUser": "Undo archiving", - "contextMenuOpenChat": "Open chat", - "contextMenuPin": "Pin", - "contextMenuUnpin": "Unpin", - "mediaViewerAuthReason": "Please authenticate to see this twonly!", - "mediaViewerTwonlyTapToOpen": "Tap to open your twonly!", - "messageSendState_Received": "Received", - "messageSendState_Opened": "Opened", - "messageSendState_Send": "Sent", - "messageSendState_Sending": "Sending", - "messageSendState_TapToLoad": "Tap to load", - "messageSendState_Loading": "Downloading", - "messageStoredInGallery": "Stored in gallery", - "messageReopened": "Re-opened", - "imageEditorDrawOk": "Take drawing", - "settingsTitle": "Settings", - "settingsChats": "Chats", - "settingsPreSelectedReactions": "Preselected reaction emojis", - "settingsPreSelectedReactionsError": "A maximum of 12 reactions can be selected.", - "settingsProfile": "Profile", - "settingsStorageData": "Data and storage", - "settingsStorageDataStoreInGTitle": "Store in Gallery", - "settingsStorageDataStoreInGSubtitle": "Store saved images additional in the systems gallery.", - "settingsStorageDataMediaAutoDownload": "Media auto-download", - "settingsStorageDataAutoDownMobile": "When using mobile data", - "settingsStorageDataAutoDownWifi": "When using WI-FI", - "settingsProfileCustomizeAvatar": "Customize your avatar", - "settingsProfileEditDisplayName": "Displayname", - "settingsProfileEditDisplayNameNew": "New Displayname", - "settingsAccount": "Konto", - "settingsSubscription": "Subscription", - "settingsAppearance": "Appearance", - "settingsPrivacy": "Privacy", - "settingsPrivacyBlockUsers": "Block users", - "settingsPrivacyBlockUsersDesc": "Blocked users will not be able to communicate with you. You can unblock a blocked user at any time.", - "settingsPrivacyBlockUsersCount": "{len} contact(s)", - "@settingsPrivacyBlockUsersCount": { - "placeholders": { - "len": {} - } - }, - "settingsNotification": "Notification", - "settingsNotifyTroubleshooting": "Troubleshooting", - "settingsNotifyTroubleshootingDesc": "Click here if you have problems receiving push notifications.", - "settingsNotifyTroubleshootingNoProblem": "No problem detected", - "settingsNotifyTroubleshootingNoProblemDesc": "Press OK to receive a test notification. When you receive no message even after waiting for 10 minutes, please send us your debug log in Settings > Help > Debug log, so we can look at that issue.", - "settingsHelp": "Help", - "settingsHelpDiagnostics": "Diagnostic protocol", - "settingsHelpFAQ": "FAQ", - "feedbackTooltip": "Give Feedback to improve twonly.", - "settingsHelpContactUs": "Contact us", - "settingsHelpVersion": "Version", - "settingsHelpLicenses": "Licenses (Source-Code)", - "settingsHelpCredits": "Licenses (Images)", - "settingsHelpImprint": "Imprint & Privacy Policy", - "contactUsFaq": "Have you read our FAQ yet?", - "contactUsEmojis": "How do you feel? (optional)", - "contactUsSelectOption": "Please select an option", - "contactUsReason": "Tell us why you're reaching out", - "contactUsMessage": "If you want to receive an answer, please add your e-mail address so we can contact you.", - "contactUsYourMessage": "Your message", - "contactUsMessageTitle": "Tell us what's going on", - "contactUsReasonNotWorking": "Something's not working", - "contactUsReasonFeatureRequest": "Feature request", - "contactUsReasonQuestion": "Question", - "contactUsReasonFeedback": "Feedback", - "contactUsReasonOther": "Other", - "contactUsIncludeLog": "Include debug log", - "contactUsWhatsThat": "What's that?", - "contactUsLastWarning": "This are the information's which will be send to us. Please verify them and then press submit.", - "contactUsSuccess": "Feedback submitted successfully!", - "contactUsShortcut": "Hide Feedback Icon", - "settingsHelpTerms": "Terms of Service", - "settingsAppearanceTheme": "Theme", - "settingsAccountDeleteAccount": "Delete account", - "settingsAccountDeleteAccountWithBallance": "In the next step, you can select what you want to to with the remaining credit ({credit}).", - "settingsAccountDeleteAccountNoBallance": "Once you delete your account, there is no going back.", - "settingsAccountDeleteAccountNoInternet": "An Internet connection is required to delete your account.", - "settingsAccountDeleteModalTitle": "Are you sure?", - "settingsAccountDeleteModalBody": "Your account will be deleted. There is no change to restore it.", - "contactVerifyNumberTitle": "Verify safety number", - "contactVerifyNumberTapToScan": "Tap to scan", - "contactVerifyNumberMarkAsVerified": "Mark as verified", - "contactVerifyNumberClearVerification": "Clear verification", - "contactVerifyNumberLongDesc": "To verify the end-to-end encryption with {username}, compare the numbers with their device. The person can also scan your code with their device.", - "@contactVerifyNumberLongDesc": { - "placeholders": { - "username": {} - } - }, - "contactNickname": "Nickname", - "contactNicknameNew": "New nickname", - "deleteAllContactMessages": "Delete all text-messages", - "deleteAllContactMessagesBody": "This will remove all messages, except stored media files, in your chat with {username}. This will NOT delete the messages stored at {username}s device!", - "@deleteAllContactMessagesBody": { - "placeholders": { - "username": {} - } - }, - "contactBlock": "Block", - "contactBlockTitle": "Block {username}", - "@contactBlockTitle": { - "placeholders": { - "username": {} - } - }, - "contactBlockBody": "A blocked user will no longer be able to send you messages and their profile will be hidden from view. To unblock a user, simply navigate to Settings > Privacy > Blocked Users.", - "contactRemove": "Remove user", - "contactRemoveTitle": "Remove {username}", - "contactRemoveBody": "Remove the user and permanently delete the chat and all associated media files. This will also delete YOUR ACCOUNT FROM YOUR CONTACT'S PHONE.", - "undo": "Undo", - "redo": "Redo", - "next": "Next", - "submit": "Submit", - "close": "Close", - "disable": "Disable", - "enable": "Enable", - "cancel": "Cancel", - "now": "Now", - "you": "You", - "minutesShort": "min.", - "image": "Image", - "video": "Video", - "react": "React", - "reply": "Reply", - "copy": "Copy", - "edit": "Edit", - "delete": "Delete", - "info": "Info", - "ok": "Ok", - "switchFrontAndBackCamera": "Switch between front and back camera.", - "addTextItem": "Text", - "protectAsARealTwonly": "Send as real twonly!", - "addDrawing": "Drawing", - "addEmoji": "Emoji", - "toggleFlashLight": "Toggle the flash light", - "toggleHighQuality": "Toggle better resolution", - "userFound": "{username} found", - "userFoundBody": "Do you want to create a follow request?", - "searchUsernameNotFoundLong": "\"{username}\" is not a twonly user. Please check the username and try again.", - "@searchUsernameNotFoundLong": { - "placeholders": { - "username": {} - } - }, - "errorUnknown": "An unexpected error has occurred. Please try again later.", - "errorBadRequest": "The request could not be understood by the server due to malformed syntax. Please check your input and try again.", - "errorTooManyRequests": "You have made too many requests in a short period. Please wait a moment before trying again.", - "errorInternalError": "The server is currently not available. Please try again later.", - "errorInvalidInvitationCode": "The invitation code you provided is invalid. Please check the code and try again.", - "errorUsernameAlreadyTaken": "The username is already taken.", - "errorSignatureNotValid": "The provided signature is not valid. Please check your credentials and try again.", - "errorUsernameNotFound": "The username you entered does not exist. Please check the spelling or create a new account.", - "errorUsernameNotValid": "The username you provided does not meet the required criteria. Please choose a valid username.", - "errorInvalidPublicKey": "The public key you provided is invalid. Please check the key and try again.", - "errorSessionAlreadyAuthenticated": "You are already logged in. Please log out if you want to log in with a different account.", - "errorSessionNotAuthenticated": "Your session is not authenticated. Please log in to continue.", - "errorOnlyOneSessionAllowed": "Only one active session is allowed per user. Please log out from other devices to continue.", - "errorNotEnoughCredit": "You do not have enough twonly-credit.", - "errorVoucherInvalid": "The voucher code you entered is not valid.", - "errorPlanLimitReached": "You have reached your plans limit. Please upgrade your plan.", - "errorPlanNotAllowed": "This feature is not available in your current plan.", - "errorPlanUpgradeNotYearly": "The plan upgrade must be paid for annually, as the current plan is also billed annually.", - "upgradeToPaidPlan": "Upgrade to a paid plan.", - "upgradeToPaidPlanButton": "Upgrade to {planId}{sufix}", - "partOfPaidPlanOf": "You are part of the paid plan of {username}!", - "year": "year", - "yearly": "Yearly", - "month": "month", - "monthly": "Monthly", - "proFeature1": "✓ Unlimited media file uploads", - "proFeature2": "✓ 1 additional Plus user", - "proFeature3": "✓ Restore flames", - "proFeature4": "✓ Support twonly", - "familyFeature1": "✓ Unlimited media file uploads", - "familyFeature2": "✓ 4 additional Plus user", - "familyFeature3": "✓ Restore flames", - "familyFeature4": "✓ Support twonly", - "redeemUserInviteCode": "Or redeem a twonly-Code.", - "redeemUserInviteCodeTitle": "Redeem twonly-Code", - "redeemUserInviteCodeSuccess": "Your plan has been successfully adjusted.", - "freeFeature1": "✓ 10 Media file uploads per day", - "plusFeature1": "✓ Unlimited media file uploads", - "plusFeature2": "✓ Additional features (coming-soon)", - "transactionHistory": "Your transaction history", - "manageSubscription": "Manage your subscription", - "nextPayment": "Next payment", - "currentBalance": "Current balance", - "manageAdditionalUsers": "Manage additional users", - "open": "Open", - "createOrRedeemVoucher": "Buy or redeem voucher", - "createVoucher": "Buy voucher", - "createVoucherDesc": "Choose the value of the voucher. The value of the voucher will be deducted from your twonly balance.", - "redeemVoucher": "Redeem voucher", - "openVouchers": "Open vouchers", - "voucherCreated": "Voucher created", - "voucherRedeemed": "Voucher redeemed", - "enterVoucherCode": "Enter Voucher Code", - "requestedVouchers": "Requested vouchers", - "redeemedVouchers": "Redeemed vouchers", - "buy": "Buy", - "subscriptionRefund": "When you upgrade, you will receive a refund of {refund} for your current subscription.", - "transactionCash": "Cash transaction", - "transactionPlanUpgrade": "Plan upgrade", - "transactionRefund": "Refund transaction", - "transactionThanksForTesting": "Thank you for testing", - "transactionUnknown": "Unknown transaction", - "transactionVoucherCreated": "Voucher created", - "transactionVoucherRedeemed": "Voucher redeemed", - "transactionAutoRenewal": "Automatic renewal", - "checkoutOptions": "Options", - "refund": "Refund", - "checkoutPayYearly": "Pay yearly", - "checkoutTotal": "Total", - "selectPaymentMethod": "Select Payment Method", - "twonlyCredit": "twonly-Credit", - "notEnoughCredit": "You do not have enough credit!", - "chargeCredit": "Charge credit", - "autoRenewal": "Auto renewal", - "autoRenewalDesc": "You can change this at any time.", - "autoRenewalLongDesc": "When your subscription expires, you will automatically be downgraded to the Preview plan. If you activate the automatic renewal, please make sure that you have enough credit for the automatic renewal. We will notify you in good time before the automatic renewal.", - "planSuccessUpgraded": "Successfully upgraded your plan.", - "checkoutSubmit": "Order with a fee.", - "additionalUsersList": "Your additional users", - "additionalUsersPlusTokens": "twonly-Codes für \"Plus\" user", - "additionalUsersFreeTokens": "twonly-Codes für \"Free\" user", - "planLimitReached": "You have reached your plan limit for today. Upgrade your plan now to send the media file.", - "planNotAllowed": "You cannot send media files with your current tariff. Upgrade your plan now to send the media file.", - "galleryDelete": "Delete file", - "galleryDetails": "Show details", - "galleryExport": "Export to gallery", - "galleryExportSuccess": "Successfully saved in the Gallery.", - "settingsResetTutorials": "Show tutorials again", - "settingsResetTutorialsDesc": "Click here to show already displayed tutorials again.", - "settingsResetTutorialsSuccess": "Tutorials will be displayed again.", - "tutorialChatListSearchUsersTitle": "Find Friends and Manage Friend Requests", - "tutorialChatListSearchUsersDesc": "If you know your friends' usernames, you can search for them here and send a friend request. You will also see all requests from other users that you can accept or block.", - "tutorialChatListContextMenuTitle": "Long press on the contact to open the context menu.", - "tutorialChatListContextMenuDesc": "With the context menu, you can pin, archive, and perform various actions on your contacts. Simply long press the contact and then move your finger to the desired option or tap directly on it.", - "tutorialChatMessagesVerifyShieldTitle": "Verify your contacts!", - "tutorialChatMessagesVerifyShieldDesc": "twonly uses the Signal protocol for secure end-to-end encryption. When you first contact someone, their public identity key is downloaded. To ensure that this key has not been tampered with by third parties, you should compare it with your friend when you meet in person. Once you have verified the user, you can also enable the twonly mode when sending images and videos.", - "tutorialChatMessagesReopenMessageTitle": "Reopen Images and Videos", - "tutorialChatMessagesReopenMessageDesc": "If your friend has sent you a picture or video with infinite display time, you can open it again at any time until you restart the app. To do this, simply double-click on the message. Your friend will then receive a notification that you have viewed the picture again.", - "memoriesEmpty": "As soon as you save pictures or videos, they end up here in your memories.", - "deleteTitle": "Are you sure?", - "deleteOkBtnForAll": "Delete for all", - "deleteOkBtnForMe": "Delete for me", - "deleteImageTitle": "Are you sure?", - "deleteImageBody": "The image will be irrevocably deleted.", - "settingsBackup": "Backup", - "backupNoticeTitle": "No backup configured", - "backupNoticeDesc": "If you change or lose your device, no one can restore your account without a backup. Therefore, back up your data.", - "backupNoticeLater": "Remind later", - "backupNoticeOpenBackup": "Create backup", - "backupPending": "Pending", - "backupFailed": "Failed", - "backupSuccess": "Success", - "backupTwonlySafeDesc": "Back up your twonly identity, as this is the only way to restore your account if you uninstall the app or lose your phone.", - "backupNoPasswordRecovery": "Due to twonly's security system, there is (currently) no password recovery function. Therefore, you must remember your password or, better yet, write it down.", - "backupServer": "Server", - "backupMaxBackupSize": "max. backup size", - "backupStorageRetention": "Storage retention", - "backupLastBackupDate": "Last backup", - "backupLastBackupSize": "Backup size", - "backupLastBackupResult": "Result", - "deleteBackupTitle": "Are you sure?", - "deleteBackupBody": "Without an backup, you can not restore your user account.", - "backupData": "Data-Backup", - "backupDataDesc": "This backup contains besides of your twonly-Identity also all of your media files. This backup will is also encrypted but stored locally. You then have to ensure to manually copy it onto your laptop or device of your choice.", - "backupInsecurePassword": "Insecure password", - "backupInsecurePasswordDesc": "The chosen password is very insecure and can therefore easily be guessed by attackers. Please choose a secure password.", - "backupInsecurePasswordOk": "Continue anyway", - "backupInsecurePasswordCancel": "Try again", - "backupTwonlySafeLongDesc": "twonly does not have any central user accounts. A key pair is created during installation, which consists of a public and a private key. The private key is only stored on your device to protect it from unauthorized access. The public key is uploaded to the server and linked to your chosen username so that others can find you.\n\ntwonly Backup regularly creates an encrypted, anonymous backup of your private key together with your contacts and settings. Your username and chosen password are enough to restore this data on another device.", - "backupSelectStrongPassword": "Choose a secure password. This is required if you want to restore your twonly Backup.", - "password": "Password", - "passwordRepeated": "Repeat password", - "passwordRepeatedNotEqual": "Passwords do not match.", - "backupPasswordRequirement": "Password must be at least 8 characters long.", - "backupExpertSettings": "Expert settings", - "backupEnableBackup": "Activate automatic backup", - "backupOwnServerDesc": "Save your twonly Backup at twonly or on any server of your choice.", - "backupUseOwnServer": "Use server", - "backupResetServer": "Use standard server", - "backupTwonlySaveNow": "Save now", - "backupChangePassword": "Change password", - "twonlySafeRecoverTitle": "Recovery", - "twonlySafeRecoverDesc": "If you have created a backup with twonly Backup, you can restore it here.", - "twonlySafeRecoverBtn": "Restore backup", - "inviteFriends": "Invite your friends", - "inviteFriendsShareBtn": "Share", - "inviteFriendsShareText": "Let's switch to twonly: {url}", - "appOutdated": "Your version of twonly is out of date.", - "appOutdatedBtn": "Update Now", - "doubleClickToReopen": "Double-click\nto open again", - "uploadLimitReached": "The upload limit has\nbeen reached. Upgrade to Pro\nor wait until tomorrow.", - "retransmissionRequested": "Retransmission requested", - "testPaymentMethod": "Thanks for the interest in a paid plan. Currently the paid plans are still deactivated. But they will be activated soon!", - "openChangeLog": "Open changelog automatically", - "reportUserTitle": "Report {username}", - "reportUserReason": "Reporting reason", - "reportUser": "Report user", - "newDeviceRegistered": "You have logged in on another device. You have therefore been logged out here.", - "tabToRemoveEmoji": "Tab to remove", - "quotedMessageWasDeleted": "The quoted message has been deleted.", - "messageWasDeleted": "Message has been deleted.", - "messageWasDeletedShort": "Deleted", - "sent": "Delivered", - "sentTo": "Delivered to", - "received": "Received", - "opened": "Opened", - "waitingForInternet": "Waiting for internet", - "editHistory": "Edit history", - "archivedChats": "Archived chats", - "durationShortSecond": "Sec.", - "durationShortMinute": "Min.", - "durationShortHour": "Hrs.", - "durationShortDays": "{count, plural, =1{1 Day} other{{count} Days}}", - "contacts": "Contacts", - "groups": "Groups", - "newGroup": "New group", - "selectMembers": "Select members", - "selectGroupName": "Select group name", - "groupNameInput": "Group name", - "groupMembers": "Members", - "addMember": "Add member", - "createGroup": "Create group", - "leaveGroup": "Leave group", - "createContactRequest": "Create contact request", - "contactRequestSend": "Contact request send", - "makeAdmin": "Make admin", - "removeAdmin": "Remove as admin", - "removeFromGroup": "Remove from group", - "admin": "Admin", - "revokeAdminRightsTitle": "Revoke {username}'s admin rights?", - "revokeAdminRightsOkBtn": "Remove as admin", - "makeAdminRightsTitle": "Make {username} an admin?", - "makeAdminRightsBody": "{username} will be able to edit this group and its members.", - "makeAdminRightsOkBtn": "Make admin", - "updateGroup": "Update group", - "alreadyInGroup": "Already in Group", - "removeContactFromGroupTitle": "Remove {username} from this group?", - "youChangedGroupName": "You have changed the group name to \"{newGroupName}\".", - "makerChangedGroupName": "{maker} has changed the group name to \"{newGroupName}\".", - "youCreatedGroup": "You have created the group.", - "makerCreatedGroup": "{maker} has created the group.", - "youRemovedMember": "You have removed {affected} from the group.", - "makerRemovedMember": "{maker} has removed {affected} from the group.", - "youAddedMember": "You have added {affected} to the group.", - "makerAddedMember": "{maker} has added {affected} to the group.", - "youMadeAdmin": "You made {affected} an admin.", - "makerMadeAdmin": "{maker} made {affected} an admin.", - "youRevokedAdminRights": "You revoked {affectedR} admin rights.", - "makerRevokedAdminRights": "{maker} revoked {affectedR} admin rights.", - "youLeftGroup": "You have left the group.", - "makerLeftGroup": "{maker} has left the group.", - "groupActionYou": "you", - "groupActionYour": "your", - "notificationFillerIn": "in", - "notificationText": "sent a message{inGroup}.", - "notificationTwonly": "sent a twonly{inGroup}.", - "notificationVideo": "sent a video{inGroup}.", - "notificationImage": "sent an image{inGroup}.", - "notificationAudio": "sent a voice message{inGroup}.", - "notificationAddedToGroup": "has added you to \"{groupname}\"", - "notificationContactRequest": "wants to connect with you.", - "notificationAcceptRequest": "is now connected with you.", - "notificationStoredMediaFile": "has stored your image.", - "notificationReaction": "has reacted to your image.", - "notificationReopenedMedia": "has reopened your image.", - "notificationReactionToVideo": "has reacted with {reaction} to your video.", - "notificationReactionToText": "has reacted with {reaction} to your message.", - "notificationReactionToImage": "has reacted with {reaction} to your image.", - "notificationReactionToAudio": "has reacted with {reaction} to your audio message.", - "notificationResponse": "has responded{inGroup}.", - "notificationTitleUnknownUser": "[Unknown]", - "notificationCategoryMessageTitle": "Messages", - "notificationCategoryMessageDesc": "Messages from other users.", - "groupContextMenuDeleteGroup": "This will permanently delete all messages in this chat.", - "groupYouAreNowLongerAMember": "You are no longer part of this group.", - "groupNetworkIssue": "Network issue. Try again later.", - "leaveGroupSelectOtherAdminTitle": "Select another admin", - "leaveGroupSelectOtherAdminBody": "To leave the group, you must first select a new administrator.", - "leaveGroupSureTitle": "Leave group", - "leaveGroupSureBody": "Do you really want to leave the group?", - "leaveGroupSureOkBtn": "Leave group", - "changeDisplayMaxTime": "Chats will now be deleted after {time} ({username}).", - "youChangedDisplayMaxTime": "Chats will now be deleted after {time}.", - "userGotReported": "User has been reported.", - "deleteChatAfter": "Delete chat after...", - "deleteChatAfterAnHour": "one hour.", - "deleteChatAfterADay": "one day.", - "deleteChatAfterAWeek": "one week.", - "deleteChatAfterAMonth": "one month.", - "deleteChatAfterAYear": "one year.", - "yourTwonlyScore": "Your twonly-Score", - "registrationClosed": "Due to the current high volume of registrations, we have temporarily disabled registration to ensure that the service remains reliable. Please try again in a few days.", - "dialogAskDeleteMediaFilePopTitle": "Are you sure you want to delete your masterpiece?", - "dialogAskDeleteMediaFilePopDelete": "Delete", - "allowErrorTracking": "Share errors and crashes with us", - "allowErrorTrackingSubtitle": "If twonly crashes or errors occur, these are automatically reported to our self-hosted Glitchtip instance. Personal data such as messages or images are never uploaded.", - "avatarSaveChanges": "Would you like to save the changes?", - "avatarSaveChangesStore": "Save", - "avatarSaveChangesDiscard": "Discard", - "inProcess": "In process", - "draftMessage": "Draft", - "exportMemories": "Export memories (Beta)", - "importMemories": "Import memories (Beta)", - "voiceMessageSlideToCancel": "Slide to cancel", - "voiceMessageCancel": "Cancel", - "shareYourProfile": "Share your profile", - "scanOtherProfile": "Scan other profile", - "skipForNow": "Skip for now", - "linkFromUsername": "Is the link from {username}?", - "linkFromUsernameLong": "If you received the link from your friend, you can mark the user as verified, as the public key in the link matches the public key already stored for that user?", - "gotLinkFromFriend": "Yes, I got the link from my friend!", - "couldNotVerifyUsername": "Could not verify {username}", - "linkPubkeyDoesNotMatch": "The public key in the link does not match the public key stored for this contact. Try to meet your friend in person and scan the QR code directly!", - "startWithCameraOpen": "Start with camera open", - "showImagePreviewWhenSending": "Display image preview when selecting recipients", - "verifiedPublicKey": "The public key of {username} has been verified and is valid.", - "memoriesAYearAgo": "One year ago", - "memoriesXYearsAgo": "{years} years ago", - "migrationOfMemories": "Migration of media files: {open} still to be processed.", - "autoStoreAllSendUnlimitedMediaFiles": "Save all sent media", - "autoStoreAllSendUnlimitedMediaFilesSubtitle": "If you enable this option, all images you send will be saved as long as they were sent with an infinite countdown and not in twonly mode." -} \ No newline at end of file diff --git a/lib/src/localization/generated/app_localizations.dart b/lib/src/localization/generated/app_localizations.dart index fdea7e0..8fa04f5 100644 --- a/lib/src/localization/generated/app_localizations.dart +++ b/lib/src/localization/generated/app_localizations.dart @@ -7,6 +7,7 @@ import 'package:intl/intl.dart' as intl; import 'app_localizations_de.dart'; import 'app_localizations_en.dart'; +import 'app_localizations_sv.dart'; // ignore_for_file: type=lint @@ -95,7 +96,8 @@ abstract class AppLocalizations { /// A list of this localizations delegate's supported locales. static const List supportedLocales = [ Locale('de'), - Locale('en') + Locale('en'), + Locale('sv') ]; /// No description provided for @registerTitle. @@ -1400,24 +1402,6 @@ abstract class AppLocalizations { /// **'✓ Support twonly'** String get familyFeature4; - /// No description provided for @redeemUserInviteCode. - /// - /// In en, this message translates to: - /// **'Or redeem a twonly-Code.'** - String get redeemUserInviteCode; - - /// No description provided for @redeemUserInviteCodeTitle. - /// - /// In en, this message translates to: - /// **'Redeem twonly-Code'** - String get redeemUserInviteCodeTitle; - - /// No description provided for @redeemUserInviteCodeSuccess. - /// - /// In en, this message translates to: - /// **'Your plan has been successfully adjusted.'** - String get redeemUserInviteCodeSuccess; - /// No description provided for @freeFeature1. /// /// In en, this message translates to: @@ -1445,7 +1429,7 @@ abstract class AppLocalizations { /// No description provided for @manageSubscription. /// /// In en, this message translates to: - /// **'Manage your subscription'** + /// **'Manage subscription'** String get manageSubscription; /// No description provided for @nextPayment. @@ -2887,6 +2871,54 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'If you enable this option, all images you send will be saved as long as they were sent with an infinite countdown and not in twonly mode.'** String get autoStoreAllSendUnlimitedMediaFilesSubtitle; + + /// No description provided for @termsOfService. + /// + /// In en, this message translates to: + /// **'Terms of service'** + String get termsOfService; + + /// No description provided for @privacyPolicy. + /// + /// In en, this message translates to: + /// **'Privacy policy'** + String get privacyPolicy; + + /// No description provided for @additionalUserAddError. + /// + /// In en, this message translates to: + /// **'Could not add additional user. Try again later.'** + String get additionalUserAddError; + + /// No description provided for @additionalUserAddButton. + /// + /// In en, this message translates to: + /// **'Add additional user ({used}/{limit})'** + String additionalUserAddButton(Object limit, Object used); + + /// No description provided for @additionalUserRemoveTitle. + /// + /// In en, this message translates to: + /// **'Remove this additional user'** + String get additionalUserRemoveTitle; + + /// No description provided for @additionalUserRemoveDesc. + /// + /// In en, this message translates to: + /// **'After removal, the additional user will automatically be downgraded to the free plan, and you can add another person.'** + String get additionalUserRemoveDesc; + + /// No description provided for @additionalUserSelectTitle. + /// + /// In en, this message translates to: + /// **'Select additional users'** + String get additionalUserSelectTitle; + + /// No description provided for @additionalUserSelectButton. + /// + /// In en, this message translates to: + /// **'Select users ({used}/{limit})'** + String additionalUserSelectButton(Object limit, Object used); } class _AppLocalizationsDelegate @@ -2900,7 +2932,7 @@ class _AppLocalizationsDelegate @override bool isSupported(Locale locale) => - ['de', 'en'].contains(locale.languageCode); + ['de', 'en', 'sv'].contains(locale.languageCode); @override bool shouldReload(_AppLocalizationsDelegate old) => false; @@ -2913,6 +2945,8 @@ AppLocalizations lookupAppLocalizations(Locale locale) { return AppLocalizationsDe(); case 'en': return AppLocalizationsEn(); + case 'sv': + return AppLocalizationsSv(); } throw FlutterError( diff --git a/lib/src/localization/generated/app_localizations_de.dart b/lib/src/localization/generated/app_localizations_de.dart index f86be36..e0e4cdc 100644 --- a/lib/src/localization/generated/app_localizations_de.dart +++ b/lib/src/localization/generated/app_localizations_de.dart @@ -734,16 +734,6 @@ class AppLocalizationsDe extends AppLocalizations { @override String get familyFeature4 => '✓ twonly unterstützen'; - @override - String get redeemUserInviteCode => 'Oder löse einen twonly-Code ein.'; - - @override - String get redeemUserInviteCodeTitle => 'twonly-Code einlösen'; - - @override - String get redeemUserInviteCodeSuccess => - 'Dein Plan wurde erfolgreich angepasst.'; - @override String get freeFeature1 => '✓ 10 Medien-Datei-Uploads pro Tag'; @@ -875,7 +865,7 @@ class AppLocalizationsDe extends AppLocalizations { String get checkoutSubmit => 'Kostenpflichtig bestellen'; @override - String get additionalUsersList => 'Ihre zusätzlichen Benutzer'; + String get additionalUsersList => 'Deine zusätzlichen Benutzer'; @override String get additionalUsersPlusTokens => 'twonly-Codes für \"Plus\"-Benutzer'; @@ -1603,4 +1593,35 @@ class AppLocalizationsDe extends AppLocalizations { @override String get autoStoreAllSendUnlimitedMediaFilesSubtitle => 'Wenn du diese Option aktivierst, werden alle Bilder, die du sendest, gespeichert, sofern sie mit einem unendlichen Countdown und nicht im twonly-Modus gesendet wurden.'; + + @override + String get termsOfService => 'Allgemeine Geschäftsbedingungen'; + + @override + String get privacyPolicy => 'Datenschutzerklärung'; + + @override + String get additionalUserAddError => + 'Es konnte kein zusätzlicher Nutzer hinzugefügt werden. Versuche es später noch einmal.'; + + @override + String additionalUserAddButton(Object limit, Object used) { + return 'Zusätzlichen Benutzer hinzufügen ($used/$limit)'; + } + + @override + String get additionalUserRemoveTitle => + 'Diesen zusätzlichen Benutzer entfernen'; + + @override + String get additionalUserRemoveDesc => + 'Der zusätzliche Nutzer wird nach der Entfernung automatisch auf den kostenlosen Tarif zurückgestuft und du kannst eine andere Person hinzufügen.'; + + @override + String get additionalUserSelectTitle => 'Zusätzliche Benutzer auswählen'; + + @override + String additionalUserSelectButton(Object limit, Object used) { + return 'Benutzer auswählen ($used/$limit)'; + } } diff --git a/lib/src/localization/generated/app_localizations_en.dart b/lib/src/localization/generated/app_localizations_en.dart index b12f328..a63d072 100644 --- a/lib/src/localization/generated/app_localizations_en.dart +++ b/lib/src/localization/generated/app_localizations_en.dart @@ -728,16 +728,6 @@ class AppLocalizationsEn extends AppLocalizations { @override String get familyFeature4 => '✓ Support twonly'; - @override - String get redeemUserInviteCode => 'Or redeem a twonly-Code.'; - - @override - String get redeemUserInviteCodeTitle => 'Redeem twonly-Code'; - - @override - String get redeemUserInviteCodeSuccess => - 'Your plan has been successfully adjusted.'; - @override String get freeFeature1 => '✓ 10 Media file uploads per day'; @@ -751,7 +741,7 @@ class AppLocalizationsEn extends AppLocalizations { String get transactionHistory => 'Your transaction history'; @override - String get manageSubscription => 'Manage your subscription'; + String get manageSubscription => 'Manage subscription'; @override String get nextPayment => 'Next payment'; @@ -1592,4 +1582,34 @@ class AppLocalizationsEn extends AppLocalizations { @override String get autoStoreAllSendUnlimitedMediaFilesSubtitle => 'If you enable this option, all images you send will be saved as long as they were sent with an infinite countdown and not in twonly mode.'; + + @override + String get termsOfService => 'Terms of service'; + + @override + String get privacyPolicy => 'Privacy policy'; + + @override + String get additionalUserAddError => + 'Could not add additional user. Try again later.'; + + @override + String additionalUserAddButton(Object limit, Object used) { + return 'Add additional user ($used/$limit)'; + } + + @override + String get additionalUserRemoveTitle => 'Remove this additional user'; + + @override + String get additionalUserRemoveDesc => + 'After removal, the additional user will automatically be downgraded to the free plan, and you can add another person.'; + + @override + String get additionalUserSelectTitle => 'Select additional users'; + + @override + String additionalUserSelectButton(Object limit, Object used) { + return 'Select users ($used/$limit)'; + } } diff --git a/lib/src/localization/generated/app_localizations_sv.dart b/lib/src/localization/generated/app_localizations_sv.dart new file mode 100644 index 0000000..f8fd6e3 --- /dev/null +++ b/lib/src/localization/generated/app_localizations_sv.dart @@ -0,0 +1,1615 @@ +// ignore: unused_import +import 'package:intl/intl.dart' as intl; +import 'app_localizations.dart'; + +// ignore_for_file: type=lint + +/// The translations for Swedish (`sv`). +class AppLocalizationsSv extends AppLocalizations { + AppLocalizationsSv([String locale = 'sv']) : super(locale); + + @override + String get registerTitle => 'Welcome to twonly!'; + + @override + String get registerSlogan => + 'twonly, a privacy friendly way to connect with friends through secure, spontaneous image sharing'; + + @override + String get onboardingWelcomeTitle => 'Welcome to twonly!'; + + @override + String get onboardingWelcomeBody => + 'Experience a private and secure way to stay in touch with friends by sharing instant pictures.'; + + @override + String get onboardingE2eTitle => 'Carefree sharing'; + + @override + String get onboardingE2eBody => + 'With end-to-end encryption, enjoy the peace of mind that only you and your friends can see the moments you share.'; + + @override + String get onboardingFocusTitle => 'Focus on sharing moments'; + + @override + String get onboardingFocusBody => + 'Say goodbye to addictive features! twonly was created for sharing moments, free from useless distractions or ads.'; + + @override + String get onboardingSendTwonliesTitle => 'Send twonlies'; + + @override + String get onboardingSendTwonliesBody => + 'Share moments securely with your partner. twonly ensures that only your partner can open it, keeping your moments with your partner a two(o)nly thing!'; + + @override + String get onboardingNotProductTitle => 'You are not the product!'; + + @override + String get onboardingNotProductBody => + 'twonly is financed by donations and an optional subscription. Your data will never be sold.'; + + @override + String get onboardingBuyOneGetTwoTitle => 'Buy one get two'; + + @override + String get onboardingBuyOneGetTwoBody => + 'twonly always requires at least two people, which is why you receive a second free license for your twonly partner with your purchase.'; + + @override + String get onboardingGetStartedTitle => 'Let\'s go!'; + + @override + String get onboardingGetStartedBody => + 'You can test twonly free of charge in preview mode. In this mode you can be found by others and receive pictures or videos but you cannot send any yourself.'; + + @override + String get onboardingTryForFree => 'Try for free'; + + @override + String get registerUsernameSlogan => + 'Please select a username so others can find you!'; + + @override + String get registerUsernameDecoration => 'Username'; + + @override + String get registerUsernameLimits => + 'Your username must be at least 3 characters long.'; + + @override + String get registerProofOfWorkFailed => + 'There was an issue with the captcha test. Please try again.'; + + @override + String get registerSubmitButton => 'Register now!'; + + @override + String get registerTwonlyCodeText => + 'Have you received a twonly code? Then redeem it either directly here or later!'; + + @override + String get registerTwonlyCodeLabel => 'twonly-Code'; + + @override + String get newMessageTitle => 'New message'; + + @override + String get chatsTapToSend => 'Click to send your first image'; + + @override + String get cameraPreviewSendTo => 'Send to'; + + @override + String get shareImageTitle => 'Share with'; + + @override + String get shareImageBestFriends => 'Best friends'; + + @override + String get shareImagePinnedContacts => 'Pinnded'; + + @override + String get shareImagedEditorSendImage => 'Send'; + + @override + String get shareImagedEditorShareWith => 'Share with'; + + @override + String get shareImagedEditorSaveImage => 'Save'; + + @override + String get shareImagedEditorSavedImage => 'Saved'; + + @override + String get shareImageSearchAllContacts => 'Search all contacts'; + + @override + String get startNewChatSearchHint => 'Name, username or groupname'; + + @override + String get shareImagedSelectAll => 'Select all'; + + @override + String get startNewChatTitle => 'Select Contact'; + + @override + String get startNewChatNewContact => 'New Contact'; + + @override + String get startNewChatYourContacts => 'Your Contacts'; + + @override + String get shareImageAllUsers => 'All contacts'; + + @override + String get shareImageAllTwonlyWarning => + 'twonlies can only be send to verified contacts!'; + + @override + String get shareImageUserNotVerified => 'User is not verified'; + + @override + String get shareImageUserNotVerifiedDesc => + 'twonlies can only be sent to verified users. To verify a user, go to their profile and to verify security number.'; + + @override + String get shareImageShowArchived => 'Show archived users'; + + @override + String get searchUsernameInput => 'Username'; + + @override + String get searchUsernameTitle => 'Search username'; + + @override + String get searchUserNamePreview => + 'To protect you and other twonly users from spam and abuse, it is not possible to search for other people in preview mode. Other users can find you and their requests will be displayed here!'; + + @override + String get selectSubscription => 'Select subscription'; + + @override + String get searchUserNamePending => 'Pending'; + + @override + String get searchUserNameBlockUserTooltip => + 'Block the user without informing.'; + + @override + String get searchUserNameRejectUserTooltip => + 'Reject the request and let the requester know.'; + + @override + String get searchUserNameArchiveUserTooltip => + 'Archive the user. He will appear again as soon as he accepts your request.'; + + @override + String get searchUsernameNotFound => 'Username not found'; + + @override + String searchUsernameNotFoundBody(Object username) { + return 'There is no user with the username \"$username\" registered'; + } + + @override + String get searchUsernameNewFollowerTitle => 'Follow requests'; + + @override + String get searchUsernameQrCodeBtn => 'Scan QR code'; + + @override + String get chatListViewSearchUserNameBtn => 'Add your first twonly contact!'; + + @override + String get chatListViewSendFirstTwonly => 'Send your first twonly!'; + + @override + String get chatListDetailInput => 'Type a message'; + + @override + String get userDeletedAccount => 'The user has deleted its account.'; + + @override + String get contextMenuUserProfile => 'User profile'; + + @override + String get contextMenuVerifyUser => 'Verify'; + + @override + String get contextMenuArchiveUser => 'Archive'; + + @override + String get contextMenuUndoArchiveUser => 'Undo archiving'; + + @override + String get contextMenuOpenChat => 'Open chat'; + + @override + String get contextMenuPin => 'Pin'; + + @override + String get contextMenuUnpin => 'Unpin'; + + @override + String get mediaViewerAuthReason => 'Please authenticate to see this twonly!'; + + @override + String get mediaViewerTwonlyTapToOpen => 'Tap to open your twonly!'; + + @override + String get messageSendState_Received => 'Received'; + + @override + String get messageSendState_Opened => 'Opened'; + + @override + String get messageSendState_Send => 'Sent'; + + @override + String get messageSendState_Sending => 'Sending'; + + @override + String get messageSendState_TapToLoad => 'Tap to load'; + + @override + String get messageSendState_Loading => 'Downloading'; + + @override + String get messageStoredInGallery => 'Stored in gallery'; + + @override + String get messageReopened => 'Re-opened'; + + @override + String get imageEditorDrawOk => 'Take drawing'; + + @override + String get settingsTitle => 'Settings'; + + @override + String get settingsChats => 'Chats'; + + @override + String get settingsPreSelectedReactions => 'Preselected reaction emojis'; + + @override + String get settingsPreSelectedReactionsError => + 'A maximum of 12 reactions can be selected.'; + + @override + String get settingsProfile => 'Profile'; + + @override + String get settingsStorageData => 'Data and storage'; + + @override + String get settingsStorageDataStoreInGTitle => 'Store in Gallery'; + + @override + String get settingsStorageDataStoreInGSubtitle => + 'Store saved images additional in the systems gallery.'; + + @override + String get settingsStorageDataMediaAutoDownload => 'Media auto-download'; + + @override + String get settingsStorageDataAutoDownMobile => 'When using mobile data'; + + @override + String get settingsStorageDataAutoDownWifi => 'When using WI-FI'; + + @override + String get settingsProfileCustomizeAvatar => 'Customize your avatar'; + + @override + String get settingsProfileEditDisplayName => 'Displayname'; + + @override + String get settingsProfileEditDisplayNameNew => 'New Displayname'; + + @override + String get settingsAccount => 'Konto'; + + @override + String get settingsSubscription => 'Subscription'; + + @override + String get settingsAppearance => 'Appearance'; + + @override + String get settingsPrivacy => 'Privacy'; + + @override + String get settingsPrivacyBlockUsers => 'Block users'; + + @override + String get settingsPrivacyBlockUsersDesc => + 'Blocked users will not be able to communicate with you. You can unblock a blocked user at any time.'; + + @override + String settingsPrivacyBlockUsersCount(Object len) { + return '$len contact(s)'; + } + + @override + String get settingsNotification => 'Notification'; + + @override + String get settingsNotifyTroubleshooting => 'Troubleshooting'; + + @override + String get settingsNotifyTroubleshootingDesc => + 'Click here if you have problems receiving push notifications.'; + + @override + String get settingsNotifyTroubleshootingNoProblem => 'No problem detected'; + + @override + String get settingsNotifyTroubleshootingNoProblemDesc => + 'Press OK to receive a test notification. When you receive no message even after waiting for 10 minutes, please send us your debug log in Settings > Help > Debug log, so we can look at that issue.'; + + @override + String get settingsHelp => 'Help'; + + @override + String get settingsHelpDiagnostics => 'Diagnostic protocol'; + + @override + String get settingsHelpFAQ => 'FAQ'; + + @override + String get feedbackTooltip => 'Give Feedback to improve twonly.'; + + @override + String get settingsHelpContactUs => 'Contact us'; + + @override + String get settingsHelpVersion => 'Version'; + + @override + String get settingsHelpLicenses => 'Licenses (Source-Code)'; + + @override + String get settingsHelpCredits => 'Licenses (Images)'; + + @override + String get settingsHelpImprint => 'Imprint & Privacy Policy'; + + @override + String get contactUsFaq => 'Have you read our FAQ yet?'; + + @override + String get contactUsEmojis => 'How do you feel? (optional)'; + + @override + String get contactUsSelectOption => 'Please select an option'; + + @override + String get contactUsReason => 'Tell us why you\'re reaching out'; + + @override + String get contactUsMessage => + 'If you want to receive an answer, please add your e-mail address so we can contact you.'; + + @override + String get contactUsYourMessage => 'Your message'; + + @override + String get contactUsMessageTitle => 'Tell us what\'s going on'; + + @override + String get contactUsReasonNotWorking => 'Something\'s not working'; + + @override + String get contactUsReasonFeatureRequest => 'Feature request'; + + @override + String get contactUsReasonQuestion => 'Question'; + + @override + String get contactUsReasonFeedback => 'Feedback'; + + @override + String get contactUsReasonOther => 'Other'; + + @override + String get contactUsIncludeLog => 'Include debug log'; + + @override + String get contactUsWhatsThat => 'What\'s that?'; + + @override + String get contactUsLastWarning => + 'This are the information\'s which will be send to us. Please verify them and then press submit.'; + + @override + String get contactUsSuccess => 'Feedback submitted successfully!'; + + @override + String get contactUsShortcut => 'Hide Feedback Icon'; + + @override + String get settingsHelpTerms => 'Terms of Service'; + + @override + String get settingsAppearanceTheme => 'Theme'; + + @override + String get settingsAccountDeleteAccount => 'Delete account'; + + @override + String settingsAccountDeleteAccountWithBallance(Object credit) { + return 'In the next step, you can select what you want to to with the remaining credit ($credit).'; + } + + @override + String get settingsAccountDeleteAccountNoBallance => + 'Once you delete your account, there is no going back.'; + + @override + String get settingsAccountDeleteAccountNoInternet => + 'An Internet connection is required to delete your account.'; + + @override + String get settingsAccountDeleteModalTitle => 'Are you sure?'; + + @override + String get settingsAccountDeleteModalBody => + 'Your account will be deleted. There is no change to restore it.'; + + @override + String get contactVerifyNumberTitle => 'Verify safety number'; + + @override + String get contactVerifyNumberTapToScan => 'Tap to scan'; + + @override + String get contactVerifyNumberMarkAsVerified => 'Mark as verified'; + + @override + String get contactVerifyNumberClearVerification => 'Clear verification'; + + @override + String contactVerifyNumberLongDesc(Object username) { + return 'To verify the end-to-end encryption with $username, compare the numbers with their device. The person can also scan your code with their device.'; + } + + @override + String get contactNickname => 'Nickname'; + + @override + String get contactNicknameNew => 'New nickname'; + + @override + String get deleteAllContactMessages => 'Delete all text-messages'; + + @override + String deleteAllContactMessagesBody(Object username) { + return 'This will remove all messages, except stored media files, in your chat with $username. This will NOT delete the messages stored at ${username}s device!'; + } + + @override + String get contactBlock => 'Block'; + + @override + String contactBlockTitle(Object username) { + return 'Block $username'; + } + + @override + String get contactBlockBody => + 'A blocked user will no longer be able to send you messages and their profile will be hidden from view. To unblock a user, simply navigate to Settings > Privacy > Blocked Users.'; + + @override + String get contactRemove => 'Remove user'; + + @override + String contactRemoveTitle(Object username) { + return 'Remove $username'; + } + + @override + String get contactRemoveBody => + 'Remove the user and permanently delete the chat and all associated media files. This will also delete YOUR ACCOUNT FROM YOUR CONTACT\'S PHONE.'; + + @override + String get undo => 'Undo'; + + @override + String get redo => 'Redo'; + + @override + String get next => 'Next'; + + @override + String get submit => 'Submit'; + + @override + String get close => 'Close'; + + @override + String get disable => 'Disable'; + + @override + String get enable => 'Enable'; + + @override + String get cancel => 'Cancel'; + + @override + String get now => 'Now'; + + @override + String get you => 'You'; + + @override + String get minutesShort => 'min.'; + + @override + String get image => 'Image'; + + @override + String get video => 'Video'; + + @override + String get react => 'React'; + + @override + String get reply => 'Reply'; + + @override + String get copy => 'Copy'; + + @override + String get edit => 'Edit'; + + @override + String get delete => 'Delete'; + + @override + String get info => 'Info'; + + @override + String get ok => 'Ok'; + + @override + String get switchFrontAndBackCamera => + 'Switch between front and back camera.'; + + @override + String get addTextItem => 'Text'; + + @override + String get protectAsARealTwonly => 'Send as real twonly!'; + + @override + String get addDrawing => 'Drawing'; + + @override + String get addEmoji => 'Emoji'; + + @override + String get toggleFlashLight => 'Toggle the flash light'; + + @override + String get toggleHighQuality => 'Toggle better resolution'; + + @override + String userFound(Object username) { + return '$username found'; + } + + @override + String get userFoundBody => 'Do you want to create a follow request?'; + + @override + String searchUsernameNotFoundLong(Object username) { + return '\"$username\" is not a twonly user. Please check the username and try again.'; + } + + @override + String get errorUnknown => + 'An unexpected error has occurred. Please try again later.'; + + @override + String get errorBadRequest => + 'The request could not be understood by the server due to malformed syntax. Please check your input and try again.'; + + @override + String get errorTooManyRequests => + 'You have made too many requests in a short period. Please wait a moment before trying again.'; + + @override + String get errorInternalError => + 'The server is currently not available. Please try again later.'; + + @override + String get errorInvalidInvitationCode => + 'The invitation code you provided is invalid. Please check the code and try again.'; + + @override + String get errorUsernameAlreadyTaken => 'The username is already taken.'; + + @override + String get errorSignatureNotValid => + 'The provided signature is not valid. Please check your credentials and try again.'; + + @override + String get errorUsernameNotFound => + 'The username you entered does not exist. Please check the spelling or create a new account.'; + + @override + String get errorUsernameNotValid => + 'The username you provided does not meet the required criteria. Please choose a valid username.'; + + @override + String get errorInvalidPublicKey => + 'The public key you provided is invalid. Please check the key and try again.'; + + @override + String get errorSessionAlreadyAuthenticated => + 'You are already logged in. Please log out if you want to log in with a different account.'; + + @override + String get errorSessionNotAuthenticated => + 'Your session is not authenticated. Please log in to continue.'; + + @override + String get errorOnlyOneSessionAllowed => + 'Only one active session is allowed per user. Please log out from other devices to continue.'; + + @override + String get errorNotEnoughCredit => 'You do not have enough twonly-credit.'; + + @override + String get errorVoucherInvalid => + 'The voucher code you entered is not valid.'; + + @override + String get errorPlanLimitReached => + 'You have reached your plans limit. Please upgrade your plan.'; + + @override + String get errorPlanNotAllowed => + 'This feature is not available in your current plan.'; + + @override + String get errorPlanUpgradeNotYearly => + 'The plan upgrade must be paid for annually, as the current plan is also billed annually.'; + + @override + String get upgradeToPaidPlan => 'Upgrade to a paid plan.'; + + @override + String upgradeToPaidPlanButton(Object planId, Object sufix) { + return 'Upgrade to $planId$sufix'; + } + + @override + String partOfPaidPlanOf(Object username) { + return 'You are part of the paid plan of $username!'; + } + + @override + String get year => 'year'; + + @override + String get yearly => 'Yearly'; + + @override + String get month => 'month'; + + @override + String get monthly => 'Monthly'; + + @override + String get proFeature1 => '✓ Unlimited media file uploads'; + + @override + String get proFeature2 => '✓ 1 additional Plus user'; + + @override + String get proFeature3 => '✓ Restore flames'; + + @override + String get proFeature4 => '✓ Support twonly'; + + @override + String get familyFeature1 => '✓ Unlimited media file uploads'; + + @override + String get familyFeature2 => '✓ 4 additional Plus user'; + + @override + String get familyFeature3 => '✓ Restore flames'; + + @override + String get familyFeature4 => '✓ Support twonly'; + + @override + String get freeFeature1 => '✓ 10 Media file uploads per day'; + + @override + String get plusFeature1 => '✓ Unlimited media file uploads'; + + @override + String get plusFeature2 => '✓ Additional features (coming-soon)'; + + @override + String get transactionHistory => 'Your transaction history'; + + @override + String get manageSubscription => 'Manage subscription'; + + @override + String get nextPayment => 'Next payment'; + + @override + String get currentBalance => 'Current balance'; + + @override + String get manageAdditionalUsers => 'Manage additional users'; + + @override + String get open => 'Open'; + + @override + String get createOrRedeemVoucher => 'Buy or redeem voucher'; + + @override + String get createVoucher => 'Buy voucher'; + + @override + String get createVoucherDesc => + 'Choose the value of the voucher. The value of the voucher will be deducted from your twonly balance.'; + + @override + String get redeemVoucher => 'Redeem voucher'; + + @override + String get openVouchers => 'Open vouchers'; + + @override + String get voucherCreated => 'Voucher created'; + + @override + String get voucherRedeemed => 'Voucher redeemed'; + + @override + String get enterVoucherCode => 'Enter Voucher Code'; + + @override + String get requestedVouchers => 'Requested vouchers'; + + @override + String get redeemedVouchers => 'Redeemed vouchers'; + + @override + String get buy => 'Buy'; + + @override + String subscriptionRefund(Object refund) { + return 'When you upgrade, you will receive a refund of $refund for your current subscription.'; + } + + @override + String get transactionCash => 'Cash transaction'; + + @override + String get transactionPlanUpgrade => 'Plan upgrade'; + + @override + String get transactionRefund => 'Refund transaction'; + + @override + String get transactionThanksForTesting => 'Thank you for testing'; + + @override + String get transactionUnknown => 'Unknown transaction'; + + @override + String get transactionVoucherCreated => 'Voucher created'; + + @override + String get transactionVoucherRedeemed => 'Voucher redeemed'; + + @override + String get transactionAutoRenewal => 'Automatic renewal'; + + @override + String get checkoutOptions => 'Options'; + + @override + String get refund => 'Refund'; + + @override + String get checkoutPayYearly => 'Pay yearly'; + + @override + String get checkoutTotal => 'Total'; + + @override + String get selectPaymentMethod => 'Select Payment Method'; + + @override + String get twonlyCredit => 'twonly-Credit'; + + @override + String get notEnoughCredit => 'You do not have enough credit!'; + + @override + String get chargeCredit => 'Charge credit'; + + @override + String get autoRenewal => 'Auto renewal'; + + @override + String get autoRenewalDesc => 'You can change this at any time.'; + + @override + String get autoRenewalLongDesc => + 'When your subscription expires, you will automatically be downgraded to the Preview plan. If you activate the automatic renewal, please make sure that you have enough credit for the automatic renewal. We will notify you in good time before the automatic renewal.'; + + @override + String get planSuccessUpgraded => 'Successfully upgraded your plan.'; + + @override + String get checkoutSubmit => 'Order with a fee.'; + + @override + String get additionalUsersList => 'Your additional users'; + + @override + String get additionalUsersPlusTokens => 'twonly-Codes für \"Plus\" user'; + + @override + String get additionalUsersFreeTokens => 'twonly-Codes für \"Free\" user'; + + @override + String get planLimitReached => + 'You have reached your plan limit for today. Upgrade your plan now to send the media file.'; + + @override + String get planNotAllowed => + 'You cannot send media files with your current tariff. Upgrade your plan now to send the media file.'; + + @override + String get galleryDelete => 'Delete file'; + + @override + String get galleryDetails => 'Show details'; + + @override + String get galleryExport => 'Export to gallery'; + + @override + String get galleryExportSuccess => 'Successfully saved in the Gallery.'; + + @override + String get settingsResetTutorials => 'Show tutorials again'; + + @override + String get settingsResetTutorialsDesc => + 'Click here to show already displayed tutorials again.'; + + @override + String get settingsResetTutorialsSuccess => + 'Tutorials will be displayed again.'; + + @override + String get tutorialChatListSearchUsersTitle => + 'Find Friends and Manage Friend Requests'; + + @override + String get tutorialChatListSearchUsersDesc => + 'If you know your friends\' usernames, you can search for them here and send a friend request. You will also see all requests from other users that you can accept or block.'; + + @override + String get tutorialChatListContextMenuTitle => + 'Long press on the contact to open the context menu.'; + + @override + String get tutorialChatListContextMenuDesc => + 'With the context menu, you can pin, archive, and perform various actions on your contacts. Simply long press the contact and then move your finger to the desired option or tap directly on it.'; + + @override + String get tutorialChatMessagesVerifyShieldTitle => 'Verify your contacts!'; + + @override + String get tutorialChatMessagesVerifyShieldDesc => + 'twonly uses the Signal protocol for secure end-to-end encryption. When you first contact someone, their public identity key is downloaded. To ensure that this key has not been tampered with by third parties, you should compare it with your friend when you meet in person. Once you have verified the user, you can also enable the twonly mode when sending images and videos.'; + + @override + String get tutorialChatMessagesReopenMessageTitle => + 'Reopen Images and Videos'; + + @override + String get tutorialChatMessagesReopenMessageDesc => + 'If your friend has sent you a picture or video with infinite display time, you can open it again at any time until you restart the app. To do this, simply double-click on the message. Your friend will then receive a notification that you have viewed the picture again.'; + + @override + String get memoriesEmpty => + 'As soon as you save pictures or videos, they end up here in your memories.'; + + @override + String get deleteTitle => 'Are you sure?'; + + @override + String get deleteOkBtnForAll => 'Delete for all'; + + @override + String get deleteOkBtnForMe => 'Delete for me'; + + @override + String get deleteImageTitle => 'Are you sure?'; + + @override + String get deleteImageBody => 'The image will be irrevocably deleted.'; + + @override + String get settingsBackup => 'Backup'; + + @override + String get backupNoticeTitle => 'No backup configured'; + + @override + String get backupNoticeDesc => + 'If you change or lose your device, no one can restore your account without a backup. Therefore, back up your data.'; + + @override + String get backupNoticeLater => 'Remind later'; + + @override + String get backupNoticeOpenBackup => 'Create backup'; + + @override + String get backupPending => 'Pending'; + + @override + String get backupFailed => 'Failed'; + + @override + String get backupSuccess => 'Success'; + + @override + String get backupTwonlySafeDesc => + 'Back up your twonly identity, as this is the only way to restore your account if you uninstall the app or lose your phone.'; + + @override + String get backupNoPasswordRecovery => + 'Due to twonly\'s security system, there is (currently) no password recovery function. Therefore, you must remember your password or, better yet, write it down.'; + + @override + String get backupServer => 'Server'; + + @override + String get backupMaxBackupSize => 'max. backup size'; + + @override + String get backupStorageRetention => 'Storage retention'; + + @override + String get backupLastBackupDate => 'Last backup'; + + @override + String get backupLastBackupSize => 'Backup size'; + + @override + String get backupLastBackupResult => 'Result'; + + @override + String get deleteBackupTitle => 'Are you sure?'; + + @override + String get deleteBackupBody => + 'Without an backup, you can not restore your user account.'; + + @override + String get backupData => 'Data-Backup'; + + @override + String get backupDataDesc => + 'This backup contains besides of your twonly-Identity also all of your media files. This backup will is also encrypted but stored locally. You then have to ensure to manually copy it onto your laptop or device of your choice.'; + + @override + String get backupInsecurePassword => 'Insecure password'; + + @override + String get backupInsecurePasswordDesc => + 'The chosen password is very insecure and can therefore easily be guessed by attackers. Please choose a secure password.'; + + @override + String get backupInsecurePasswordOk => 'Continue anyway'; + + @override + String get backupInsecurePasswordCancel => 'Try again'; + + @override + String get backupTwonlySafeLongDesc => + 'twonly does not have any central user accounts. A key pair is created during installation, which consists of a public and a private key. The private key is only stored on your device to protect it from unauthorized access. The public key is uploaded to the server and linked to your chosen username so that others can find you.\n\ntwonly Backup regularly creates an encrypted, anonymous backup of your private key together with your contacts and settings. Your username and chosen password are enough to restore this data on another device.'; + + @override + String get backupSelectStrongPassword => + 'Choose a secure password. This is required if you want to restore your twonly Backup.'; + + @override + String get password => 'Password'; + + @override + String get passwordRepeated => 'Repeat password'; + + @override + String get passwordRepeatedNotEqual => 'Passwords do not match.'; + + @override + String get backupPasswordRequirement => + 'Password must be at least 8 characters long.'; + + @override + String get backupExpertSettings => 'Expert settings'; + + @override + String get backupEnableBackup => 'Activate automatic backup'; + + @override + String get backupOwnServerDesc => + 'Save your twonly Backup at twonly or on any server of your choice.'; + + @override + String get backupUseOwnServer => 'Use server'; + + @override + String get backupResetServer => 'Use standard server'; + + @override + String get backupTwonlySaveNow => 'Save now'; + + @override + String get backupChangePassword => 'Change password'; + + @override + String get twonlySafeRecoverTitle => 'Recovery'; + + @override + String get twonlySafeRecoverDesc => + 'If you have created a backup with twonly Backup, you can restore it here.'; + + @override + String get twonlySafeRecoverBtn => 'Restore backup'; + + @override + String get inviteFriends => 'Invite your friends'; + + @override + String get inviteFriendsShareBtn => 'Share'; + + @override + String inviteFriendsShareText(Object url) { + return 'Let\'s switch to twonly: $url'; + } + + @override + String get appOutdated => 'Your version of twonly is out of date.'; + + @override + String get appOutdatedBtn => 'Update Now'; + + @override + String get doubleClickToReopen => 'Double-click\nto open again'; + + @override + String get uploadLimitReached => + 'The upload limit has\nbeen reached. Upgrade to Pro\nor wait until tomorrow.'; + + @override + String get retransmissionRequested => 'Retransmission requested'; + + @override + String get testPaymentMethod => + 'Thanks for the interest in a paid plan. Currently the paid plans are still deactivated. But they will be activated soon!'; + + @override + String get openChangeLog => 'Open changelog automatically'; + + @override + String reportUserTitle(Object username) { + return 'Report $username'; + } + + @override + String get reportUserReason => 'Reporting reason'; + + @override + String get reportUser => 'Report user'; + + @override + String get newDeviceRegistered => + 'You have logged in on another device. You have therefore been logged out here.'; + + @override + String get tabToRemoveEmoji => 'Tab to remove'; + + @override + String get quotedMessageWasDeleted => 'The quoted message has been deleted.'; + + @override + String get messageWasDeleted => 'Message has been deleted.'; + + @override + String get messageWasDeletedShort => 'Deleted'; + + @override + String get sent => 'Delivered'; + + @override + String get sentTo => 'Delivered to'; + + @override + String get received => 'Received'; + + @override + String get opened => 'Opened'; + + @override + String get waitingForInternet => 'Waiting for internet'; + + @override + String get editHistory => 'Edit history'; + + @override + String get archivedChats => 'Archived chats'; + + @override + String get durationShortSecond => 'Sec.'; + + @override + String get durationShortMinute => 'Min.'; + + @override + String get durationShortHour => 'Hrs.'; + + @override + String durationShortDays(num count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: '$count Days', + one: '1 Day', + ); + return '$_temp0'; + } + + @override + String get contacts => 'Contacts'; + + @override + String get groups => 'Groups'; + + @override + String get newGroup => 'New group'; + + @override + String get selectMembers => 'Select members'; + + @override + String get selectGroupName => 'Select group name'; + + @override + String get groupNameInput => 'Group name'; + + @override + String get groupMembers => 'Members'; + + @override + String get addMember => 'Add member'; + + @override + String get createGroup => 'Create group'; + + @override + String get leaveGroup => 'Leave group'; + + @override + String get createContactRequest => 'Create contact request'; + + @override + String get contactRequestSend => 'Contact request send'; + + @override + String get makeAdmin => 'Make admin'; + + @override + String get removeAdmin => 'Remove as admin'; + + @override + String get removeFromGroup => 'Remove from group'; + + @override + String get admin => 'Admin'; + + @override + String revokeAdminRightsTitle(Object username) { + return 'Revoke $username\'s admin rights?'; + } + + @override + String get revokeAdminRightsOkBtn => 'Remove as admin'; + + @override + String makeAdminRightsTitle(Object username) { + return 'Make $username an admin?'; + } + + @override + String makeAdminRightsBody(Object username) { + return '$username will be able to edit this group and its members.'; + } + + @override + String get makeAdminRightsOkBtn => 'Make admin'; + + @override + String get updateGroup => 'Update group'; + + @override + String get alreadyInGroup => 'Already in Group'; + + @override + String removeContactFromGroupTitle(Object username) { + return 'Remove $username from this group?'; + } + + @override + String youChangedGroupName(Object newGroupName) { + return 'You have changed the group name to \"$newGroupName\".'; + } + + @override + String makerChangedGroupName(Object maker, Object newGroupName) { + return '$maker has changed the group name to \"$newGroupName\".'; + } + + @override + String get youCreatedGroup => 'You have created the group.'; + + @override + String makerCreatedGroup(Object maker) { + return '$maker has created the group.'; + } + + @override + String youRemovedMember(Object affected) { + return 'You have removed $affected from the group.'; + } + + @override + String makerRemovedMember(Object affected, Object maker) { + return '$maker has removed $affected from the group.'; + } + + @override + String youAddedMember(Object affected) { + return 'You have added $affected to the group.'; + } + + @override + String makerAddedMember(Object affected, Object maker) { + return '$maker has added $affected to the group.'; + } + + @override + String youMadeAdmin(Object affected) { + return 'You made $affected an admin.'; + } + + @override + String makerMadeAdmin(Object affected, Object maker) { + return '$maker made $affected an admin.'; + } + + @override + String youRevokedAdminRights(Object affectedR) { + return 'You revoked $affectedR admin rights.'; + } + + @override + String makerRevokedAdminRights(Object affectedR, Object maker) { + return '$maker revoked $affectedR admin rights.'; + } + + @override + String get youLeftGroup => 'You have left the group.'; + + @override + String makerLeftGroup(Object maker) { + return '$maker has left the group.'; + } + + @override + String get groupActionYou => 'you'; + + @override + String get groupActionYour => 'your'; + + @override + String get notificationFillerIn => 'in'; + + @override + String notificationText(Object inGroup) { + return 'sent a message$inGroup.'; + } + + @override + String notificationTwonly(Object inGroup) { + return 'sent a twonly$inGroup.'; + } + + @override + String notificationVideo(Object inGroup) { + return 'sent a video$inGroup.'; + } + + @override + String notificationImage(Object inGroup) { + return 'sent an image$inGroup.'; + } + + @override + String notificationAudio(Object inGroup) { + return 'sent a voice message$inGroup.'; + } + + @override + String notificationAddedToGroup(Object groupname) { + return 'has added you to \"$groupname\"'; + } + + @override + String get notificationContactRequest => 'wants to connect with you.'; + + @override + String get notificationAcceptRequest => 'is now connected with you.'; + + @override + String get notificationStoredMediaFile => 'has stored your image.'; + + @override + String get notificationReaction => 'has reacted to your image.'; + + @override + String get notificationReopenedMedia => 'has reopened your image.'; + + @override + String notificationReactionToVideo(Object reaction) { + return 'has reacted with $reaction to your video.'; + } + + @override + String notificationReactionToText(Object reaction) { + return 'has reacted with $reaction to your message.'; + } + + @override + String notificationReactionToImage(Object reaction) { + return 'has reacted with $reaction to your image.'; + } + + @override + String notificationReactionToAudio(Object reaction) { + return 'has reacted with $reaction to your audio message.'; + } + + @override + String notificationResponse(Object inGroup) { + return 'has responded$inGroup.'; + } + + @override + String get notificationTitleUnknownUser => '[Unknown]'; + + @override + String get notificationCategoryMessageTitle => 'Messages'; + + @override + String get notificationCategoryMessageDesc => 'Messages from other users.'; + + @override + String get groupContextMenuDeleteGroup => + 'This will permanently delete all messages in this chat.'; + + @override + String get groupYouAreNowLongerAMember => + 'You are no longer part of this group.'; + + @override + String get groupNetworkIssue => 'Network issue. Try again later.'; + + @override + String get leaveGroupSelectOtherAdminTitle => 'Select another admin'; + + @override + String get leaveGroupSelectOtherAdminBody => + 'To leave the group, you must first select a new administrator.'; + + @override + String get leaveGroupSureTitle => 'Leave group'; + + @override + String get leaveGroupSureBody => 'Do you really want to leave the group?'; + + @override + String get leaveGroupSureOkBtn => 'Leave group'; + + @override + String changeDisplayMaxTime(Object time, Object username) { + return 'Chats will now be deleted after $time ($username).'; + } + + @override + String youChangedDisplayMaxTime(Object time) { + return 'Chats will now be deleted after $time.'; + } + + @override + String get userGotReported => 'User has been reported.'; + + @override + String get deleteChatAfter => 'Delete chat after...'; + + @override + String get deleteChatAfterAnHour => 'one hour.'; + + @override + String get deleteChatAfterADay => 'one day.'; + + @override + String get deleteChatAfterAWeek => 'one week.'; + + @override + String get deleteChatAfterAMonth => 'one month.'; + + @override + String get deleteChatAfterAYear => 'one year.'; + + @override + String get yourTwonlyScore => 'Your twonly-Score'; + + @override + String get registrationClosed => + 'Due to the current high volume of registrations, we have temporarily disabled registration to ensure that the service remains reliable. Please try again in a few days.'; + + @override + String get dialogAskDeleteMediaFilePopTitle => + 'Are you sure you want to delete your masterpiece?'; + + @override + String get dialogAskDeleteMediaFilePopDelete => 'Delete'; + + @override + String get allowErrorTracking => 'Share errors and crashes with us'; + + @override + String get allowErrorTrackingSubtitle => + 'If twonly crashes or errors occur, these are automatically reported to our self-hosted Glitchtip instance. Personal data such as messages or images are never uploaded.'; + + @override + String get avatarSaveChanges => 'Would you like to save the changes?'; + + @override + String get avatarSaveChangesStore => 'Save'; + + @override + String get avatarSaveChangesDiscard => 'Discard'; + + @override + String get inProcess => 'In process'; + + @override + String get draftMessage => 'Draft'; + + @override + String get exportMemories => 'Export memories (Beta)'; + + @override + String get importMemories => 'Import memories (Beta)'; + + @override + String get voiceMessageSlideToCancel => 'Slide to cancel'; + + @override + String get voiceMessageCancel => 'Cancel'; + + @override + String get shareYourProfile => 'Share your profile'; + + @override + String get scanOtherProfile => 'Scan other profile'; + + @override + String get skipForNow => 'Skip for now'; + + @override + String linkFromUsername(Object username) { + return 'Is the link from $username?'; + } + + @override + String get linkFromUsernameLong => + 'If you received the link from your friend, you can mark the user as verified, as the public key in the link matches the public key already stored for that user?'; + + @override + String get gotLinkFromFriend => 'Yes, I got the link from my friend!'; + + @override + String couldNotVerifyUsername(Object username) { + return 'Could not verify $username'; + } + + @override + String get linkPubkeyDoesNotMatch => + 'The public key in the link does not match the public key stored for this contact. Try to meet your friend in person and scan the QR code directly!'; + + @override + String get startWithCameraOpen => 'Start with camera open'; + + @override + String get showImagePreviewWhenSending => + 'Display image preview when selecting recipients'; + + @override + String verifiedPublicKey(Object username) { + return 'The public key of $username has been verified and is valid.'; + } + + @override + String get memoriesAYearAgo => 'One year ago'; + + @override + String memoriesXYearsAgo(Object years) { + return '$years years ago'; + } + + @override + String migrationOfMemories(Object open) { + return 'Migration of media files: $open still to be processed.'; + } + + @override + String get autoStoreAllSendUnlimitedMediaFiles => 'Save all sent media'; + + @override + String get autoStoreAllSendUnlimitedMediaFilesSubtitle => + 'If you enable this option, all images you send will be saved as long as they were sent with an infinite countdown and not in twonly mode.'; + + @override + String get termsOfService => 'Terms of service'; + + @override + String get privacyPolicy => 'Privacy policy'; + + @override + String get additionalUserAddError => + 'Could not add additional user. Try again later.'; + + @override + String additionalUserAddButton(Object limit, Object used) { + return 'Add additional user ($used/$limit)'; + } + + @override + String get additionalUserRemoveTitle => 'Remove this additional user'; + + @override + String get additionalUserRemoveDesc => + 'After removal, the additional user will automatically be downgraded to the free plan, and you can add another person.'; + + @override + String get additionalUserSelectTitle => 'Select additional users'; + + @override + String additionalUserSelectButton(Object limit, Object used) { + return 'Select users ($used/$limit)'; + } +} diff --git a/lib/src/localization/translations b/lib/src/localization/translations new file mode 160000 index 0000000..2c12b3f --- /dev/null +++ b/lib/src/localization/translations @@ -0,0 +1 @@ +Subproject commit 2c12b3fe70dd47a4f4006205480e94172792beed diff --git a/lib/src/model/protobuf/api/websocket/client_to_server.pb.dart b/lib/src/model/protobuf/api/websocket/client_to_server.pb.dart index 610971e..a8f1d04 100644 --- a/lib/src/model/protobuf/api/websocket/client_to_server.pb.dart +++ b/lib/src/model/protobuf/api/websocket/client_to_server.pb.dart @@ -2260,6 +2260,69 @@ class ApplicationData_DeleteAccount extends $pb.GeneratedMessage { static ApplicationData_DeleteAccount? _defaultInstance; } +class ApplicationData_AddAdditionalUser extends $pb.GeneratedMessage { + factory ApplicationData_AddAdditionalUser({ + $fixnum.Int64? userId, + }) { + final result = create(); + if (userId != null) result.userId = userId; + return result; + } + + ApplicationData_AddAdditionalUser._(); + + factory ApplicationData_AddAdditionalUser.fromBuffer( + $core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory ApplicationData_AddAdditionalUser.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'ApplicationData.AddAdditionalUser', + package: + const $pb.PackageName(_omitMessageNames ? '' : 'client_to_server'), + createEmptyInstance: create) + ..aInt64(1, _omitFieldNames ? '' : 'userId') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + ApplicationData_AddAdditionalUser clone() => + ApplicationData_AddAdditionalUser()..mergeFromMessage(this); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + ApplicationData_AddAdditionalUser copyWith( + void Function(ApplicationData_AddAdditionalUser) updates) => + super.copyWith((message) => + updates(message as ApplicationData_AddAdditionalUser)) + as ApplicationData_AddAdditionalUser; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static ApplicationData_AddAdditionalUser create() => + ApplicationData_AddAdditionalUser._(); + @$core.override + ApplicationData_AddAdditionalUser createEmptyInstance() => create(); + static $pb.PbList createRepeated() => + $pb.PbList(); + @$core.pragma('dart2js:noInline') + static ApplicationData_AddAdditionalUser getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor( + create); + static ApplicationData_AddAdditionalUser? _defaultInstance; + + @$pb.TagNumber(1) + $fixnum.Int64 get userId => $_getI64(0); + @$pb.TagNumber(1) + set userId($fixnum.Int64 value) => $_setInt64(0, value); + @$pb.TagNumber(1) + $core.bool hasUserId() => $_has(0); + @$pb.TagNumber(1) + void clearUserId() => $_clearField(1); +} + enum ApplicationData_ApplicationData { textMessage, getUserByUsername, @@ -2285,6 +2348,7 @@ enum ApplicationData_ApplicationData { changeUsername, ipaPurchase, ipaForceCheck, + addAdditionalUser, notSet } @@ -2314,6 +2378,7 @@ class ApplicationData extends $pb.GeneratedMessage { ApplicationData_ChangeUsername? changeUsername, ApplicationData_IPAPurchase? ipaPurchase, ApplicationData_IPAForceCheck? ipaForceCheck, + ApplicationData_AddAdditionalUser? addAdditionalUser, }) { final result = create(); if (textMessage != null) result.textMessage = textMessage; @@ -2348,6 +2413,7 @@ class ApplicationData extends $pb.GeneratedMessage { if (changeUsername != null) result.changeUsername = changeUsername; if (ipaPurchase != null) result.ipaPurchase = ipaPurchase; if (ipaForceCheck != null) result.ipaForceCheck = ipaForceCheck; + if (addAdditionalUser != null) result.addAdditionalUser = addAdditionalUser; return result; } @@ -2386,6 +2452,7 @@ class ApplicationData extends $pb.GeneratedMessage { 26: ApplicationData_ApplicationData.changeUsername, 27: ApplicationData_ApplicationData.ipaPurchase, 28: ApplicationData_ApplicationData.ipaForceCheck, + 29: ApplicationData_ApplicationData.addAdditionalUser, 0: ApplicationData_ApplicationData.notSet }; static final $pb.BuilderInfo _i = $pb.BuilderInfo( @@ -2417,7 +2484,8 @@ class ApplicationData extends $pb.GeneratedMessage { 25, 26, 27, - 28 + 28, + 29 ]) ..aOM(1, _omitFieldNames ? '' : 'textMessage', protoName: 'textMessage', @@ -2508,6 +2576,10 @@ class ApplicationData extends $pb.GeneratedMessage { 28, _omitFieldNames ? '' : 'ipaForceCheck', protoName: 'ipaForceCheck', subBuilder: ApplicationData_IPAForceCheck.create) + ..aOM( + 29, _omitFieldNames ? '' : 'addAdditionalUser', + protoName: 'addAdditionalUser', + subBuilder: ApplicationData_AddAdditionalUser.create) ..hasRequiredFields = false; @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') @@ -2822,6 +2894,18 @@ class ApplicationData extends $pb.GeneratedMessage { void clearIpaForceCheck() => $_clearField(28); @$pb.TagNumber(28) ApplicationData_IPAForceCheck ensureIpaForceCheck() => $_ensure(23); + + @$pb.TagNumber(29) + ApplicationData_AddAdditionalUser get addAdditionalUser => $_getN(24); + @$pb.TagNumber(29) + set addAdditionalUser(ApplicationData_AddAdditionalUser value) => + $_setField(29, value); + @$pb.TagNumber(29) + $core.bool hasAddAdditionalUser() => $_has(24); + @$pb.TagNumber(29) + void clearAddAdditionalUser() => $_clearField(29); + @$pb.TagNumber(29) + ApplicationData_AddAdditionalUser ensureAddAdditionalUser() => $_ensure(24); } class Response_PreKey extends $pb.GeneratedMessage { diff --git a/lib/src/model/protobuf/api/websocket/client_to_server.pbjson.dart b/lib/src/model/protobuf/api/websocket/client_to_server.pbjson.dart index d3353af..df410ab 100644 --- a/lib/src/model/protobuf/api/websocket/client_to_server.pbjson.dart +++ b/lib/src/model/protobuf/api/websocket/client_to_server.pbjson.dart @@ -389,15 +389,6 @@ const ApplicationData$json = { '9': 0, '10': 'redeemAdditionalCode' }, - { - '1': 'removeAdditionalUser', - '3': 18, - '4': 1, - '5': 11, - '6': '.client_to_server.ApplicationData.RemoveAdditionalUser', - '9': 0, - '10': 'removeAdditionalUser' - }, { '1': 'updatePlanOptions', '3': 19, @@ -479,6 +470,24 @@ const ApplicationData$json = { '9': 0, '10': 'ipaForceCheck' }, + { + '1': 'removeAdditionalUser', + '3': 18, + '4': 1, + '5': 11, + '6': '.client_to_server.ApplicationData.RemoveAdditionalUser', + '9': 0, + '10': 'removeAdditionalUser' + }, + { + '1': 'addAdditionalUser', + '3': 29, + '4': 1, + '5': 11, + '6': '.client_to_server.ApplicationData.AddAdditionalUser', + '9': 0, + '10': 'addAdditionalUser' + }, ], '3': [ ApplicationData_TextMessage$json, @@ -504,7 +513,8 @@ const ApplicationData$json = { ApplicationData_ReportUser$json, ApplicationData_IPAPurchase$json, ApplicationData_IPAForceCheck$json, - ApplicationData_DeleteAccount$json + ApplicationData_DeleteAccount$json, + ApplicationData_AddAdditionalUser$json ], '8': [ {'1': 'ApplicationData'}, @@ -714,6 +724,14 @@ const ApplicationData_DeleteAccount$json = { '1': 'DeleteAccount', }; +@$core.Deprecated('Use applicationDataDescriptor instead') +const ApplicationData_AddAdditionalUser$json = { + '1': 'AddAdditionalUser', + '2': [ + {'1': 'user_id', '3': 1, '4': 1, '5': 3, '10': 'userId'}, + ], +}; + /// Descriptor for `ApplicationData`. Decode as a `google.protobuf.DescriptorProto`. final $typed_data.Uint8List applicationDataDescriptor = $convert.base64Decode( 'Cg9BcHBsaWNhdGlvbkRhdGESUQoLdGV4dE1lc3NhZ2UYASABKAsyLS5jbGllbnRfdG9fc2Vydm' @@ -740,48 +758,51 @@ final $typed_data.Uint8List applicationDataDescriptor = $convert.base64Decode( 'VudHNJbnZpdGVzGBAgASgLMjcuY2xpZW50X3RvX3NlcnZlci5BcHBsaWNhdGlvbkRhdGEuR2V0' 'QWRkQWNjb3VudHNJbnZpdGVzSABSFWdldEFkZGFjY291bnRzSW52aXRlcxJsChRyZWRlZW1BZG' 'RpdGlvbmFsQ29kZRgRIAEoCzI2LmNsaWVudF90b19zZXJ2ZXIuQXBwbGljYXRpb25EYXRhLlJl' - 'ZGVlbUFkZGl0aW9uYWxDb2RlSABSFHJlZGVlbUFkZGl0aW9uYWxDb2RlEmwKFHJlbW92ZUFkZG' - 'l0aW9uYWxVc2VyGBIgASgLMjYuY2xpZW50X3RvX3NlcnZlci5BcHBsaWNhdGlvbkRhdGEuUmVt' - 'b3ZlQWRkaXRpb25hbFVzZXJIAFIUcmVtb3ZlQWRkaXRpb25hbFVzZXISYwoRdXBkYXRlUGxhbk' - '9wdGlvbnMYEyABKAsyMy5jbGllbnRfdG9fc2VydmVyLkFwcGxpY2F0aW9uRGF0YS5VcGRhdGVQ' - 'bGFuT3B0aW9uc0gAUhF1cGRhdGVQbGFuT3B0aW9ucxJUCgxkb3dubG9hZERvbmUYFCABKAsyLi' - '5jbGllbnRfdG9fc2VydmVyLkFwcGxpY2F0aW9uRGF0YS5Eb3dubG9hZERvbmVIAFIMZG93bmxv' - 'YWREb25lEnUKF2dldFNpZ25lZFByZWtleUJ5VXNlcmlkGBYgASgLMjkuY2xpZW50X3RvX3Nlcn' - 'Zlci5BcHBsaWNhdGlvbkRhdGEuR2V0U2lnbmVkUHJlS2V5QnlVc2VySWRIAFIXZ2V0U2lnbmVk' - 'UHJla2V5QnlVc2VyaWQSZgoSdXBkYXRlU2lnbmVkUHJla2V5GBcgASgLMjQuY2xpZW50X3RvX3' - 'NlcnZlci5BcHBsaWNhdGlvbkRhdGEuVXBkYXRlU2lnbmVkUHJlS2V5SABSEnVwZGF0ZVNpZ25l' - 'ZFByZWtleRJXCg1kZWxldGVBY2NvdW50GBggASgLMi8uY2xpZW50X3RvX3NlcnZlci5BcHBsaW' - 'NhdGlvbkRhdGEuRGVsZXRlQWNjb3VudEgAUg1kZWxldGVBY2NvdW50Ek4KCnJlcG9ydFVzZXIY' - 'GSABKAsyLC5jbGllbnRfdG9fc2VydmVyLkFwcGxpY2F0aW9uRGF0YS5SZXBvcnRVc2VySABSCn' - 'JlcG9ydFVzZXISWgoOY2hhbmdlVXNlcm5hbWUYGiABKAsyMC5jbGllbnRfdG9fc2VydmVyLkFw' - 'cGxpY2F0aW9uRGF0YS5DaGFuZ2VVc2VybmFtZUgAUg5jaGFuZ2VVc2VybmFtZRJRCgtpcGFQdX' - 'JjaGFzZRgbIAEoCzItLmNsaWVudF90b19zZXJ2ZXIuQXBwbGljYXRpb25EYXRhLklQQVB1cmNo' - 'YXNlSABSC2lwYVB1cmNoYXNlElcKDWlwYUZvcmNlQ2hlY2sYHCABKAsyLy5jbGllbnRfdG9fc2' - 'VydmVyLkFwcGxpY2F0aW9uRGF0YS5JUEFGb3JjZUNoZWNrSABSDWlwYUZvcmNlQ2hlY2saagoL' - 'VGV4dE1lc3NhZ2USFwoHdXNlcl9pZBgBIAEoA1IGdXNlcklkEhIKBGJvZHkYAyABKAxSBGJvZH' - 'kSIAoJcHVzaF9kYXRhGAQgASgMSABSCHB1c2hEYXRhiAEBQgwKCl9wdXNoX2RhdGEaLwoRR2V0' - 'VXNlckJ5VXNlcm5hbWUSGgoIdXNlcm5hbWUYASABKAlSCHVzZXJuYW1lGiwKDkNoYW5nZVVzZX' - 'JuYW1lEhoKCHVzZXJuYW1lGAEgASgJUgh1c2VybmFtZRo1ChRVcGRhdGVHb29nbGVGY21Ub2tl' - 'bhIdCgpnb29nbGVfZmNtGAEgASgJUglnb29nbGVGY20aJgoLR2V0VXNlckJ5SWQSFwoHdXNlcl' - '9pZBgBIAEoA1IGdXNlcklkGikKDVJlZGVlbVZvdWNoZXISGAoHdm91Y2hlchgBIAEoCVIHdm91' - 'Y2hlchpwChFTd2l0Y2hUb1BheWVkUGxhbhIXCgdwbGFuX2lkGAEgASgJUgZwbGFuSWQSHwoLcG' - 'F5X21vbnRobHkYAiABKAhSCnBheU1vbnRobHkSIQoMYXV0b19yZW5ld2FsGAMgASgIUgthdXRv' - 'UmVuZXdhbBo2ChFVcGRhdGVQbGFuT3B0aW9ucxIhCgxhdXRvX3JlbmV3YWwYASABKAhSC2F1dG' - '9SZW5ld2FsGjAKDUNyZWF0ZVZvdWNoZXISHwoLdmFsdWVfY2VudHMYASABKA1SCnZhbHVlQ2Vu' - 'dHMaDQoLR2V0TG9jYXRpb24aDQoLR2V0Vm91Y2hlcnMaEwoRR2V0QXZhaWxhYmxlUGxhbnMaFw' - 'oVR2V0QWRkQWNjb3VudHNJbnZpdGVzGhUKE0dldEN1cnJlbnRQbGFuSW5mb3MaNwoUUmVkZWVt' - 'QWRkaXRpb25hbENvZGUSHwoLaW52aXRlX2NvZGUYAiABKAlSCmludml0ZUNvZGUaLwoUUmVtb3' - 'ZlQWRkaXRpb25hbFVzZXISFwoHdXNlcl9pZBgBIAEoA1IGdXNlcklkGi0KEkdldFByZWtleXNC' - 'eVVzZXJJZBIXCgd1c2VyX2lkGAEgASgDUgZ1c2VySWQaMgoXR2V0U2lnbmVkUHJlS2V5QnlVc2' - 'VySWQSFwoHdXNlcl9pZBgBIAEoA1IGdXNlcklkGpsBChJVcGRhdGVTaWduZWRQcmVLZXkSKAoQ' - 'c2lnbmVkX3ByZWtleV9pZBgBIAEoA1IOc2lnbmVkUHJla2V5SWQSIwoNc2lnbmVkX3ByZWtleR' - 'gCIAEoDFIMc2lnbmVkUHJla2V5EjYKF3NpZ25lZF9wcmVrZXlfc2lnbmF0dXJlGAMgASgMUhVz' - 'aWduZWRQcmVrZXlTaWduYXR1cmUaNQoMRG93bmxvYWREb25lEiUKDmRvd25sb2FkX3Rva2VuGA' - 'EgASgMUg1kb3dubG9hZFRva2VuGk4KClJlcG9ydFVzZXISKAoQcmVwb3J0ZWRfdXNlcl9pZBgB' - 'IAEoA1IOcmVwb3J0ZWRVc2VySWQSFgoGcmVhc29uGAIgASgJUgZyZWFzb24acQoLSVBBUHVyY2' - 'hhc2USHQoKcHJvZHVjdF9pZBgBIAEoCVIJcHJvZHVjdElkEhYKBnNvdXJjZRgCIAEoCVIGc291' - 'cmNlEisKEXZlcmlmaWNhdGlvbl9kYXRhGAMgASgJUhB2ZXJpZmljYXRpb25EYXRhGg8KDUlQQU' - 'ZvcmNlQ2hlY2saDwoNRGVsZXRlQWNjb3VudEIRCg9BcHBsaWNhdGlvbkRhdGE='); + 'ZGVlbUFkZGl0aW9uYWxDb2RlSABSFHJlZGVlbUFkZGl0aW9uYWxDb2RlEmMKEXVwZGF0ZVBsYW' + '5PcHRpb25zGBMgASgLMjMuY2xpZW50X3RvX3NlcnZlci5BcHBsaWNhdGlvbkRhdGEuVXBkYXRl' + 'UGxhbk9wdGlvbnNIAFIRdXBkYXRlUGxhbk9wdGlvbnMSVAoMZG93bmxvYWREb25lGBQgASgLMi' + '4uY2xpZW50X3RvX3NlcnZlci5BcHBsaWNhdGlvbkRhdGEuRG93bmxvYWREb25lSABSDGRvd25s' + 'b2FkRG9uZRJ1ChdnZXRTaWduZWRQcmVrZXlCeVVzZXJpZBgWIAEoCzI5LmNsaWVudF90b19zZX' + 'J2ZXIuQXBwbGljYXRpb25EYXRhLkdldFNpZ25lZFByZUtleUJ5VXNlcklkSABSF2dldFNpZ25l' + 'ZFByZWtleUJ5VXNlcmlkEmYKEnVwZGF0ZVNpZ25lZFByZWtleRgXIAEoCzI0LmNsaWVudF90b1' + '9zZXJ2ZXIuQXBwbGljYXRpb25EYXRhLlVwZGF0ZVNpZ25lZFByZUtleUgAUhJ1cGRhdGVTaWdu' + 'ZWRQcmVrZXkSVwoNZGVsZXRlQWNjb3VudBgYIAEoCzIvLmNsaWVudF90b19zZXJ2ZXIuQXBwbG' + 'ljYXRpb25EYXRhLkRlbGV0ZUFjY291bnRIAFINZGVsZXRlQWNjb3VudBJOCgpyZXBvcnRVc2Vy' + 'GBkgASgLMiwuY2xpZW50X3RvX3NlcnZlci5BcHBsaWNhdGlvbkRhdGEuUmVwb3J0VXNlckgAUg' + 'pyZXBvcnRVc2VyEloKDmNoYW5nZVVzZXJuYW1lGBogASgLMjAuY2xpZW50X3RvX3NlcnZlci5B' + 'cHBsaWNhdGlvbkRhdGEuQ2hhbmdlVXNlcm5hbWVIAFIOY2hhbmdlVXNlcm5hbWUSUQoLaXBhUH' + 'VyY2hhc2UYGyABKAsyLS5jbGllbnRfdG9fc2VydmVyLkFwcGxpY2F0aW9uRGF0YS5JUEFQdXJj' + 'aGFzZUgAUgtpcGFQdXJjaGFzZRJXCg1pcGFGb3JjZUNoZWNrGBwgASgLMi8uY2xpZW50X3RvX3' + 'NlcnZlci5BcHBsaWNhdGlvbkRhdGEuSVBBRm9yY2VDaGVja0gAUg1pcGFGb3JjZUNoZWNrEmwK' + 'FHJlbW92ZUFkZGl0aW9uYWxVc2VyGBIgASgLMjYuY2xpZW50X3RvX3NlcnZlci5BcHBsaWNhdG' + 'lvbkRhdGEuUmVtb3ZlQWRkaXRpb25hbFVzZXJIAFIUcmVtb3ZlQWRkaXRpb25hbFVzZXISYwoR' + 'YWRkQWRkaXRpb25hbFVzZXIYHSABKAsyMy5jbGllbnRfdG9fc2VydmVyLkFwcGxpY2F0aW9uRG' + 'F0YS5BZGRBZGRpdGlvbmFsVXNlckgAUhFhZGRBZGRpdGlvbmFsVXNlchpqCgtUZXh0TWVzc2Fn' + 'ZRIXCgd1c2VyX2lkGAEgASgDUgZ1c2VySWQSEgoEYm9keRgDIAEoDFIEYm9keRIgCglwdXNoX2' + 'RhdGEYBCABKAxIAFIIcHVzaERhdGGIAQFCDAoKX3B1c2hfZGF0YRovChFHZXRVc2VyQnlVc2Vy' + 'bmFtZRIaCgh1c2VybmFtZRgBIAEoCVIIdXNlcm5hbWUaLAoOQ2hhbmdlVXNlcm5hbWUSGgoIdX' + 'Nlcm5hbWUYASABKAlSCHVzZXJuYW1lGjUKFFVwZGF0ZUdvb2dsZUZjbVRva2VuEh0KCmdvb2ds' + 'ZV9mY20YASABKAlSCWdvb2dsZUZjbRomCgtHZXRVc2VyQnlJZBIXCgd1c2VyX2lkGAEgASgDUg' + 'Z1c2VySWQaKQoNUmVkZWVtVm91Y2hlchIYCgd2b3VjaGVyGAEgASgJUgd2b3VjaGVyGnAKEVN3' + 'aXRjaFRvUGF5ZWRQbGFuEhcKB3BsYW5faWQYASABKAlSBnBsYW5JZBIfCgtwYXlfbW9udGhseR' + 'gCIAEoCFIKcGF5TW9udGhseRIhCgxhdXRvX3JlbmV3YWwYAyABKAhSC2F1dG9SZW5ld2FsGjYK' + 'EVVwZGF0ZVBsYW5PcHRpb25zEiEKDGF1dG9fcmVuZXdhbBgBIAEoCFILYXV0b1JlbmV3YWwaMA' + 'oNQ3JlYXRlVm91Y2hlchIfCgt2YWx1ZV9jZW50cxgBIAEoDVIKdmFsdWVDZW50cxoNCgtHZXRM' + 'b2NhdGlvbhoNCgtHZXRWb3VjaGVycxoTChFHZXRBdmFpbGFibGVQbGFucxoXChVHZXRBZGRBY2' + 'NvdW50c0ludml0ZXMaFQoTR2V0Q3VycmVudFBsYW5JbmZvcxo3ChRSZWRlZW1BZGRpdGlvbmFs' + 'Q29kZRIfCgtpbnZpdGVfY29kZRgCIAEoCVIKaW52aXRlQ29kZRovChRSZW1vdmVBZGRpdGlvbm' + 'FsVXNlchIXCgd1c2VyX2lkGAEgASgDUgZ1c2VySWQaLQoSR2V0UHJla2V5c0J5VXNlcklkEhcK' + 'B3VzZXJfaWQYASABKANSBnVzZXJJZBoyChdHZXRTaWduZWRQcmVLZXlCeVVzZXJJZBIXCgd1c2' + 'VyX2lkGAEgASgDUgZ1c2VySWQamwEKElVwZGF0ZVNpZ25lZFByZUtleRIoChBzaWduZWRfcHJl' + 'a2V5X2lkGAEgASgDUg5zaWduZWRQcmVrZXlJZBIjCg1zaWduZWRfcHJla2V5GAIgASgMUgxzaW' + 'duZWRQcmVrZXkSNgoXc2lnbmVkX3ByZWtleV9zaWduYXR1cmUYAyABKAxSFXNpZ25lZFByZWtl' + 'eVNpZ25hdHVyZRo1CgxEb3dubG9hZERvbmUSJQoOZG93bmxvYWRfdG9rZW4YASABKAxSDWRvd2' + '5sb2FkVG9rZW4aTgoKUmVwb3J0VXNlchIoChByZXBvcnRlZF91c2VyX2lkGAEgASgDUg5yZXBv' + 'cnRlZFVzZXJJZBIWCgZyZWFzb24YAiABKAlSBnJlYXNvbhpxCgtJUEFQdXJjaGFzZRIdCgpwcm' + '9kdWN0X2lkGAEgASgJUglwcm9kdWN0SWQSFgoGc291cmNlGAIgASgJUgZzb3VyY2USKwoRdmVy' + 'aWZpY2F0aW9uX2RhdGEYAyABKAlSEHZlcmlmaWNhdGlvbkRhdGEaDwoNSVBBRm9yY2VDaGVjax' + 'oPCg1EZWxldGVBY2NvdW50GiwKEUFkZEFkZGl0aW9uYWxVc2VyEhcKB3VzZXJfaWQYASABKANS' + 'BnVzZXJJZEIRCg9BcHBsaWNhdGlvbkRhdGE='); @$core.Deprecated('Use responseDescriptor instead') const Response$json = { diff --git a/lib/src/model/protobuf/api/websocket/error.pbenum.dart b/lib/src/model/protobuf/api/websocket/error.pbenum.dart index 69c76f0..7f8f093 100644 --- a/lib/src/model/protobuf/api/websocket/error.pbenum.dart +++ b/lib/src/model/protobuf/api/websocket/error.pbenum.dart @@ -89,6 +89,8 @@ class ErrorCode extends $pb.ProtobufEnum { ErrorCode._(1033, _omitEnumNames ? '' : 'RegistrationDisabled'); static const ErrorCode IPAPaymentExpired = ErrorCode._(1034, _omitEnumNames ? '' : 'IPAPaymentExpired'); + static const ErrorCode UserIsNotInFreePlan = + ErrorCode._(1035, _omitEnumNames ? '' : 'UserIsNotInFreePlan'); static const $core.List values = [ Unknown, @@ -128,6 +130,7 @@ class ErrorCode extends $pb.ProtobufEnum { InvalidProofOfWork, RegistrationDisabled, IPAPaymentExpired, + UserIsNotInFreePlan, ]; static final $core.Map<$core.int, ErrorCode> _byValue = diff --git a/lib/src/model/protobuf/api/websocket/error.pbjson.dart b/lib/src/model/protobuf/api/websocket/error.pbjson.dart index 9f69260..9c82638 100644 --- a/lib/src/model/protobuf/api/websocket/error.pbjson.dart +++ b/lib/src/model/protobuf/api/websocket/error.pbjson.dart @@ -55,6 +55,7 @@ const ErrorCode$json = { {'1': 'InvalidProofOfWork', '2': 1032}, {'1': 'RegistrationDisabled', '2': 1033}, {'1': 'IPAPaymentExpired', '2': 1034}, + {'1': 'UserIsNotInFreePlan', '2': 1035}, ], }; @@ -76,4 +77,4 @@ final $typed_data.Uint8List errorCodeDescriptor = $convert.base64Decode( 'duZWRQcmVLZXkQgwgSEwoOVXNlcklkTm90Rm91bmQQhAgSFwoSVXNlcklkQWxyZWFkeVRha2Vu' 'EIUIEhcKEkFwcFZlcnNpb25PdXRkYXRlZBCGCBIYChNOZXdEZXZpY2VSZWdpc3RlcmVkEIcIEh' 'cKEkludmFsaWRQcm9vZk9mV29yaxCICBIZChRSZWdpc3RyYXRpb25EaXNhYmxlZBCJCBIWChFJ' - 'UEFQYXltZW50RXhwaXJlZBCKCA=='); + 'UEFQYXltZW50RXhwaXJlZBCKCBIYChNVc2VySXNOb3RJbkZyZWVQbGFuEIsI'); diff --git a/lib/src/model/protobuf/api/websocket/server_to_client.pb.dart b/lib/src/model/protobuf/api/websocket/server_to_client.pb.dart index 3d411cb..ce1e526 100644 --- a/lib/src/model/protobuf/api/websocket/server_to_client.pb.dart +++ b/lib/src/model/protobuf/api/websocket/server_to_client.pb.dart @@ -428,7 +428,6 @@ class Response_Plan extends $pb.GeneratedMessage { $fixnum.Int64? dailyMediaUploadLimit, $fixnum.Int64? maximalUploadSizeOfSingleMediaSize, $fixnum.Int64? additionalPlusAccounts, - $fixnum.Int64? additionalFreeAccounts, $fixnum.Int64? monthlyCostsCent, $fixnum.Int64? yearlyCostsCent, $core.bool? allowedToSendTextMessages, @@ -444,8 +443,6 @@ class Response_Plan extends $pb.GeneratedMessage { maximalUploadSizeOfSingleMediaSize; if (additionalPlusAccounts != null) result.additionalPlusAccounts = additionalPlusAccounts; - if (additionalFreeAccounts != null) - result.additionalFreeAccounts = additionalFreeAccounts; if (monthlyCostsCent != null) result.monthlyCostsCent = monthlyCostsCent; if (yearlyCostsCent != null) result.yearlyCostsCent = yearlyCostsCent; if (allowedToSendTextMessages != null) @@ -474,7 +471,6 @@ class Response_Plan extends $pb.GeneratedMessage { ..aInt64(3, _omitFieldNames ? '' : 'dailyMediaUploadLimit') ..aInt64(4, _omitFieldNames ? '' : 'maximalUploadSizeOfSingleMediaSize') ..aInt64(5, _omitFieldNames ? '' : 'additionalPlusAccounts') - ..aInt64(6, _omitFieldNames ? '' : 'additionalFreeAccounts') ..aInt64(7, _omitFieldNames ? '' : 'monthlyCostsCent') ..aInt64(8, _omitFieldNames ? '' : 'yearlyCostsCent') ..aOB(9, _omitFieldNames ? '' : 'allowedToSendTextMessages') @@ -548,48 +544,39 @@ class Response_Plan extends $pb.GeneratedMessage { @$pb.TagNumber(5) void clearAdditionalPlusAccounts() => $_clearField(5); - @$pb.TagNumber(6) - $fixnum.Int64 get additionalFreeAccounts => $_getI64(5); - @$pb.TagNumber(6) - set additionalFreeAccounts($fixnum.Int64 value) => $_setInt64(5, value); - @$pb.TagNumber(6) - $core.bool hasAdditionalFreeAccounts() => $_has(5); - @$pb.TagNumber(6) - void clearAdditionalFreeAccounts() => $_clearField(6); - @$pb.TagNumber(7) - $fixnum.Int64 get monthlyCostsCent => $_getI64(6); + $fixnum.Int64 get monthlyCostsCent => $_getI64(5); @$pb.TagNumber(7) - set monthlyCostsCent($fixnum.Int64 value) => $_setInt64(6, value); + set monthlyCostsCent($fixnum.Int64 value) => $_setInt64(5, value); @$pb.TagNumber(7) - $core.bool hasMonthlyCostsCent() => $_has(6); + $core.bool hasMonthlyCostsCent() => $_has(5); @$pb.TagNumber(7) void clearMonthlyCostsCent() => $_clearField(7); @$pb.TagNumber(8) - $fixnum.Int64 get yearlyCostsCent => $_getI64(7); + $fixnum.Int64 get yearlyCostsCent => $_getI64(6); @$pb.TagNumber(8) - set yearlyCostsCent($fixnum.Int64 value) => $_setInt64(7, value); + set yearlyCostsCent($fixnum.Int64 value) => $_setInt64(6, value); @$pb.TagNumber(8) - $core.bool hasYearlyCostsCent() => $_has(7); + $core.bool hasYearlyCostsCent() => $_has(6); @$pb.TagNumber(8) void clearYearlyCostsCent() => $_clearField(8); @$pb.TagNumber(9) - $core.bool get allowedToSendTextMessages => $_getBF(8); + $core.bool get allowedToSendTextMessages => $_getBF(7); @$pb.TagNumber(9) - set allowedToSendTextMessages($core.bool value) => $_setBool(8, value); + set allowedToSendTextMessages($core.bool value) => $_setBool(7, value); @$pb.TagNumber(9) - $core.bool hasAllowedToSendTextMessages() => $_has(8); + $core.bool hasAllowedToSendTextMessages() => $_has(7); @$pb.TagNumber(9) void clearAllowedToSendTextMessages() => $_clearField(9); @$pb.TagNumber(10) - $core.bool get isAdditionalAccount => $_getBF(9); + $core.bool get isAdditionalAccount => $_getBF(8); @$pb.TagNumber(10) - set isAdditionalAccount($core.bool value) => $_setBool(9, value); + set isAdditionalAccount($core.bool value) => $_setBool(8, value); @$pb.TagNumber(10) - $core.bool hasIsAdditionalAccount() => $_has(9); + $core.bool hasIsAdditionalAccount() => $_has(8); @$pb.TagNumber(10) void clearIsAdditionalAccount() => $_clearField(10); } diff --git a/lib/src/model/protobuf/api/websocket/server_to_client.pbjson.dart b/lib/src/model/protobuf/api/websocket/server_to_client.pbjson.dart index 567af49..8139c89 100644 --- a/lib/src/model/protobuf/api/websocket/server_to_client.pbjson.dart +++ b/lib/src/model/protobuf/api/websocket/server_to_client.pbjson.dart @@ -220,13 +220,6 @@ const Response_Plan$json = { '5': 3, '10': 'additionalPlusAccounts' }, - { - '1': 'additional_free_accounts', - '3': 6, - '4': 1, - '5': 3, - '10': 'additionalFreeAccounts' - }, { '1': 'monthly_costs_cent', '3': 7, @@ -713,78 +706,77 @@ const Response_TransactionTypes$json = { final $typed_data.Uint8List responseDescriptor = $convert.base64Decode( 'CghSZXNwb25zZRIvCgJvaxgBIAEoCzIdLnNlcnZlcl90b19jbGllbnQuUmVzcG9uc2UuT2tIAF' 'ICb2sSKAoFZXJyb3IYAiABKA4yEC5lcnJvci5FcnJvckNvZGVIAFIFZXJyb3IaIwoNQXV0aGVu' - 'dGljYXRlZBISCgRwbGFuGAEgASgJUgRwbGFuGp4ECgRQbGFuEhcKB3BsYW5faWQYASABKAlSBn' + 'dGljYXRlZBISCgRwbGFuGAEgASgJUgRwbGFuGuQDCgRQbGFuEhcKB3BsYW5faWQYASABKAlSBn' 'BsYW5JZBIqChF1cGxvYWRfc2l6ZV9saW1pdBgCIAEoA1IPdXBsb2FkU2l6ZUxpbWl0EjcKGGRh' 'aWx5X21lZGlhX3VwbG9hZF9saW1pdBgDIAEoA1IVZGFpbHlNZWRpYVVwbG9hZExpbWl0ElQKKG' '1heGltYWxfdXBsb2FkX3NpemVfb2Zfc2luZ2xlX21lZGlhX3NpemUYBCABKANSIm1heGltYWxV' 'cGxvYWRTaXplT2ZTaW5nbGVNZWRpYVNpemUSOAoYYWRkaXRpb25hbF9wbHVzX2FjY291bnRzGA' - 'UgASgDUhZhZGRpdGlvbmFsUGx1c0FjY291bnRzEjgKGGFkZGl0aW9uYWxfZnJlZV9hY2NvdW50' - 'cxgGIAEoA1IWYWRkaXRpb25hbEZyZWVBY2NvdW50cxIsChJtb250aGx5X2Nvc3RzX2NlbnQYBy' - 'ABKANSEG1vbnRobHlDb3N0c0NlbnQSKgoReWVhcmx5X2Nvc3RzX2NlbnQYCCABKANSD3llYXJs' - 'eUNvc3RzQ2VudBJACh1hbGxvd2VkX3RvX3NlbmRfdGV4dF9tZXNzYWdlcxgJIAEoCFIZYWxsb3' - 'dlZFRvU2VuZFRleHRNZXNzYWdlcxIyChVpc19hZGRpdGlvbmFsX2FjY291bnQYCiABKAhSE2lz' - 'QWRkaXRpb25hbEFjY291bnQaPgoFUGxhbnMSNQoFcGxhbnMYASADKAsyHy5zZXJ2ZXJfdG9fY2' - 'xpZW50LlJlc3BvbnNlLlBsYW5SBXBsYW5zGk0KEUFkZEFjY291bnRzSW52aXRlEhcKB3BsYW5f' - 'aWQYASABKAlSBnBsYW5JZBIfCgtpbnZpdGVfY29kZRgCIAEoCVIKaW52aXRlQ29kZRpcChJBZG' - 'RBY2NvdW50c0ludml0ZXMSRgoHaW52aXRlcxgBIAMoCzIsLnNlcnZlcl90b19jbGllbnQuUmVz' - 'cG9uc2UuQWRkQWNjb3VudHNJbnZpdGVSB2ludml0ZXMaxQEKC1RyYW5zYWN0aW9uEiMKDWRlcG' - '9zaXRfY2VudHMYASABKANSDGRlcG9zaXRDZW50cxJWChB0cmFuc2FjdGlvbl90eXBlGAIgASgO' - 'Misuc2VydmVyX3RvX2NsaWVudC5SZXNwb25zZS5UcmFuc2FjdGlvblR5cGVzUg90cmFuc2FjdG' - 'lvblR5cGUSOQoZY3JlYXRlZF9hdF91bml4X3RpbWVzdGFtcBgDIAEoA1IWY3JlYXRlZEF0VW5p' - 'eFRpbWVzdGFtcBpFChFBZGRpdGlvbmFsQWNjb3VudBIXCgd1c2VyX2lkGAEgASgDUgZ1c2VySW' - 'QSFwoHcGxhbl9pZBgDIAEoCVIGcGxhbklkGr4BCgdWb3VjaGVyEh0KCnZvdWNoZXJfaWQYASAB' - 'KAlSCXZvdWNoZXJJZBIfCgt2YWx1ZV9jZW50cxgCIAEoA1IKdmFsdWVDZW50cxIaCghyZWRlZW' - '1lZBgDIAEoCFIIcmVkZWVtZWQSHAoJcmVxdWVzdGVkGAQgASgIUglyZXF1ZXN0ZWQSOQoZY3Jl' - 'YXRlZF9hdF91bml4X3RpbWVzdGFtcBgFIAEoA1IWY3JlYXRlZEF0VW5peFRpbWVzdGFtcBpKCg' - 'hWb3VjaGVycxI+Cgh2b3VjaGVycxgBIAMoCzIiLnNlcnZlcl90b19jbGllbnQuUmVzcG9uc2Uu' - 'Vm91Y2hlclIIdm91Y2hlcnMalwUKDFBsYW5CYWxsYW5jZRJACh11c2VkX2RhaWx5X21lZGlhX3' - 'VwbG9hZF9saW1pdBgBIAEoA1IZdXNlZERhaWx5TWVkaWFVcGxvYWRMaW1pdBI+Chx1c2VkX3Vw' - 'bG9hZF9tZWRpYV9zaXplX2xpbWl0GAIgASgDUhh1c2VkVXBsb2FkTWVkaWFTaXplTGltaXQSMw' - 'oTcGF5bWVudF9wZXJpb2RfZGF5cxgDIAEoA0gAUhFwYXltZW50UGVyaW9kRGF5c4gBARJLCiBs' - 'YXN0X3BheW1lbnRfZG9uZV91bml4X3RpbWVzdGFtcBgEIAEoA0gBUhxsYXN0UGF5bWVudERvbm' - 'VVbml4VGltZXN0YW1wiAEBEkoKDHRyYW5zYWN0aW9ucxgFIAMoCzImLnNlcnZlcl90b19jbGll' - 'bnQuUmVzcG9uc2UuVHJhbnNhY3Rpb25SDHRyYW5zYWN0aW9ucxJdChNhZGRpdGlvbmFsX2FjY2' - '91bnRzGAYgAygLMiwuc2VydmVyX3RvX2NsaWVudC5SZXNwb25zZS5BZGRpdGlvbmFsQWNjb3Vu' - 'dFISYWRkaXRpb25hbEFjY291bnRzEiYKDGF1dG9fcmVuZXdhbBgHIAEoCEgCUgthdXRvUmVuZX' - 'dhbIgBARJCChthZGRpdGlvbmFsX2FjY291bnRfb3duZXJfaWQYCCABKANIA1IYYWRkaXRpb25h' - 'bEFjY291bnRPd25lcklkiAEBQhYKFF9wYXltZW50X3BlcmlvZF9kYXlzQiMKIV9sYXN0X3BheW' - '1lbnRfZG9uZV91bml4X3RpbWVzdGFtcEIPCg1fYXV0b19yZW5ld2FsQh4KHF9hZGRpdGlvbmFs' - 'X2FjY291bnRfb3duZXJfaWQaTgoITG9jYXRpb24SFgoGY291bnR5GAEgASgJUgZjb3VudHkSFg' - 'oGcmVnaW9uGAIgASgJUgZyZWdpb24SEgoEY2l0eRgDIAEoCVIEY2l0eRowCgZQcmVLZXkSDgoC' - 'aWQYASABKANSAmlkEhYKBnByZWtleRgCIAEoDFIGcHJla2V5GpUBCgxTaWduZWRQcmVLZXkSKA' - 'oQc2lnbmVkX3ByZWtleV9pZBgBIAEoA1IOc2lnbmVkUHJla2V5SWQSIwoNc2lnbmVkX3ByZWtl' - 'eRgCIAEoDFIMc2lnbmVkUHJla2V5EjYKF3NpZ25lZF9wcmVrZXlfc2lnbmF0dXJlGAMgASgMUh' - 'VzaWduZWRQcmVrZXlTaWduYXR1cmUa9gMKCFVzZXJEYXRhEhcKB3VzZXJfaWQYASABKANSBnVz' - 'ZXJJZBI7CgdwcmVrZXlzGAIgAygLMiEuc2VydmVyX3RvX2NsaWVudC5SZXNwb25zZS5QcmVLZX' - 'lSB3ByZWtleXMSHwoIdXNlcm5hbWUYByABKAxIAFIIdXNlcm5hbWWIAQESMwoTcHVibGljX2lk' - 'ZW50aXR5X2tleRgDIAEoDEgBUhFwdWJsaWNJZGVudGl0eUtleYgBARIoCg1zaWduZWRfcHJla2' - 'V5GAQgASgMSAJSDHNpZ25lZFByZWtleYgBARI7ChdzaWduZWRfcHJla2V5X3NpZ25hdHVyZRgF' - 'IAEoDEgDUhVzaWduZWRQcmVrZXlTaWduYXR1cmWIAQESLQoQc2lnbmVkX3ByZWtleV9pZBgGIA' - 'EoA0gEUg5zaWduZWRQcmVrZXlJZIgBARIsCg9yZWdpc3RyYXRpb25faWQYCCABKANIBVIOcmVn' - 'aXN0cmF0aW9uSWSIAQFCCwoJX3VzZXJuYW1lQhYKFF9wdWJsaWNfaWRlbnRpdHlfa2V5QhAKDl' - '9zaWduZWRfcHJla2V5QhoKGF9zaWduZWRfcHJla2V5X3NpZ25hdHVyZUITChFfc2lnbmVkX3By' - 'ZWtleV9pZEISChBfcmVnaXN0cmF0aW9uX2lkGlkKC1VwbG9hZFRva2VuEiEKDHVwbG9hZF90b2' - 'tlbhgBIAEoDFILdXBsb2FkVG9rZW4SJwoPZG93bmxvYWRfdG9rZW5zGAIgAygMUg5kb3dubG9h' - 'ZFRva2Vucxo5Cg5Eb3dubG9hZFRva2VucxInCg9kb3dubG9hZF90b2tlbnMYASADKAxSDmRvd2' - '5sb2FkVG9rZW5zGkUKC1Byb29mT2ZXb3JrEhYKBnByZWZpeBgBIAEoCVIGcHJlZml4Eh4KCmRp' - 'ZmZpY3VsdHkYAiABKANSCmRpZmZpY3VsdHkawwcKAk9rEhQKBE5vbmUYASABKAhIAFIETm9uZR' - 'IYCgZ1c2VyaWQYAiABKANIAFIGdXNlcmlkEiYKDWF1dGhjaGFsbGVuZ2UYAyABKAxIAFINYXV0' - 'aGNoYWxsZW5nZRJKCgt1cGxvYWR0b2tlbhgEIAEoCzImLnNlcnZlcl90b19jbGllbnQuUmVzcG' - '9uc2UuVXBsb2FkVG9rZW5IAFILdXBsb2FkdG9rZW4SQQoIdXNlcmRhdGEYBSABKAsyIy5zZXJ2' - 'ZXJfdG9fY2xpZW50LlJlc3BvbnNlLlVzZXJEYXRhSABSCHVzZXJkYXRhEh4KCWF1dGh0b2tlbh' - 'gGIAEoDEgAUglhdXRodG9rZW4SQQoIbG9jYXRpb24YByABKAsyIy5zZXJ2ZXJfdG9fY2xpZW50' - 'LlJlc3BvbnNlLkxvY2F0aW9uSABSCGxvY2F0aW9uElAKDWF1dGhlbnRpY2F0ZWQYCCABKAsyKC' - '5zZXJ2ZXJfdG9fY2xpZW50LlJlc3BvbnNlLkF1dGhlbnRpY2F0ZWRIAFINYXV0aGVudGljYXRl' - 'ZBI4CgVwbGFucxgJIAEoCzIgLnNlcnZlcl90b19jbGllbnQuUmVzcG9uc2UuUGxhbnNIAFIFcG' - 'xhbnMSTQoMcGxhbmJhbGxhbmNlGAogASgLMicuc2VydmVyX3RvX2NsaWVudC5SZXNwb25zZS5Q' - 'bGFuQmFsbGFuY2VIAFIMcGxhbmJhbGxhbmNlEkEKCHZvdWNoZXJzGAsgASgLMiMuc2VydmVyX3' - 'RvX2NsaWVudC5SZXNwb25zZS5Wb3VjaGVyc0gAUgh2b3VjaGVycxJfChJhZGRhY2NvdW50c2lu' - 'dml0ZXMYDCABKAsyLS5zZXJ2ZXJfdG9fY2xpZW50LlJlc3BvbnNlLkFkZEFjY291bnRzSW52aX' - 'Rlc0gAUhJhZGRhY2NvdW50c2ludml0ZXMSUwoOZG93bmxvYWR0b2tlbnMYDSABKAsyKS5zZXJ2' - 'ZXJfdG9fY2xpZW50LlJlc3BvbnNlLkRvd25sb2FkVG9rZW5zSABSDmRvd25sb2FkdG9rZW5zEk' - '0KDHNpZ25lZHByZWtleRgOIAEoCzInLnNlcnZlcl90b19jbGllbnQuUmVzcG9uc2UuU2lnbmVk' - 'UHJlS2V5SABSDHNpZ25lZHByZWtleRJKCgtwcm9vZk9mV29yaxgPIAEoCzImLnNlcnZlcl90b1' - '9jbGllbnQuUmVzcG9uc2UuUHJvb2ZPZldvcmtIAFILcHJvb2ZPZldvcmtCBAoCT2silgEKEFRy' - 'YW5zYWN0aW9uVHlwZXMSCgoGUmVmdW5kEAASEwoPVm91Y2hlclJlZGVlbWVkEAESEgoOVm91Y2' - 'hlckNyZWF0ZWQQAhIICgRDYXNoEAMSDwoLUGxhblVwZ3JhZGUQBBILCgdVbmtub3duEAUSFAoQ' - 'VGhhbmtzRm9yVGVzdGluZxAGEg8KC0F1dG9SZW5ld2FsEAdCCgoIUmVzcG9uc2U='); + 'UgASgDUhZhZGRpdGlvbmFsUGx1c0FjY291bnRzEiwKEm1vbnRobHlfY29zdHNfY2VudBgHIAEo' + 'A1IQbW9udGhseUNvc3RzQ2VudBIqChF5ZWFybHlfY29zdHNfY2VudBgIIAEoA1IPeWVhcmx5Q2' + '9zdHNDZW50EkAKHWFsbG93ZWRfdG9fc2VuZF90ZXh0X21lc3NhZ2VzGAkgASgIUhlhbGxvd2Vk' + 'VG9TZW5kVGV4dE1lc3NhZ2VzEjIKFWlzX2FkZGl0aW9uYWxfYWNjb3VudBgKIAEoCFITaXNBZG' + 'RpdGlvbmFsQWNjb3VudBo+CgVQbGFucxI1CgVwbGFucxgBIAMoCzIfLnNlcnZlcl90b19jbGll' + 'bnQuUmVzcG9uc2UuUGxhblIFcGxhbnMaTQoRQWRkQWNjb3VudHNJbnZpdGUSFwoHcGxhbl9pZB' + 'gBIAEoCVIGcGxhbklkEh8KC2ludml0ZV9jb2RlGAIgASgJUgppbnZpdGVDb2RlGlwKEkFkZEFj' + 'Y291bnRzSW52aXRlcxJGCgdpbnZpdGVzGAEgAygLMiwuc2VydmVyX3RvX2NsaWVudC5SZXNwb2' + '5zZS5BZGRBY2NvdW50c0ludml0ZVIHaW52aXRlcxrFAQoLVHJhbnNhY3Rpb24SIwoNZGVwb3Np' + 'dF9jZW50cxgBIAEoA1IMZGVwb3NpdENlbnRzElYKEHRyYW5zYWN0aW9uX3R5cGUYAiABKA4yKy' + '5zZXJ2ZXJfdG9fY2xpZW50LlJlc3BvbnNlLlRyYW5zYWN0aW9uVHlwZXNSD3RyYW5zYWN0aW9u' + 'VHlwZRI5ChljcmVhdGVkX2F0X3VuaXhfdGltZXN0YW1wGAMgASgDUhZjcmVhdGVkQXRVbml4VG' + 'ltZXN0YW1wGkUKEUFkZGl0aW9uYWxBY2NvdW50EhcKB3VzZXJfaWQYASABKANSBnVzZXJJZBIX' + 'CgdwbGFuX2lkGAMgASgJUgZwbGFuSWQavgEKB1ZvdWNoZXISHQoKdm91Y2hlcl9pZBgBIAEoCV' + 'IJdm91Y2hlcklkEh8KC3ZhbHVlX2NlbnRzGAIgASgDUgp2YWx1ZUNlbnRzEhoKCHJlZGVlbWVk' + 'GAMgASgIUghyZWRlZW1lZBIcCglyZXF1ZXN0ZWQYBCABKAhSCXJlcXVlc3RlZBI5ChljcmVhdG' + 'VkX2F0X3VuaXhfdGltZXN0YW1wGAUgASgDUhZjcmVhdGVkQXRVbml4VGltZXN0YW1wGkoKCFZv' + 'dWNoZXJzEj4KCHZvdWNoZXJzGAEgAygLMiIuc2VydmVyX3RvX2NsaWVudC5SZXNwb25zZS5Wb3' + 'VjaGVyUgh2b3VjaGVycxqXBQoMUGxhbkJhbGxhbmNlEkAKHXVzZWRfZGFpbHlfbWVkaWFfdXBs' + 'b2FkX2xpbWl0GAEgASgDUhl1c2VkRGFpbHlNZWRpYVVwbG9hZExpbWl0Ej4KHHVzZWRfdXBsb2' + 'FkX21lZGlhX3NpemVfbGltaXQYAiABKANSGHVzZWRVcGxvYWRNZWRpYVNpemVMaW1pdBIzChNw' + 'YXltZW50X3BlcmlvZF9kYXlzGAMgASgDSABSEXBheW1lbnRQZXJpb2REYXlziAEBEksKIGxhc3' + 'RfcGF5bWVudF9kb25lX3VuaXhfdGltZXN0YW1wGAQgASgDSAFSHGxhc3RQYXltZW50RG9uZVVu' + 'aXhUaW1lc3RhbXCIAQESSgoMdHJhbnNhY3Rpb25zGAUgAygLMiYuc2VydmVyX3RvX2NsaWVudC' + '5SZXNwb25zZS5UcmFuc2FjdGlvblIMdHJhbnNhY3Rpb25zEl0KE2FkZGl0aW9uYWxfYWNjb3Vu' + 'dHMYBiADKAsyLC5zZXJ2ZXJfdG9fY2xpZW50LlJlc3BvbnNlLkFkZGl0aW9uYWxBY2NvdW50Uh' + 'JhZGRpdGlvbmFsQWNjb3VudHMSJgoMYXV0b19yZW5ld2FsGAcgASgISAJSC2F1dG9SZW5ld2Fs' + 'iAEBEkIKG2FkZGl0aW9uYWxfYWNjb3VudF9vd25lcl9pZBgIIAEoA0gDUhhhZGRpdGlvbmFsQW' + 'Njb3VudE93bmVySWSIAQFCFgoUX3BheW1lbnRfcGVyaW9kX2RheXNCIwohX2xhc3RfcGF5bWVu' + 'dF9kb25lX3VuaXhfdGltZXN0YW1wQg8KDV9hdXRvX3JlbmV3YWxCHgocX2FkZGl0aW9uYWxfYW' + 'Njb3VudF9vd25lcl9pZBpOCghMb2NhdGlvbhIWCgZjb3VudHkYASABKAlSBmNvdW50eRIWCgZy' + 'ZWdpb24YAiABKAlSBnJlZ2lvbhISCgRjaXR5GAMgASgJUgRjaXR5GjAKBlByZUtleRIOCgJpZB' + 'gBIAEoA1ICaWQSFgoGcHJla2V5GAIgASgMUgZwcmVrZXkalQEKDFNpZ25lZFByZUtleRIoChBz' + 'aWduZWRfcHJla2V5X2lkGAEgASgDUg5zaWduZWRQcmVrZXlJZBIjCg1zaWduZWRfcHJla2V5GA' + 'IgASgMUgxzaWduZWRQcmVrZXkSNgoXc2lnbmVkX3ByZWtleV9zaWduYXR1cmUYAyABKAxSFXNp' + 'Z25lZFByZWtleVNpZ25hdHVyZRr2AwoIVXNlckRhdGESFwoHdXNlcl9pZBgBIAEoA1IGdXNlck' + 'lkEjsKB3ByZWtleXMYAiADKAsyIS5zZXJ2ZXJfdG9fY2xpZW50LlJlc3BvbnNlLlByZUtleVIH' + 'cHJla2V5cxIfCgh1c2VybmFtZRgHIAEoDEgAUgh1c2VybmFtZYgBARIzChNwdWJsaWNfaWRlbn' + 'RpdHlfa2V5GAMgASgMSAFSEXB1YmxpY0lkZW50aXR5S2V5iAEBEigKDXNpZ25lZF9wcmVrZXkY' + 'BCABKAxIAlIMc2lnbmVkUHJla2V5iAEBEjsKF3NpZ25lZF9wcmVrZXlfc2lnbmF0dXJlGAUgAS' + 'gMSANSFXNpZ25lZFByZWtleVNpZ25hdHVyZYgBARItChBzaWduZWRfcHJla2V5X2lkGAYgASgD' + 'SARSDnNpZ25lZFByZWtleUlkiAEBEiwKD3JlZ2lzdHJhdGlvbl9pZBgIIAEoA0gFUg5yZWdpc3' + 'RyYXRpb25JZIgBAUILCglfdXNlcm5hbWVCFgoUX3B1YmxpY19pZGVudGl0eV9rZXlCEAoOX3Np' + 'Z25lZF9wcmVrZXlCGgoYX3NpZ25lZF9wcmVrZXlfc2lnbmF0dXJlQhMKEV9zaWduZWRfcHJla2' + 'V5X2lkQhIKEF9yZWdpc3RyYXRpb25faWQaWQoLVXBsb2FkVG9rZW4SIQoMdXBsb2FkX3Rva2Vu' + 'GAEgASgMUgt1cGxvYWRUb2tlbhInCg9kb3dubG9hZF90b2tlbnMYAiADKAxSDmRvd25sb2FkVG' + '9rZW5zGjkKDkRvd25sb2FkVG9rZW5zEicKD2Rvd25sb2FkX3Rva2VucxgBIAMoDFIOZG93bmxv' + 'YWRUb2tlbnMaRQoLUHJvb2ZPZldvcmsSFgoGcHJlZml4GAEgASgJUgZwcmVmaXgSHgoKZGlmZm' + 'ljdWx0eRgCIAEoA1IKZGlmZmljdWx0eRrDBwoCT2sSFAoETm9uZRgBIAEoCEgAUgROb25lEhgK' + 'BnVzZXJpZBgCIAEoA0gAUgZ1c2VyaWQSJgoNYXV0aGNoYWxsZW5nZRgDIAEoDEgAUg1hdXRoY2' + 'hhbGxlbmdlEkoKC3VwbG9hZHRva2VuGAQgASgLMiYuc2VydmVyX3RvX2NsaWVudC5SZXNwb25z' + 'ZS5VcGxvYWRUb2tlbkgAUgt1cGxvYWR0b2tlbhJBCgh1c2VyZGF0YRgFIAEoCzIjLnNlcnZlcl' + '90b19jbGllbnQuUmVzcG9uc2UuVXNlckRhdGFIAFIIdXNlcmRhdGESHgoJYXV0aHRva2VuGAYg' + 'ASgMSABSCWF1dGh0b2tlbhJBCghsb2NhdGlvbhgHIAEoCzIjLnNlcnZlcl90b19jbGllbnQuUm' + 'VzcG9uc2UuTG9jYXRpb25IAFIIbG9jYXRpb24SUAoNYXV0aGVudGljYXRlZBgIIAEoCzIoLnNl' + 'cnZlcl90b19jbGllbnQuUmVzcG9uc2UuQXV0aGVudGljYXRlZEgAUg1hdXRoZW50aWNhdGVkEj' + 'gKBXBsYW5zGAkgASgLMiAuc2VydmVyX3RvX2NsaWVudC5SZXNwb25zZS5QbGFuc0gAUgVwbGFu' + 'cxJNCgxwbGFuYmFsbGFuY2UYCiABKAsyJy5zZXJ2ZXJfdG9fY2xpZW50LlJlc3BvbnNlLlBsYW' + '5CYWxsYW5jZUgAUgxwbGFuYmFsbGFuY2USQQoIdm91Y2hlcnMYCyABKAsyIy5zZXJ2ZXJfdG9f' + 'Y2xpZW50LlJlc3BvbnNlLlZvdWNoZXJzSABSCHZvdWNoZXJzEl8KEmFkZGFjY291bnRzaW52aX' + 'RlcxgMIAEoCzItLnNlcnZlcl90b19jbGllbnQuUmVzcG9uc2UuQWRkQWNjb3VudHNJbnZpdGVz' + 'SABSEmFkZGFjY291bnRzaW52aXRlcxJTCg5kb3dubG9hZHRva2VucxgNIAEoCzIpLnNlcnZlcl' + '90b19jbGllbnQuUmVzcG9uc2UuRG93bmxvYWRUb2tlbnNIAFIOZG93bmxvYWR0b2tlbnMSTQoM' + 'c2lnbmVkcHJla2V5GA4gASgLMicuc2VydmVyX3RvX2NsaWVudC5SZXNwb25zZS5TaWduZWRQcm' + 'VLZXlIAFIMc2lnbmVkcHJla2V5EkoKC3Byb29mT2ZXb3JrGA8gASgLMiYuc2VydmVyX3RvX2Ns' + 'aWVudC5SZXNwb25zZS5Qcm9vZk9mV29ya0gAUgtwcm9vZk9mV29ya0IECgJPayKWAQoQVHJhbn' + 'NhY3Rpb25UeXBlcxIKCgZSZWZ1bmQQABITCg9Wb3VjaGVyUmVkZWVtZWQQARISCg5Wb3VjaGVy' + 'Q3JlYXRlZBACEggKBENhc2gQAxIPCgtQbGFuVXBncmFkZRAEEgsKB1Vua25vd24QBRIUChBUaG' + 'Fua3NGb3JUZXN0aW5nEAYSDwoLQXV0b1JlbmV3YWwQB0IKCghSZXNwb25zZQ=='); diff --git a/lib/src/providers/purchases.provider.dart b/lib/src/providers/purchases.provider.dart index 4ba1a2a..29ba6a8 100644 --- a/lib/src/providers/purchases.provider.dart +++ b/lib/src/providers/purchases.provider.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:convert'; import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:in_app_purchase/in_app_purchase.dart'; @@ -120,15 +121,36 @@ class PurchasesProvider with ChangeNotifier, DiagnosticableTreeMixin { } Future _verifyPurchase(PurchaseDetails purchaseDetails) async { + Log.info('verifying _verifyPurchase'); + if (Platform.isIOS) { + try { + var b64Data = purchaseDetails.verificationData.serverVerificationData + .split('.')[1]; + + final paddingNeeded = (4 - (b64Data.length % 4)) % 4; + b64Data += '=' * paddingNeeded; + + final jsonData = base64Decode(b64Data); + final data = jsonDecode(utf8.decode(jsonData)) as Map; + final expiresDate = data['expiresDate'] as int; + final dt = + DateTime.fromMillisecondsSinceEpoch(expiresDate, isUtc: true); + if (dt.isBefore(DateTime.now())) { + Log.warn('ExpiresDate is in the past: $dt'); + if (_userTriggeredBuyButton && Platform.isIOS) { + await launchUrl( + Uri.parse('https://apps.apple.com/account/subscriptions'), + mode: LaunchMode.externalApplication, + ); + } + return false; + } + } catch (e) { + Log.error(e); + } + } if (kDebugMode) { Log.info(purchaseDetails.productID); - Log.info(purchaseDetails.verificationData.serverVerificationData); - // if (Platform.isIOS) { - // final data = purchaseDetails.verificationData.serverVerificationData; - // printWrapped(data); - // final datas = data.split('.')[1]; - // printWrapped(datas); - // } Log.info(purchaseDetails.verificationData.source); } final res = await apiService.ipaPurchase( @@ -163,7 +185,9 @@ class PurchasesProvider with ChangeNotifier, DiagnosticableTreeMixin { Log.info( '_handlePurchase: ${purchaseDetails.productID}, ${purchaseDetails.status}', ); - if (purchaseDetails.status == PurchaseStatus.purchased) { + if (purchaseDetails.status == PurchaseStatus.purchased || + (purchaseDetails.status == PurchaseStatus.restored && + _userTriggeredBuyButton)) { await _verifyPurchase(purchaseDetails); } if (purchaseDetails.status == PurchaseStatus.restored && diff --git a/lib/src/services/api.service.dart b/lib/src/services/api.service.dart index 0245c5c..68e7489 100644 --- a/lib/src/services/api.service.dart +++ b/lib/src/services/api.service.dart @@ -595,20 +595,6 @@ class ApiService { return null; } - Future?> getAdditionalUserInvites() async { - final get = ApplicationData_GetAddAccountsInvites(); - final appData = ApplicationData()..getAddaccountsInvites = get; - final req = createClientToServerFromApplicationData(appData); - final res = await sendRequestSync(req); - if (res.isSuccess) { - final ok = res.value as server.Response_Ok; - if (ok.hasAddaccountsinvites()) { - return ok.addaccountsinvites.invites; - } - } - return null; - } - Future updatePlanOptions(bool autoRenewal) async { final get = ApplicationData_UpdatePlanOptions()..autoRenewal = autoRenewal; final appData = ApplicationData()..updatePlanOptions = get; @@ -623,6 +609,13 @@ class ApiService { return sendRequestSync(req, contactId: userId.toInt()); } + Future addAdditionalUser(Int64 userId) async { + final get = ApplicationData_AddAdditionalUser()..userId = userId; + final appData = ApplicationData()..addAdditionalUser = get; + final req = createClientToServerFromApplicationData(appData); + return sendRequestSync(req, contactId: userId.toInt()); + } + Future buyVoucher(int valueInCents) async { final get = ApplicationData_CreateVoucher()..valueCents = valueInCents; final appData = ApplicationData()..createVoucher = get; diff --git a/lib/src/views/settings/account.view.dart b/lib/src/views/settings/account.view.dart index 73e7809..eebbbbd 100644 --- a/lib/src/views/settings/account.view.dart +++ b/lib/src/views/settings/account.view.dart @@ -60,18 +60,18 @@ class _AccountViewState extends State { ), body: ListView( children: [ - ListTile( - title: const Text('Transfer account'), - subtitle: const Text('Coming soon'), - onTap: () async { - await showAlertDialog( - context, - 'Coming soon', - 'This feature is not yet implemented!', - ); - }, - ), - const Divider(), + // ListTile( + // title: const Text('Transfer account'), + // subtitle: const Text('Coming soon'), + // onTap: () async { + // await showAlertDialog( + // context, + // 'Coming soon', + // 'This feature is not yet implemented!', + // ); + // }, + // ), + // const Divider(), Container( alignment: Alignment.center, padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 20), @@ -99,15 +99,6 @@ class _AccountViewState extends State { ), ) : Text(context.lang.settingsAccountDeleteAccountNoBallance), - onLongPress: !kReleaseMode - ? () async { - await deleteLocalUserData(); - await Restart.restartApp( - notificationTitle: 'Account successfully deleted', - notificationBody: 'Click here to open the app again', - ); - } - : null, onTap: (formattedBallance == null) ? null : () async { diff --git a/lib/src/views/settings/developer/developer.view.dart b/lib/src/views/settings/developer/developer.view.dart index 7ad114f..1dcc1d8 100644 --- a/lib/src/views/settings/developer/developer.view.dart +++ b/lib/src/views/settings/developer/developer.view.dart @@ -1,8 +1,10 @@ import 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:restart_app/restart_app.dart'; import 'package:twonly/globals.dart'; import 'package:twonly/src/utils/storage.dart'; +import 'package:twonly/src/views/components/alert_dialog.dart'; import 'package:twonly/src/views/settings/developer/automated_testing.view.dart'; import 'package:twonly/src/views/settings/developer/retransmission_data.view.dart'; @@ -64,6 +66,23 @@ class _DeveloperSettingsViewState extends State { ); }, ), + ListTile( + title: const Text('Delete all (!) app data'), + onTap: () async { + final ok = await showAlertDialog( + context, + 'Sure?', + 'If you do not have a backup, you have to register with a new account.', + ); + if (ok) { + await deleteLocalUserData(); + await Restart.restartApp( + notificationTitle: 'Account successfully deleted', + notificationBody: 'Click here to open the app again', + ); + } + }, + ), ListTile( title: const Text('Disable ffmpeg'), subtitle: const Text( diff --git a/lib/src/views/settings/subscription/additional_users.view.dart b/lib/src/views/settings/subscription/additional_users.view.dart index ed86120..4b67ece 100644 --- a/lib/src/views/settings/subscription/additional_users.view.dart +++ b/lib/src/views/settings/subscription/additional_users.view.dart @@ -1,39 +1,19 @@ import 'dart:async'; -import 'dart:convert'; +import 'package:fixnum/fixnum.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:provider/provider.dart'; import 'package:twonly/globals.dart'; import 'package:twonly/src/database/daos/contacts.dao.dart'; import 'package:twonly/src/model/protobuf/api/websocket/error.pbserver.dart'; import 'package:twonly/src/model/protobuf/api/websocket/server_to_client.pb.dart'; -import 'package:twonly/src/utils/log.dart'; +import 'package:twonly/src/providers/purchases.provider.dart'; +import 'package:twonly/src/services/subscription.service.dart'; import 'package:twonly/src/utils/misc.dart'; -import 'package:twonly/src/utils/storage.dart'; import 'package:twonly/src/views/components/alert_dialog.dart'; +import 'package:twonly/src/views/settings/subscription/select_additional_users.view.dart'; import 'package:twonly/src/views/settings/subscription_custom/subscription.view.dart'; -Future?> loadAdditionalUserInvites() async { - final ballance = await apiService.getAdditionalUserInvites(); - if (ballance != null) { - await updateUserdata((u) { - u.additionalUserInvites = - jsonEncode(ballance.map((x) => x.writeToJson()).toList()); - return u; - }); - return ballance; - } - if (gUser.lastPlanBallance != null) { - try { - final decoded = jsonDecode(gUser.additionalUserInvites!) as List; - return decoded.map(Response_AddAccountsInvite.fromJson).toList(); - } catch (e) { - Log.error('could not parse additional user json: $e'); - } - } - return null; -} - class AdditionalUsersView extends StatefulWidget { const AdditionalUsersView({required this.ballance, super.key}); @@ -44,42 +24,94 @@ class AdditionalUsersView extends StatefulWidget { } class _AdditionalUsersViewState extends State { - List? additionalInvites; Response_PlanBallance? ballance; + late int _unusedAdditionalAccounts; + int _planLimit = 0; + @override void initState() { super.initState(); ballance = widget.ballance; + + final currentPlan = context.read().plan; + if (currentPlan == SubscriptionPlan.Pro || + currentPlan == SubscriptionPlan.Family) { + _planLimit = 1; + } else if (currentPlan == SubscriptionPlan.Family) { + _planLimit = 4; + } + _unusedAdditionalAccounts = + _planLimit - (ballance?.additionalAccounts.length ?? _planLimit); unawaited(initAsync(force: false)); } Future initAsync({required bool force}) async { - additionalInvites = await loadAdditionalUserInvites(); if (force) { ballance = await loadPlanBalance(); + _unusedAdditionalAccounts = + _planLimit - (ballance?.additionalAccounts.length ?? _planLimit); } setState(() {}); } + Future addAdditionalUser() async { + final selectedUserIds = await Navigator.push( + context, + MaterialPageRoute( + builder: (context) => SelectAdditionalUsers( + limit: _planLimit, + alreadySelected: ballance?.additionalAccounts + .map((e) => e.userId.toInt()) + .toList() ?? + [], + ), + ), + ) as List?; + if (selectedUserIds == null) return; + for (final selectedUserId in selectedUserIds) { + final res = await apiService.addAdditionalUser(Int64(selectedUserId)); + if (res.isError && mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(context.lang.additionalUserAddError)), + ); + } + } + await initAsync(force: true); + } + @override Widget build(BuildContext context) { - var plusInvites = []; - if (additionalInvites != null) { - plusInvites = - additionalInvites!.where((x) => x.planId == 'Plus').toList(); - } return Scaffold( appBar: AppBar( title: Text(context.lang.manageAdditionalUsers), ), body: ListView( children: [ + if (_unusedAdditionalAccounts > 0) + Center( + child: Padding( + padding: const EdgeInsets.all(12), + child: FilledButton( + onPressed: addAdditionalUser, + child: Text( + context.lang.additionalUserAddButton( + _planLimit, + ballance?.additionalAccounts.length ?? 0, + ), + ), + ), + ), + ), if (ballance != null && ballance!.additionalAccounts.isNotEmpty) - ListTile( - title: Text( - context.lang.additionalUsersList, - style: const TextStyle(fontSize: 13), + Padding( + padding: const EdgeInsets.only(top: 10), + child: ListTile( + title: Text( + context.lang.additionalUsersList, + textAlign: TextAlign.center, + style: const TextStyle(fontSize: 13), + ), ), ), if (ballance != null) @@ -91,22 +123,6 @@ class _AdditionalUsersViewState extends State { }, ), ), - if (plusInvites.isNotEmpty) - Text( - context.lang.additionalUsersPlusTokens, - textAlign: TextAlign.center, - style: const TextStyle(fontSize: 16), - ), - Padding( - padding: const EdgeInsets.all(16), - child: GridView.count( - crossAxisCount: 2, - physics: const NeverScrollableScrollPhysics(), - childAspectRatio: 16 / 5, - shrinkWrap: true, - children: plusInvites.map(AdditionalUserInvite.new).toList(), - ), - ), ], ), ); @@ -178,8 +194,8 @@ class _AdditionalAccountState extends State { onPressed: () async { final remove = await showAlertDialog( context, - 'Remove this additional user', - 'The additional user will automatically be downgraded to the free plan after removal and you will receive a new invitation code to give to another person.', + context.lang.additionalUserRemoveTitle, + context.lang.additionalUserRemoveDesc, ); if (remove) { final res = await apiService @@ -208,40 +224,3 @@ class _AdditionalAccountState extends State { ); } } - -class AdditionalUserInvite extends StatefulWidget { - const AdditionalUserInvite(this.invite, {super.key}); - final Response_AddAccountsInvite invite; - @override - State createState() => _AdditionalUserInviteState(); -} - -class _AdditionalUserInviteState extends State { - Future _copyVoucherId() async { - await Clipboard.setData(ClipboardData(text: widget.invite.inviteCode)); - await HapticFeedback.heavyImpact(); - if (!mounted) return; - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('${widget.invite.inviteCode} copied.')), - ); - } - - @override - Widget build(BuildContext context) { - return GestureDetector( - onTap: _copyVoucherId, - child: Card( - elevation: 3, - child: Center( - child: Text( - widget.invite.inviteCode.toUpperCase(), - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - ), - ), - ), - ), - ); - } -} diff --git a/lib/src/views/settings/subscription/select_additional_users.view.dart b/lib/src/views/settings/subscription/select_additional_users.view.dart new file mode 100644 index 0000000..20adc79 --- /dev/null +++ b/lib/src/views/settings/subscription/select_additional_users.view.dart @@ -0,0 +1,256 @@ +import 'dart:async'; +import 'dart:collection'; +import 'package:flutter/material.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:twonly/globals.dart'; +import 'package:twonly/src/database/daos/contacts.dao.dart'; +import 'package:twonly/src/database/twonly.db.dart'; +import 'package:twonly/src/utils/misc.dart'; +import 'package:twonly/src/views/components/avatar_icon.component.dart'; +import 'package:twonly/src/views/components/flame.dart'; +import 'package:twonly/src/views/components/user_context_menu.component.dart'; + +class SelectAdditionalUsers extends StatefulWidget { + const SelectAdditionalUsers({ + required this.alreadySelected, + required this.limit, + super.key, + }); + final List alreadySelected; + final int limit; + @override + State createState() => _SelectAdditionalUsers(); +} + +class _SelectAdditionalUsers extends State { + List contacts = []; + List allContacts = []; + final TextEditingController searchUserName = TextEditingController(); + late StreamSubscription> contactSub; + + final HashSet selectedUsers = HashSet(); + late HashSet _alreadySelected; + + @override + void initState() { + super.initState(); + + _alreadySelected = HashSet.from(widget.alreadySelected); + + final stream = twonlyDB.contactsDao.watchAllAcceptedContacts(); + + contactSub = stream.listen((update) async { + update.sort( + (a, b) => getContactDisplayName(a).compareTo(getContactDisplayName(b)), + ); + setState(() { + allContacts = update; + }); + await filterUsers(); + }); + } + + @override + void dispose() { + unawaited(contactSub.cancel()); + super.dispose(); + } + + Future filterUsers() async { + if (searchUserName.value.text.isEmpty) { + setState(() { + contacts = allContacts; + }); + return; + } + final usersFiltered = allContacts + .where( + (user) => getContactDisplayName(user) + .toLowerCase() + .contains(searchUserName.value.text.toLowerCase()), + ) + .toList(); + setState(() { + contacts = usersFiltered; + }); + } + + void toggleSelectedUser(int userId) { + if (_alreadySelected.contains(userId)) return; + if (!selectedUsers.contains(userId)) { + selectedUsers.add(userId); + } else { + selectedUsers.remove(userId); + } + setState(() {}); + } + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: () => FocusScope.of(context).unfocus(), + child: Scaffold( + appBar: AppBar( + title: Text(context.lang.additionalUserSelectTitle), + ), + floatingActionButton: FilledButton.icon( + onPressed: selectedUsers.isEmpty + ? null + : () => Navigator.pop(context, selectedUsers.toList()), + label: Text( + context.lang.additionalUserSelectButton( + widget.limit, + selectedUsers.length + widget.alreadySelected.length, + ), + ), + icon: const FaIcon(FontAwesomeIcons.userPlus), + ), + body: SafeArea( + child: Padding( + padding: + const EdgeInsets.only(bottom: 40, left: 10, top: 20, right: 10), + child: Column( + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 10), + child: TextField( + onChanged: (_) async { + await filterUsers(); + }, + controller: searchUserName, + decoration: getInputDecoration( + context, + context.lang.shareImageSearchAllContacts, + ), + ), + ), + const SizedBox(height: 10), + Expanded( + child: ListView.builder( + restorationId: 'new_message_users_list', + itemCount: + contacts.length + (selectedUsers.isEmpty ? 0 : 2), + itemBuilder: (BuildContext context, int i) { + if (selectedUsers.isNotEmpty) { + final selected = selectedUsers.toList(); + if (i == 0) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 18), + constraints: const BoxConstraints( + maxHeight: 150, + ), + child: SingleChildScrollView( + child: LayoutBuilder( + builder: (context, constraints) { + return Wrap( + spacing: 8, + children: selected.map((w) { + return _Chip( + contact: allContacts + .firstWhere((t) => t.userId == w), + onTap: toggleSelectedUser, + ); + }).toList(), + ); + }, + ), + ), + ); + } + if (i == 1) { + return const Divider(); + } + i -= 2; + } + final user = contacts[i]; + return UserContextMenu( + key: ValueKey(user.userId), + contact: user, + child: ListTile( + title: Row( + children: [ + Text(getContactDisplayName(user)), + FlameCounterWidget( + contactId: user.userId, + prefix: true, + ), + ], + ), + subtitle: (_alreadySelected.contains(user.userId)) + ? Text(context.lang.alreadyInGroup) + : null, + leading: AvatarIcon( + contactId: user.userId, + fontSize: 13, + ), + trailing: Checkbox( + value: selectedUsers.contains(user.userId) | + _alreadySelected.contains(user.userId), + side: WidgetStateBorderSide.resolveWith( + (states) { + if (states.contains(WidgetState.selected)) { + return const BorderSide(width: 0); + } + return BorderSide( + color: Theme.of(context).colorScheme.outline, + ); + }, + ), + onChanged: (bool? value) { + toggleSelectedUser(user.userId); + }, + ), + onTap: () { + toggleSelectedUser(user.userId); + }, + ), + ); + }, + ), + ), + ], + ), + ), + ), + ), + ); + } +} + +class _Chip extends StatelessWidget { + const _Chip({ + required this.contact, + required this.onTap, + }); + final Contact contact; + final void Function(int) onTap; + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: () => onTap(contact.userId), + child: Chip( + avatar: AvatarIcon( + contactId: contact.userId, + fontSize: 10, + ), + label: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + getContactDisplayName(contact), + style: const TextStyle(fontSize: 14), + overflow: TextOverflow.ellipsis, + ), + const SizedBox(width: 15), + const FaIcon( + FontAwesomeIcons.xmark, + color: Colors.grey, + size: 12, + ), + ], + ), + ), + ); + } +} diff --git a/lib/src/views/settings/subscription/subscription.view.dart b/lib/src/views/settings/subscription/subscription.view.dart index feb2c24..dbaf3bc 100644 --- a/lib/src/views/settings/subscription/subscription.view.dart +++ b/lib/src/views/settings/subscription/subscription.view.dart @@ -7,7 +7,6 @@ import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:provider/provider.dart'; import 'package:twonly/globals.dart'; import 'package:twonly/src/database/daos/contacts.dao.dart'; -import 'package:twonly/src/model/protobuf/api/websocket/error.pb.dart'; import 'package:twonly/src/model/protobuf/api/websocket/server_to_client.pb.dart'; import 'package:twonly/src/model/purchases/purchasable_product.dart'; import 'package:twonly/src/providers/purchases.provider.dart'; @@ -115,26 +114,33 @@ class _SubscriptionViewState extends State { onPurchase: initAsync, ), ], - if (currentPlan == SubscriptionPlan.Free) ...[ - const SizedBox(height: 10), - Center( - child: Padding( - padding: const EdgeInsets.all(14), - child: Text( - context.lang.redeemUserInviteCode, - textAlign: TextAlign.center, - style: const TextStyle(fontSize: 18), - ), - ), - ), - const SizedBox(height: 10), - PlanCard( - plan: SubscriptionPlan.Plus, - onPurchase: initAsync, - ), - ], const SizedBox(height: 10), - if (currentPlan != SubscriptionPlan.Family) const Divider(), + BetterListTile( + icon: FontAwesomeIcons.fileContract, + text: context.lang.termsOfService, + trailing: + const FaIcon(FontAwesomeIcons.arrowUpRightFromSquare, size: 15), + onTap: () async { + await launchUrl(Uri.parse('https://twonly.eu/de/legal/agb.html')); + }, + ), + BetterListTile( + leading: const FaIcon( + FontAwesomeIcons.gavel, + size: 15, + ), + text: context.lang.privacyPolicy, + trailing: + const FaIcon(FontAwesomeIcons.arrowUpRightFromSquare, size: 15), + onTap: () async { + await launchUrl( + Uri.parse('https://twonly.eu/de/legal/privacy.html'), + ); + }, + ), + if (isPayingUser(currentPlan) || + currentPlan == SubscriptionPlan.Tester) + const Divider(), if (isPayingUser(currentPlan) || currentPlan == SubscriptionPlan.Tester) BetterListTile( @@ -169,7 +175,7 @@ class PlanCard extends StatefulWidget { this.paidMonthly, }); final SubscriptionPlan plan; - final void Function()? onPurchase; + final Future Function()? onPurchase; final bool? paidMonthly; @override @@ -177,24 +183,22 @@ class PlanCard extends StatefulWidget { } String getFormattedPrice(PurchasableProduct product) { - if (product.price.contains('€')) { - return product.price.replaceAll(',00', '').replaceAll('.00', ''); - } return product.price; } class _PlanCardState extends State { + PurchasableProduct? _isLoading; Future onButtonPressed(PurchasableProduct? product) async { - if (widget.onPurchase == null) return; - if (widget.plan == SubscriptionPlan.Free || - widget.plan == SubscriptionPlan.Plus) { - await redeemUserInviteCode(context, SubscriptionPlan.Plus.name); - widget.onPurchase!(); - return; - } + if (widget.onPurchase == null || _isLoading != null) return; if (product == null) return; + setState(() { + _isLoading = product; + }); await context.read().buy(product); - widget.onPurchase!(); + await widget.onPurchase!(); + setState(() { + _isLoading = null; + }); } @override @@ -263,43 +267,6 @@ class _PlanCardState extends State { fontWeight: FontWeight.bold, ), ), - if (yearlyProduct != null && currentPlan != widget.plan) - const SizedBox(height: 10), - if (yearlyProduct != null && - widget.paidMonthly == null && - currentPlan != widget.plan) - Column( - children: [ - Text( - '${getFormattedPrice(yearlyProduct)}/${context.lang.year}', - textAlign: TextAlign.center, - style: const TextStyle( - fontSize: 20, - fontWeight: FontWeight.bold, - ), - ), - if (monthlyProduct != null) - Text( - '${getFormattedPrice(monthlyProduct)}/${context.lang.month}', - textAlign: TextAlign.center, - style: const TextStyle( - fontSize: 16, - color: Colors.grey, - ), - ), - ], - ), - if (widget.paidMonthly != null) - Text( - (widget.paidMonthly!) - ? '${getFormattedPrice(monthlyProduct!)}/${context.lang.month}' - : '${getFormattedPrice(yearlyProduct!)}/${context.lang.year}', - textAlign: TextAlign.center, - style: const TextStyle( - fontSize: 20, - fontWeight: FontWeight.bold, - ), - ), ], ), const SizedBox(height: 10), @@ -330,31 +297,43 @@ class _PlanCardState extends State { ), if (widget.onPurchase != null && monthlyProduct != null) OutlinedButton.icon( - onPressed: () => onButtonPressed(monthlyProduct), - label: (widget.plan == SubscriptionPlan.Free || - widget.plan == SubscriptionPlan.Plus) - ? Text(context.lang.redeemUserInviteCodeTitle) - : Text( - context.lang.upgradeToPaidPlanButton( - widget.plan.name, - ' (${context.lang.monthly})', - ), - ), + onPressed: _isLoading != null + ? null + : () => onButtonPressed(monthlyProduct), + icon: _isLoading == monthlyProduct + ? const SizedBox( + width: 10, + height: 10, + child: CircularProgressIndicator(strokeWidth: 1), + ) + : null, + label: Text( + context.lang.upgradeToPaidPlanButton( + widget.plan.name, + ' (${getFormattedPrice(monthlyProduct)}/${context.lang.month})', + ), + ), ), if (widget.onPurchase != null && (yearlyProduct != null || currentPlan == SubscriptionPlan.Free)) FilledButton.icon( - onPressed: () => onButtonPressed(yearlyProduct), - label: (widget.plan == SubscriptionPlan.Free || - widget.plan == SubscriptionPlan.Plus) - ? Text(context.lang.redeemUserInviteCodeTitle) - : Text( - context.lang.upgradeToPaidPlanButton( - widget.plan.name, - ' (${context.lang.yearly})', - ), - ), + onPressed: _isLoading != null + ? null + : () => onButtonPressed(yearlyProduct), + icon: _isLoading == yearlyProduct + ? const SizedBox( + width: 10, + height: 10, + child: CircularProgressIndicator(strokeWidth: 1), + ) + : null, + label: Text( + context.lang.upgradeToPaidPlanButton( + widget.plan.name, + ' (${getFormattedPrice(yearlyProduct!)}/${context.lang.year})', + ), + ), ), ], ), @@ -363,75 +342,3 @@ class _PlanCardState extends State { ); } } - -Future redeemUserInviteCode(BuildContext context, String newPlan) async { - var inviteCode = ''; - // ignore: inference_failure_on_function_invocation - await showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: Text(context.lang.redeemUserInviteCodeTitle), - content: StatefulBuilder( - builder: (BuildContext context, StateSetter setState) { - return SingleChildScrollView( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: const EdgeInsets.symmetric(vertical: 16), - child: TextField( - onChanged: (value) => setState(() { - inviteCode = value.toUpperCase(); - }), - decoration: InputDecoration( - labelText: context.lang.registerTwonlyCodeLabel, - border: const OutlineInputBorder(), - ), - textCapitalization: TextCapitalization.characters, - ), - ), - ], - ), - ); - }, - ), - actions: [ - TextButton( - onPressed: () { - Navigator.of(context).pop(); - }, - child: Text(context.lang.cancel), - ), - TextButton( - onPressed: () async { - final res = await apiService.redeemUserInviteCode(inviteCode); - if (!context.mounted) return; - if (res.isSuccess) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(context.lang.redeemUserInviteCodeSuccess), - ), - ); - // reconnect to load new plan. - await apiService.close(() {}); - await apiService.connect(); - } else { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - errorCodeToText(context, res.error as ErrorCode), - ), - ), - ); - } - if (!context.mounted) return; - Navigator.of(context).pop(); - }, - child: Text(context.lang.ok), - ), - ], - ); - }, - ); -} diff --git a/lib/src/views/settings/subscription_custom/subscription.view.dart b/lib/src/views/settings/subscription_custom/subscription.view.dart index 372bfb1..2a9eeb9 100644 --- a/lib/src/views/settings/subscription_custom/subscription.view.dart +++ b/lib/src/views/settings/subscription_custom/subscription.view.dart @@ -226,27 +226,6 @@ class _SubscriptionCustomViewState extends State { await initAsync(); }, ), - if (!isPayingUser(currentPlan)) ...[ - const SizedBox(height: 10), - Center( - child: Padding( - padding: const EdgeInsets.all(14), - child: Text( - context.lang.redeemUserInviteCode, - textAlign: TextAlign.center, - style: const TextStyle(fontSize: 18), - ), - ), - ), - const SizedBox(height: 10), - PlanCard( - plan: SubscriptionPlan.Plus, - onTap: () async { - await redeemUserInviteCode(context, SubscriptionPlan.Plus.name); - await initAsync(); - }, - ), - ], const SizedBox(height: 10), if (currentPlan != SubscriptionPlan.Family) const Divider(), BetterListTile( @@ -479,13 +458,9 @@ class PlanCard extends StatelessWidget { padding: const EdgeInsets.only(top: 10), child: FilledButton.icon( onPressed: onTap, - label: (plan == SubscriptionPlan.Free || - plan == SubscriptionPlan.Plus) - ? Text(context.lang.redeemUserInviteCodeTitle) - : Text( - context.lang - .upgradeToPaidPlanButton(plan.name, ''), - ), + label: Text( + context.lang.upgradeToPaidPlanButton(plan.name, ''), + ), ), ), ], @@ -496,75 +471,3 @@ class PlanCard extends StatelessWidget { ); } } - -Future redeemUserInviteCode(BuildContext context, String newPlan) async { - var inviteCode = ''; - // ignore: inference_failure_on_function_invocation - await showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: Text(context.lang.redeemUserInviteCodeTitle), - content: StatefulBuilder( - builder: (BuildContext context, StateSetter setState) { - return SingleChildScrollView( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: const EdgeInsets.symmetric(vertical: 16), - child: TextField( - onChanged: (value) => setState(() { - inviteCode = value.toUpperCase(); - }), - decoration: InputDecoration( - labelText: context.lang.registerTwonlyCodeLabel, - border: const OutlineInputBorder(), - ), - textCapitalization: TextCapitalization.characters, - ), - ), - ], - ), - ); - }, - ), - actions: [ - TextButton( - onPressed: () { - Navigator.of(context).pop(); - }, - child: Text(context.lang.cancel), - ), - TextButton( - onPressed: () async { - final res = await apiService.redeemUserInviteCode(inviteCode); - if (!context.mounted) return; - if (res.isSuccess) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(context.lang.redeemUserInviteCodeSuccess), - ), - ); - // reconnect to load new plan. - await apiService.close(() {}); - await apiService.connect(); - } else { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - errorCodeToText(context, res.error as ErrorCode), - ), - ), - ); - } - if (!context.mounted) return; - Navigator.of(context).pop(); - }, - child: Text(context.lang.ok), - ), - ], - ); - }, - ); -}