From 5761b25e1c6be14d3b8ab09b43f5685b8ab5b8db Mon Sep 17 00:00:00 2001
From: mercuriosilber <35112265+mercuriosilber@users.noreply.github.com>
Date: Tue, 12 Jun 2018 14:50:45 +0200
Subject: [PATCH 01/21] Update InfoPlist.strings
German translation added
---
Adamant/Assets/l18n/de.lproj/InfoPlist.strings | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/Adamant/Assets/l18n/de.lproj/InfoPlist.strings b/Adamant/Assets/l18n/de.lproj/InfoPlist.strings
index c292cb899..5397761b3 100644
--- a/Adamant/Assets/l18n/de.lproj/InfoPlist.strings
+++ b/Adamant/Assets/l18n/de.lproj/InfoPlist.strings
@@ -5,14 +5,14 @@
"CFBundleName" = "Adamant";
/* Privacy - Camera Usage Description */
-"NSCameraUsageDescription" = "The camera needed to scan QR codes for addresses and passphrases";
+"NSCameraUsageDescription" = "Die Kamera wird benötigt, um QR-Codes für Adressen und Passphrases zu scannen";
/* Privacy - Face ID Usage Description */
-"NSFaceIDUsageDescription" = "Sign In into your ADAMANT with Face ID";
+"NSFaceIDUsageDescription" = "In dein ADAMANT mit Face ID einloggen";
/* Privacy - Photo Library Additions Usage Description */
-"NSPhotoLibraryAddUsageDescription" = "Saving generated QR codes with passphrases and addresses";
+"NSPhotoLibraryAddUsageDescription" = "Generierte QR-Codes mit Passphrases und Adressen werden gespeichert";
/* Privacy - Photo Library Usage Description */
-"NSPhotoLibraryUsageDescription" = "Reading QR codes with passphrases and addresses";
+"NSPhotoLibraryUsageDescription" = "QR-Codes mit Passphrases und Adressen werden gelesen";
From 2ce6169f1c80681152862a75e465e8fe5cd8f347 Mon Sep 17 00:00:00 2001
From: mercuriosilber <35112265+mercuriosilber@users.noreply.github.com>
Date: Tue, 19 Jun 2018 15:07:05 +0200
Subject: [PATCH 02/21] German translation added
I will look at the translation in the app to correct some words later (I need to see the context)
---
.../Assets/l18n/de.lproj/Localizable.strings | 304 +++++++++---------
1 file changed, 152 insertions(+), 152 deletions(-)
diff --git a/Adamant/Assets/l18n/de.lproj/Localizable.strings b/Adamant/Assets/l18n/de.lproj/Localizable.strings
index 25b5e111a..a30e7c192 100755
--- a/Adamant/Assets/l18n/de.lproj/Localizable.strings
+++ b/Adamant/Assets/l18n/de.lproj/Localizable.strings
@@ -1,20 +1,20 @@
/* AccountsProvider: Address not valid error, %@ for address */
-"AccountsProvider.Error.AddressNotValidFormat" = "Invalid address: %@";
+"AccountsProvider.Error.AddressNotValidFormat" = "Ungültige Adresse: %@";
/* Login: user typed in invalid passphrase */
-"AccountServiceError.InvalidPassphrase" = "Wrong passphrase";
+"AccountServiceError.InvalidPassphrase" = "Falsche Passphrase";
/* Login: user typed in wrong passphrase */
-"AccountServiceError.WrongPassphrase" = "Wrong passphrase";
+"AccountServiceError.WrongPassphrase" = "Falsche Passphrase";
/* Account tab: Confirm logout alert: Logout (Ok) button */
-"AccountTab.ConfirmLogout.Logout" = "Logout";
+"AccountTab.ConfirmLogout.Logout" = "Ausloggen";
/* Account tab: Confirm logout alert */
-"AccountTab.ConfirmLogout.MessageFormat" = "Logout from %@?";
+"AccountTab.ConfirmLogout.MessageFormat" = "Ausloggen aus %@?";
/* Account tab: Failed to update account message. %@ for error message */
-"AccountTab.Error.FailedToUpdateAccountFormat" = "Failed to update account: %@";
+"AccountTab.Error.FailedToUpdateAccountFormat" = "Fehler beim Aktualisieren des Konto: %@";
/* Account atb: A full 'Get free tokens' link, with %@ as address */
"AccountTab.FreeTokens.UrlFormat" = "https://adamant.im/free-adm-tokens/?wallet=%@";
@@ -23,37 +23,37 @@
"AccountTab.JoinIco.UrlFormat" = "https://adamant.im/ico/?wallet=%@";
/* Account tab: Balance row title */
-"AccountTab.Row.Balance" = "Balance";
+"AccountTab.Row.Balance" = "Kontostand";
/* Account tab: 'Join the ICO' button */
-"AccountTab.Row.JoinIco" = "Join the ICO";
+"AccountTab.Row.JoinIco" = "Zur ICO";
/* Account tab: 'Logout' button */
-"AccountTab.Row.Logout" = "Logout";
+"AccountTab.Row.Logout" = "Ausloggen";
/* Account tab: 'Send tokens' button */
-"AccountTab.Row.SendTokens" = "Send Tokens";
+"AccountTab.Row.SendTokens" = "Tokens senden";
/* Account tab: 'Get free tokens' button */
-"AccountTab.Row.FreeTokens" = "Free ADM tokens";
+"AccountTab.Row.FreeTokens" = "Kostenlose ADM Tokens";
/* Account tab: Account section title. */
-"AccountTab.Section.Account" = "Account";
+"AccountTab.Section.Account" = "Konto";
/* Account tab: Actions section title */
-"AccountTab.Section.Actions" = "Actions";
+"AccountTab.Section.Actions" = "Optionen";
/* Account tab: Wallet section title */
"AccountTab.Section.Wallet" = "Wallet";
/* Account page: scene title */
-"AccountTab.Title" = "Account";
+"AccountTab.Title" = "Konto";
/* Account tab: 'Transfer not allowed' alert 'go to WebApp button' */
"AccountTab.TransferBlocked.GoToPWA" = "msg.adamant.im";
/* Account tab: Inform user that sending tokens not allowed by Apple until the end of ICO */
-"AccountTab.TransferBlocked.Message" = "Due to Apple restrictions, sending tokens is not allowed until the end of the ICO. For now, you can send tokens using WebApp at msg.adamant.im";
+"AccountTab.TransferBlocked.Message" = "Aufgrund von Richtlinien von Apple, Versenden von Tokens ist während des ICO nicht möglich. Zurzeit können Sie Tokens mit der WebApp unter msg.adamant.im versenden";
/* Account tab: 'Transfer not allowed' alert title */
"AccountTab.TransferBlocked.Title" = "Sorry!";
@@ -62,446 +62,446 @@
"ADAMANT" = "ADAMANT";
/* ApiService: No connection message. Generally bad network. */
-"ApiService.Error.NoConnection" = "No connection to the Internet";
+"ApiService.Error.NoConnection" = "Keine Internetverbindung";
/* Serious internal error: Failed to build endpoint url */
-"ApiService.InternalError.EndpointBuildFailed" = "Endpoint build failed. Report a bug";
+"ApiService.InternalError.EndpointBuildFailed" = "Endpoint Build fehlgeschlagen. Bericht senden";
/* Serious internal error: Failed to sign transaction */
-"ApiService.InternalError.FailedTransactionSigning" = "Transaction failed";
+"ApiService.InternalError.FailedTransactionSigning" = "Transaktion fehlgeschlagen";
/* Serious internal error: Error parsing response */
-"ApiService.InternalError.ParsingFailed" = "Parsing failed. Report a bug";
+"ApiService.InternalError.ParsingFailed" = "Parsing fehlgeschlagen. Bericht senden";
/* Unknown internal error */
-"ApiService.InternalError.UnknownError" = "Unknown error. Report a bug";
+"ApiService.InternalError.UnknownError" = "Unbekannter Fehler. Bericht senden";
/* Eureka forms Cancel button */
-"Cancel" = "Cancel";
+"Cancel" = "Abbrechen";
/* ChatList: outgoing message preview format, like 'You: %@' */
-"ChatListPage.SentMessageFormat" = "You: %@";
+"ChatListPage.SentMessageFormat" = "Sie: %@";
/* ChatList: scene title */
"ChatListPage.Title" = "Chats";
/* ChatsProvider: Notify user that he doesn't have money to pay a message fee */
-"ChatsProvider.Error.NotEnoughtMoney" = "You don't have enough money to send a message";
+"ChatsProvider.Error.NotEnoughtMoney" = "Sie haben nicht genügend Tokens, um eine Nachricht zu senden";
/* ChatsProvider: Transaction not found error. %@ for transaction's ID */
-"ChatsProvider.Error.TransactionNotFoundFormat" = "Message with id %@ not found";
+"ChatsProvider.Error.TransactionNotFoundFormat" = "Nachricht mit der ID %@ nicht gefunden";
/* ChatsProvider: Validation error: Message is empty */
-"ChatsProvider.Validation.MessageIsEmpty" = "Message is empty";
+"ChatsProvider.Validation.MessageIsEmpty" = "Nachricht ist leer";
/* ChatsProvider: Validation error: Message is too long */
-"ChatsProvider.Validation.MessageTooLong" = "Message is too long";
+"ChatsProvider.Validation.MessageTooLong" = "Nachricht ist zu lang";
/* Known contacts: Adamant ICO message. Markdown supported. */
-"Chats.IcoMessage" = "You have a possibility to **Join the ICO** of ADAMANT, the most secure and anonymous messenger. Learn more on Adamant.im website or in the Whitepaper.\n\nPlease make sure your have saved the passphrase to this account — exit your account and open it again. Better save your password on a sheet of paper also. But remember, only **you are responsible for the passphrase safety**. It cannot be recovered. And if other person will get it, your money will be stolen. Treat this question as secure, as if tokens in your wallet will cost a billion dollars some time.\n\nTo buy ADM tokens, go to Wallet tab and click Join the ICO, or open the page Adamant.im/ico/ in the web browser. In the ICO form enter your ADAMANT address — you will receive ADM tokens there. If you moved from Messenger App, your ADM address will be filled already, other way go back to Messenger and click ADAMANT address to copy it to clipboard. Next, choose crypto you want to spend and its amount. You'll see how much ADM tokens you will receive, including volume bonus: 20–30 ETH: +20%, 30–50 ETH: +30%, 50–90 ETH: +40%, 90+ ETH: +50%. Click Buy ADAMANT tokens. You'll get unique address where you need to send crypto. As soon as your transaction will be confirmed, you'll receive ADM tokens. Transaction can be done **from any wallet or exchange**. It is not necessary to transfer exact amount including transaction fees, the payment will be processed anyway.\nAfter you receive ADM tokens, we recommend to keep them as long as possible. All of unsold tokens during ICO will be distributed among users' wallets, adding 5% monthly. Additional info is on Adamant.im website and in the Whitepaper.\n\nDo not reply to this message, it is a system account. If you still have any questions, contact account **U15677078342684640219**";
+"Chats.IcoMessage" = "Sie haben die Möglichkeit, **am ICO** von ADAMANT, dem sichersten Messenger überhaupt, teilzunehmen. Mehr dazu auf Adamant.im oder im Whitepaper.\n\nBitte vergewissern Sie sich, dass sie die Passphrase für dieses Konto gemerkt haben — loggen Sie sich aus und wieder ein. Wir empfehlen Ihnen, diese auf einem Blatt Papier aufzuschreiben. Bitte beachten: nur **Sie sind für die Sicherheit der Passphrase zuständig**. Sie kann nicht wiederhergestellt werden. Wenn Dritte an die Passphrase kommen, Ihre Tokens werden gestohlen. Behandeln sie die Passphrase so, als würden in Ihrer Wallet mehrere Millionen Dollar liegen.\n\nUm die ADM Tokens zu kaufen, gehen Sie zur Wallet und klicken Sie Zur ICO oder öffnen Sie Adamant.im/ico/ im Browser. Im ICO-Formular geben Sie Ihre ADAMANT-Adresse ein. Als Nächstes, wählen Sie Kryptowährung, mit der Sie zahlen möchten, und den Betrag. Es wird angezeigt, wie viele ADM Tokens Sie bekommen, inklusive Bonus: 20–30 ETH: +20%, 30–50 ETH: +30%, 50–90 ETH: +40%, 90+ ETH: +50%. Klicken Sie auf ADAMANT Tokens kaufen. Es wird eine Adresse angezeigt, wo Sie Kryptowährung senden sollen. Sobald Ihre Transaktion bestätigt wurde, erhalten Sie Ihre ADM Tokens. Die Zahlung kann **von jeder Wallet oder Börse** getätigt werden. Es ist nicht notwendig, den exakten Betrag inklusive der Transaktionsgebühren zu senden, die Zahlung wird trotzdem bearbeitet.\nNachdem Sie Ihre ADM Tokens erhalten haben, empfehlen wir Ihnen, sie so lange wie möglich zu behalten. Alle während des ICO nicht verkaufte Tokens werden unter den Wallets verteilt, 5% im Monat werden werden hinzugefügt. Zusätzliche Informationen finden Sie auf Adamant.im und im Whitepaper.\n\nBitte nicht auf diese Nachricht antworten, sie wurde maschinell erstellt. Wenn Sie noch Fragen haben, schreiben Sie **U15677078342684640219** an";
/* Known contacts: Adamant pre ICO message */
"Chats.PreIcoMessage" = "You have a possibility to invest in ADAMANT, the most secure and anonymous messenger. Now is a Pre-ICO stage — the most profitable for investors. Learn more on Adamant.im website or in the Whitepaper. To participate just reply to this message and we will assist. We are eager to answer quickly, but sometimes delays for a couple of hours are possible.\nAfter you invest and receive ADM tokens, we recommend to keep them as long as possible. All of unsold tokens during ICO will be distributed among users wallets, adding 5% monthly. Additional info is on Adamant.im website and in the Whitepaper.";
/* Known contacts: Adamant welcome message. Markdown supported. */
-"Chats.WelcomeMessage" = "Welcome to ADAMANT, the most secure and anonymous messenger. Remember, your security and anonymity is up to you also. Do not follow links you receive, otherwise your IP can be compromised. Do not trust browser extensions. Better to share your ADM address personally, but not using other messengers. Keep your secret passphrase secure. Set a password on your device or logout before leaving.\nLearn more about security and anonymity at https://adamant.im/staysecured.\nAll the transactions within the blockchain do need to have their minimal fees. This is necessary to support the network infrastructure. To start messengning now, **get free tokens** — go to Wallet tab and click Free ADM tokens. Then click Start new chat on Chats tab and put your interlocutor's address. To copy your address, click on ADAMANT address on Wallet tab.\nDo not reply to this message, it is a system account. If you still have any questions, contact account U15677078342684640219";
+"Chats.WelcomeMessage" = "Willkommen im ADAMANT, dem sichersten und anonymsten Messenger überhaupt. Beachten Sie, Ihre Sicherheit und Anonymität sind in Ihrem Interesse. Folgen Sie keinen Links, andernfalls kann Ihre IP-Adresse für Dritte sichtbar werden. Vertrauen Sie keinen Browser-Erweiterungen. Übergeben Sie Ihre ADM-Adresse persönlich, nutzen Sie dafür keine anderen Messenger. Halten Sie Ihre Passphrase geheim. Setzen Sie ein Passwort auf Ihrem Gerät oder loggen Sie sich aus, wenn Sie es nicht nutzen.\nLesen Sie mehr über Sicherheit und Anonymität unter https://adamant.im/staysecured.\nAlle Transaktionen auf der Blockchain müssen eine minimale Gebühr haben. Das ist nötig, um die Netzwerkinfrastruktur zu unterstützen. Um jetzt Nachrichten zu versenden, **erhalten Sie kostenlose Tokens** — gehen Sie zu Wallet und klicken Kostenlose ADM Tokens. Dann klicken Sie Neuen Chat starten in Chats und geben Sie die Adresse Ihres Gesprächspartners ein. Um Ihre Adresse zu kopieren, klicken Sie auf die ADAMANT-Adresse in Ihrer Wallet.\nBitte nicht auf diese Nachricht antworten, sie wurde maschinell erstellt. Wenn Sie noch Fragen haben, schreiben Sie **U15677078342684640219** an";
-/* Alert 'Retry Or Delete' title. Used in caht for sending failed messages again or delete them */
-"Chats.RetryOrDelete.Title" = "Retry or delete?";
+/* Alert 'Retry Or Delete' title. Used in chat for sending failed messages again or delete them */
+"Chats.RetryOrDelete.Title" = "Erneut versuchen oder löschen?";
/* Alert 'Retry Or Delete' body message. Used in caht for sending failed messages again or delete them */
-"Chats.RetryOrDelete.Body" = "Do you whant to send message again or delete it?";
+"Chats.RetryOrDelete.Body" = "Möchten Sie versuchen, die Nachricht nochmal zu versenden oder diese löschen?";
/* Chat: inform user that he can't cancel transaction, that was sent */
-"ChatScene.Error.cancelError" = "Message already sended";
+"ChatScene.Error.cancelError" = "Nachricht bereits versendet";
/* Chat: message input placeholder */
-"ChatScene.NewMessage.Placeholder" = "New message";
+"ChatScene.NewMessage.Placeholder" = "Neue Nachricht";
/* Chat: Send message button */
-"ChatScene.Send" = "Send";
+"ChatScene.Send" = "Senden";
/* Chat: 'Sent funds' bubble title */
-"ChatScene.Sent" = "Sent";
+"ChatScene.Sent" = "Versendet";
/* Chat: 'Sent funds' buble 'Tap for details' tip */
-"ChatScene.tapForDetails" = "Tap for details";
+"ChatScene.tapForDetails" = "Tippen Sie für Details";
/* Shared error: Account not found error. Using %@ for address. */
-"Error.AccountNotFoundFormat" = "Account not found: %@";
+"Error.AccountNotFoundFormat" = "Konto nicht gefunden: %@";
/* Shared error: Internal error format, %@ for message */
-"Error.InternalErrorFormat" = "Internal error: %@";
+"Error.InternalErrorFormat" = "Interner Fehler: %@";
/* Error messge title for support email */
-"Error.Mail.Title" = "I have error in my iOS app";
+"Error.Mail.Title" = "Ich habe einen Fehler in meiner iOS-App";
/* Error messge body for support email */
-"Error.Mail.Body" = "Hello,\nI have this error:\n\n%@\n\nMy device info:\n%@";
+"Error.Mail.Body" = "Hallo,\nIch habe den folgenden Fehler:\n\n%@\n\nMeine Geräte-Info:\n%@";
/* Error messge body for support email, with detailed error description. Where first %@ - error's short message, second %@ - detailed description, third %@ - deviceInfo */
-"Error.Mail.Body.Detailed" = "Hello,\nI have this error:\n\n%@\n\n%@\n\nDevice:\n%@";
+"Error.Mail.Body.Detailed" = "Hallo,\nIch habe den folgenden Fehler:\n\n%@\n\n%@\n\nGerät:\n%@";
/* Shared error: Network problems. In most cases - no connection */
-"Error.NoNetwork" = "No connection";
+"Error.NoNetwork" = "Keine Verbindung";
/* Shared error: Remote error format, %@ for message */
-"Error.RemoteServerErrorFormat" = "Blockchain Node error: %@";
+"Error.RemoteServerErrorFormat" = "Blockchain Node Fehler: %@";
/* Shared error: User not logged */
-"Error.UserNotLogged" = "User not logged";
+"Error.UserNotLogged" = "Sie sind nicht eingeloggt";
/* Login: Notify user, that he disabled camera in settings, and need to authorize application. */
-"LoginScene.Error.AuthorizeCamera" = "You need to authorize ADAMANT to use device's camera";
+"LoginScene.Error.AuthorizeCamera" = "Sie müssen die Kamera freigeben, damit ADAMANT die Gerätekamera verwenden kann";
/* Login: User disabled access to photolibrary, he can authorize application in settings */
-"LoginScene.Error.AuthorizePhotolibrary" = "You need to authorize ADAMANT to use your Photo library";
+"LoginScene.Error.AuthorizePhotolibrary" = "Sie müssen ADAMANT für die Verwendung der Fotoalben autorisieren";
/* Login: No network error. */
-"LoginScene.Error.NoInternet" = "No connection to the Internet";
+"LoginScene.Error.NoInternet" = "Keine Internetverbindung";
/* Login: notify user that he is trying to login without a passphrase */
-"LoginScene.Error.NoPassphrase" = "Enter a passphrase";
+"LoginScene.Error.NoPassphrase" = "Geben Sie die Passphrase ein";
/* Login: Notify user that picked photo doesn't contains a valid qr code with passphrase */
-"LoginScene.Error.NoQrOnPhoto" = "Selected image does not contain valid QR codes";
+"LoginScene.Error.NoQrOnPhoto" = "Das ausgewählte Bild enthält keinen gültigen QR-Code";
/* Login: Notify user that device not supported by QR reader */
-"LoginScene.Error.QrNotSupported" = "QR code reading is not supported by the device";
+"LoginScene.Error.QrNotSupported" = "Ihr Gerät unterstützt nicht das Lesen von QR-Codes";
/* Login: Notify user that scanned QR doesn't contains a passphrase. */
-"LoginScene.Error.WrongQr" = "QR code does not contains a valid passphrase";
+"LoginScene.Error.WrongQr" = "Der QR-Code enthält keine gültige Passphrase";
/* Login: notify user that we are trying to log in */
-"LoginScene.LoggingInProgress" = "Logging in…";
+"LoginScene.LoggingInProgress" = "Einloggen…";
/* Login: Login into previous account with biometry or pincode */
-"LoginScene.LoginIntoAdamant" = "Login into ADAMANT";
+"LoginScene.LoginIntoAdamant" = "In ADAMANT einloggen";
/* Login: generate new passphrase button */
-"LoginScene.Row.Generate" = "Generate new passphrase";
+"LoginScene.Row.Generate" = "Neue Passphrase generieren";
/* Login: Login button */
-"LoginScene.Row.Login" = "Login";
+"LoginScene.Row.Login" = "Einloggen";
/* Login: Passphrase placeholder */
"LoginScene.Row.Passphrase.Placeholder" = "Passphrase";
/* Login: Login with pincode button */
-"LoginScene.Row.Pincode" = "Login with PIN code";
+"LoginScene.Row.Pincode" = "Mit der PIN einloggen";
/* Login: Login with QR button. */
-"LoginScene.Row.Qr" = "Login with QR code";
+"LoginScene.Row.Qr" = "Mit dem QR-Code einloggen";
/* Login: security alert, notify user that he must save his new passphrase */
-"LoginScene.Row.SavePassphraseAlert" = "Save the passphrase for new Wallet and Messenger account. There is no login to enter Wallet, only the passphrase needed. If lost, no way to recover it.";
+"LoginScene.Row.SavePassphraseAlert" = "Speichern Sie die Passphrase für die neue Wallet und das Messenger-Konto. Es gibt keinen Login für die Wallet, nur die Passphrase wird benötigt. Wenn Sie verloren geht, kann sie nicht wiederhergestellt werden.";
/* Login: a small hint for a user, that he can tap on passphrase to save it */
-"LoginScene.Row.TapToSave" = "Tap to save";
+"LoginScene.Row.TapToSave" = "Tippen, um zu speichern";
/* Login: login with existing passphrase section */
-"LoginScene.Section.Login" = "Login";
+"LoginScene.Section.Login" = "Einloggen";
/* Login: Create new account section */
-"LoginScene.Section.NewAccount" = "New account";
+"LoginScene.Section.NewAccount" = "Neues Konto";
/* New chat: Recipient address placeholder. Note that address text field always shows U letter, so you can left this line blank. */
"NewChatScene.Address.Placeholder" = "";
/* New chat: Notify user that specified address (%@) not found. Using %@ for address */
-"NewChatScene.Error.AddressNotFoundFormat" = "Address not found: %@";
+"NewChatScene.Error.AddressNotFoundFormat" = "Adresse nicht gefunden: %@";
/* New chat: Notify user that he did enter invalid address */
-"NewChatScene.Error.InvalidAddress" = "Enter a valid address";
+"NewChatScene.Error.InvalidAddress" = "Geben Sie eine gültige Adresse ein";
/* New chat: Notify user that he can't start chat with himself */
-"NewChatScene.Error.OwnAddress" = "You don't need an encrypted anonymous chat to talk to yourself";
+"NewChatScene.Error.OwnAddress" = "Sie brauchen keinen anonymen und verschlüsselten Chat, um mit sich selbst zu reden";
/* New Chat: Notify user that scanned QR doesn't contains an address */
-"NewChatScene.Error.WrongQr" = "QR code does not contain a valid address";
+"NewChatScene.Error.WrongQr" = "Der QR-Code enthält keine gültige Adresse";
/* New chat: Scan QR with address button */
-"NewChatScene.ScanQr" = "Scan QR code";
+"NewChatScene.ScanQr" = "QR-Code scannen";
/* New chat: scene title */
-"NewChatScene.Title" = "New Chat";
+"NewChatScene.Title" = "Neuer Chat";
/* NodesList: Button label */
-"NodesList.NodesList" = "Nodes list";
+"NodesList.NodesList" = "Node-Liste";
/* NodesList: scene title */
-"NodesList.Title" = "List of used nodes";
+"NodesList.Title" = "Liste der benutzten Nodes";
/* NodesList: 'Saved' message */
-"NodesList.Saved" = "Saved";
+"NodesList.Saved" = "Gespeichert";
/* NodesList: 'Unable To Save' message */
-"NodesList.UnableToSave" = "Unable to save";
+"NodesList.UnableToSave" = "Kann nicht gespeichert werden";
/* NodesList: 'Add new node' button lable */
-"NodesList.AddNewNode" = "Add new node";
+"NodesList.AddNewNode" = "Eine neue Node hinzufügen";
/* NodesList: 'Node url' plaseholder */
-"NodesList.NodeUrl" = "Node url";
+"NodesList.NodeUrl" = "Node-URL";
/* Notifications: Modes description. Markdown supported. */
-"Notifications.ModesDescription" = "#### Notification modes\n\n#### Disabled\nNo notifications.\n\n#### Background Fetch\nYour device fetchs for new messages by itself. No external calls. Fetch is initiated by iOS, the actual time determined by the operating system based on many factors like battery charge, cellular network, application usage patterns and cannot be predicted. This can be 20 minutes, or 6 hours, or maybe even a day. You still can open app and check for a new message though.\n\n#### Push\nNotifications sent to your device by ADAMANT Notification Service. You receive notification almost instantly after a message was sent and approved by the Blockchain - a few seconds delay. But this mode requires your device to register it's Device Token in the Service's database. Device tokens are safe and secure, and this option is recommended in most cases.\n\nYou can read more about device registration on ADAMANT's Github page.";
+"Notifications.ModesDescription" = "#### Benachrichtigungsmodi\n\n#### Deaktiviert\nKeine Benachrichtigungen.\n\n#### Hintergrundaktualisierung\nIhr Gerät erhält neue Nachrichteninformationen automatisch. Keine externen Aufrufe. Die Hintergrundaktualisierung wird von iOS verwaltet, die Zeit wird vom Betriebssystem bestimmt und ist von vielen Faktoren wie Akkustand, Netzwerkauslastung, Nutzungsmuster abhängig und kann nicht vorhergesagt werden. Es können 20 Minuten, 6 Stunden, oder ein Tag sein. Sie können die App trotzdem öffnen und nachschauen, ob eine Nachricht angekommen ist.\n\n#### Push\nBenachrichtigungen werden auf Ihr Gerät vom ADAMANT Benachrichtigungsservice gesendet. Sie erhalten eine Benachrichtigung umgehend, nachdem die Nachricht versendet und von der Blockchain bestätigt wurde - mit einer kleinen Verzögerung. Jedoch erfordert dieser Modus, dass der Gerätetoken Ihres Geräts in der Servicedatenbank registriert ist. Gerätetokens sind sicher, und diese Option ist zu empfehlen.\n\nSie können mehr über die Geräteregistrierung auf der ADAMANTs Github-Seite nachlesen.";
/* Notifications: Notifications update mode */
-"Notifications.Section.Settings" = "Notifications settings";
+"Notifications.Section.Settings" = "Benachrichtigungseinstellungen";
/* Notifications: Selected notifications types */
-"Notifications.Section.NotificationsType" = "Notifications";
+"Notifications.Section.NotificationsType" = "Benachrichtigungen";
/* Notifications: About ANS */
-"Notifications.Section.AboutANS" = "About ANS";
+"Notifications.Section.AboutANS" = "Über ANS";
/* Notifications: Disable notifications */
-"Notifications.Mode.NotificationsDisabled" = "Disabled";
+"Notifications.Mode.NotificationsDisabled" = "Deaktiviert";
/* Notifications: Use Background fetch notifications */
-"Notifications.Mode.BackgroundFetch" = "Background Fetch";
+"Notifications.Mode.BackgroundFetch" = "Hintergrundaktualisierung";
/* Notifications: Use Apple Push notifications */
"Notifications.Mode.ApplePush" = "Push";
/* Notifications: Visit Github */
-"Notifications.Row.VisitGithub" = "Visit Github";
+"Notifications.Row.VisitGithub" = "Github besuchen";
/* Notifications: Mode */
-"Notifications.Row.Mode" = "Notifications mode";
+"Notifications.Row.Mode" = "Benachrichtigungsoption";
/* Notifications: Send new messages notifications */
-"Notifications.Row.Messages" = "Messages";
+"Notifications.Row.Messages" = "Nachrichten";
/* Notifications: Send new transfers notifications */
-"Notifications.Row.Transfers" = "Transfers";
+"Notifications.Row.Transfers" = "Transaktionen";
/* Notifications: Something went wrong while registering remote notifications. %@ for description */
-"NotificationsService.Error.RegistrationRemotesFormat" = "Registration in ANS failed. Please, try again later. Reason %@";
+"NotificationsService.Error.RegistrationRemotesFormat" = "Registrierung in ANS fehlgeschlagen. Bitte erneut versuchen. Ursache %@";
/* Notifications: New message notification title */
-"NotificationsService.NewMessage.Title" = "New Message";
+"NotificationsService.NewMessage.Title" = "Neue Nachricht";
/* Notifications: New single message notification body */
-"NotificationsService.NewMessage.BodySingle" = "You have new message";
+"NotificationsService.NewMessage.BodySingle" = "Sie haben eine neue Nachricht";
/* Notifications: New transfer transaction title */
-"NotificationsService.NewTransfer.Title" = "New Transfer";
+"NotificationsService.NewTransfer.Title" = "Neue Transaktion";
/* Notifications: New single transfer transaction body */
-"NotificationsService.NewTransfer.BodySingle" = "You have new transfer";
+"NotificationsService.NewTransfer.BodySingle" = "Sie haben eine neue Transaktion";
/* Notifications: User has disabled notifications. Head him into settings */
-"NotificationsService.NotificationsDisabled" = "Notifications disabled. You can enable notifications in Settings";
+"NotificationsService.NotificationsDisabled" = "Benachrichtigungen deaktiviert. Sie können Benachrichtigungen in den Einstellungen aktivieren";
/* Pinpad: Ask user to create new pin */
-"Pinpad.EnterNewPin" = "Enter new PIN code";
+"Pinpad.EnterNewPin" = "Neue PIN eingeben";
/* Pinpad: Ask user to repeat new pin */
-"Pinpad.ReenterPin" = "Re-enter your PIN code";
+"Pinpad.ReenterPin" = "Geben Sie die PIN erneut ein";
/* QRGenerator: Bad Internal generator error message format. Using %@ for error description */
-"QrGeneratorScene.Error.InternalErrorFormat" = "Internal error: %@. Report a bug";
+"QrGeneratorScene.Error.InternalErrorFormat" = "Interner Fehler: %@. Bericht senden";
/* QRGenerator: user typed in invalid passphrase */
-"QrGeneratorScene.Error.InvalidPassphrase" = "Enter a valid passphrase";
+"QrGeneratorScene.Error.InvalidPassphrase" = "Geben Sie eine gültige Passphrase ein";
/* QRGenerator: Passphrase textview placeholder */
"QrGeneratorScene.Passphrase.Placeholder" = "Passphrase";
/* QRGenerator: small 'Tap to save' tooltip under generated QR */
-"QrGeneratorScene.TapToSave" = "Tap to save";
+"QrGeneratorScene.TapToSave" = "Tippen, um zu speichern";
/* QRGenerator: scene title */
-"QrGeneratorScene.Title" = "QR Generator";
+"QrGeneratorScene.Title" = "QR-Generator";
/* Config: turn off 'Stay Logged In' confirmation */
-"SettingsPage.DoNotStayLoggedIn" = "Do not stay logged in";
+"SettingsPage.DoNotStayLoggedIn" = "Nicht eingeloggt bleiben";
/* Config: Authorization reason for turning biometry off */
-"SettingsPage.DoNotUseBiometry" = "Do not use biometry to log in";
+"SettingsPage.DoNotUseBiometry" = "Benutzen Sie keine Biometrie für die Anmeldung";
/* Config: Generate QR with passphrase row */
-"SettingsPage.Row.GenerateQr" = "Generate QR with passphrase";
+"SettingsPage.Row.GenerateQr" = "Einen QR-Code mit der Passphrase generieren";
/* Config: Show notifications */
-"SettingsPage.Row.Notifications" = "Notifications";
+"SettingsPage.Row.Notifications" = "Benachrichtigungen";
/* Config: Stay logged option */
-"SettingsPage.Row.StayLoggedIn" = "Stay logged in";
+"SettingsPage.Row.StayLoggedIn" = "Eingeloggt bleiben";
/* Config: Version row */
"SettingsPage.Row.Version" = "Version";
/* Config: Application Info section */
-"SettingsPage.Section.ApplicationInfo" = "Application";
+"SettingsPage.Section.ApplicationInfo" = "App Info";
/* Config: Settings section */
-"SettingsPage.Section.Settings" = "Settings";
+"SettingsPage.Section.Settings" = "Einstellungen";
/* Config: Utilities section */
-"SettingsPage.Section.Utilities" = "Utilities";
+"SettingsPage.Section.Utilities" = "Werkzeuge";
/* Config: scene title */
-"SettingsPage.Title" = "Settings";
+"SettingsPage.Title" = "Einstellungen";
/* Config: Authorization reason for turning biometry on */
-"SettingsPage.UseBiometry" = "Use biometry";
+"SettingsPage.UseBiometry" = "Biometrie verwenden";
/* Shared alert 'Cancel' button. Used anywhere */
-"Shared.Cancel" = "Cancel";
+"Shared.Cancel" = "Abbrechen";
/* Shared alert notification: message about item copied to pasteboard. */
-"Shared.CopiedToPasteboard" = "Copied to Pasteboard!";
+"Shared.CopiedToPasteboard" = "Kopiert!";
/* Shared alert 'Copy' button. Used anywhere. Used for copy-paste info. */
-"Shared.CopyToPasteboard" = "Copy to Pasteboard";
+"Shared.CopyToPasteboard" = "Kopieren";
/* Shared alert Done message. Used anywhere */
-"Shared.Done" = "Done";
+"Shared.Done" = "Fertig";
/* Shared alert 'Error' title. Used anywhere */
-"Shared.Error" = "Error";
+"Shared.Error" = "Fehler";
/* Shared alert 'Generate QR' button. Used to generate QR codes with addresses and passphrases. Used with sharing and saving, anywhere. */
-"Shared.GenerateQRCode" = "Generate QR code";
+"Shared.GenerateQRCode" = "QR-Code generieren";
/* Shared alert notification: title for no internet connection message. */
-"Shared.NoInternet.Title" = "No internet connection";
+"Shared.NoInternet.Title" = "Keine Internetverbindung";
/* Shared alert notification: body message for no internet connection. */
-"Shared.NoInternet.Body" = "Please check your internet connection and try again";
+"Shared.NoInternet.Body" = "Bitte überprüfen Sie Ihre Internetverbindung und versuchen Sie es erneut";
/* Shared alert 'Ok' button. Used anywhere */
"Shared.Ok" = "Ok";
/* Shared alert 'Save' button. Used anywhere */
-"Shared.Save" = "Save";
+"Shared.Save" = "Speichern";
/* Shared alert 'Save to Photos'. Used with saving images to photolibrary */
-"Shared.SaveToPhotolibrary" = "Save to Photos";
+"Shared.SaveToPhotolibrary" = "Im Fotoalbum speichern";
/* Shared alert 'Settings' button. Used to go to system Settings app, on application settings page. Should be same as Settings application title. */
-"Shared.Settings" = "Settings";
+"Shared.Settings" = "Einstellungen";
/* Shared alert 'Share' button. Used anywhere for presenting standart iOS 'Share' menu. */
-"Shared.Share" = "Share";
+"Shared.Share" = "Teilen";
/* Shared alert 'Retry' button. Used anywhere. */
-"Shared.Retry" = "Retry";
+"Shared.Retry" = "Erneut versuchen";
/* Shared alert 'Delete' button. Used anywhere. */
-"Shared.Delete" = "Delete";
+"Shared.Delete" = "Löschen";
/* ShareQR scene: User had not authorized access to write images to photolibrary */
-"ShareQR.photolibraryNotAuthorized" = "You need to authorize ADAMANT to use your Photo library";
+"ShareQR.photolibraryNotAuthorized" = "Sie müssen ADAMANT für die Benutzung des Fotoalbums autorisieren";
/* Main tab bar: Account page */
-"Tabs.Account" = "Account";
+"Tabs.Account" = "Konto";
/* Main tab bar: Chats page */
"Tabs.Chats" = "Chats";
/* Main tab bar: Settings page */
-"Tabs.Settings" = "Settings";
+"Tabs.Settings" = "Einstellungen";
/* TransactionList: scene title */
-"TransactionListScene.Title" = "Transactions";
+"TransactionListScene.Title" = "Transaktionen";
/* Transaction details: scene title */
"TransactionDetailsScene.Title" = "Details";
/* Transaction details: amount row. */
-"TransactionDetailsScene.Row.Amount" = "Amount";
+"TransactionDetailsScene.Row.Amount" = "Betrag";
/* Transaction details: Block id row. */
"TransactionDetailsScene.Row.Block" = "Block";
/* Transaction details: confirmations row. */
-"TransactionDetailsScene.Row.Confirmations" = "Confirmations";
+"TransactionDetailsScene.Row.Confirmations" = "Bestätigungen";
/* Transaction details: date row. */
-"TransactionDetailsScene.Row.Date" = "Date";
+"TransactionDetailsScene.Row.Date" = "Datum";
/* Transaction details: 'Open transaction in explorer' row. */
-"TransactionDetailsScene.Row.Explorer" = "Open in Explorer";
+"TransactionDetailsScene.Row.Explorer" = "Im Explorer öffnen";
/* Transaction details: fee row. */
-"TransactionDetailsScene.Row.Fee" = "Fee";
+"TransactionDetailsScene.Row.Fee" = "Gebühr";
/* Transaction details: sender row. */
-"TransactionDetailsScene.Row.From" = "From";
+"TransactionDetailsScene.Row.From" = "Von";
/* Transaction details: Id row. */
-"TransactionDetailsScene.Row.Id" = "Id";
+"TransactionDetailsScene.Row.Id" = "ID";
/* Transaction details: recipient row. */
-"TransactionDetailsScene.Row.To" = "To";
+"TransactionDetailsScene.Row.To" = "Zu";
/* Export transaction: 'Share transaction summary' button */
-"TransactionDetailsScene.Share.Summary" = "Summary";
+"TransactionDetailsScene.Share.Summary" = "Gesamt";
/* Export transaction: 'Share transaction URL' button */
"TransactionDetailsScene.Share.URL" = "URL";
/* TransfersProvider: Transaction not found error. %@ for transaction's ID */
-"TransfersProvider.Error.TransactionNotFoundFormat" = "Transaction with id %@ not found";
+"TransfersProvider.Error.TransactionNotFoundFormat" = "Transaktion mit der ID %@ nicht gefunden";
/* Transfer: transfer amount placeholder */
-"TransferScene.Amount.Placeholder" = "to send";
+"TransferScene.Amount.Placeholder" = "senden";
/* Transfer: Address not found error */
-"TransferScene.Error.AddressNotFound" = "Address not found";
+"TransferScene.Error.AddressNotFound" = "Adresse nicht gefunden";
/* Transfer: Address validation error */
-"TransferScene.Error.InvalidAddress" = "Enter a valid address";
+"TransferScene.Error.InvalidAddress" = "Geben Sie eine gültige Adresse ein";
/* Transfer: Amount is hiegher that user's total money notification */
-"TransferScene.Error.NotEnoughtMoney" = "You don't have that tokens";
+"TransferScene.Error.NotEnoughtMoney" = "Sie haben nicht genug Tokens";
/* Transfer: Amount is zero, or even negative notification */
-"TransferScene.Error.TooLittleMoney" = "You should send more tokens";
+"TransferScene.Error.TooLittleMoney" = "Sie müssen mehr Tokens senden";
/* Transfer: recipient address placeholder */
-"TransferScene.Recipient.Placeholder" = "of the recipient";
+"TransferScene.Recipient.Placeholder" = "Empfänger";
/* Transfer: amount of adamant to transfer. */
-"TransferScene.Row.Amount" = "Amount";
+"TransferScene.Row.Amount" = "Betrag";
/* Transfer: logged user balance. */
-"TransferScene.Row.Balance" = "Balance";
+"TransferScene.Row.Balance" = "Kontostand";
/* Transfer: maximum amount to transfer: available account money substracting transfer fee. */
-"TransferScene.Row.MaxToTransfer" = "Max to transfer";
+"TransferScene.Row.MaxToTransfer" = "Maximalbetrag für den Versand verfügbar";
/* Transfer: recipient address */
-"TransferScene.Row.Recipient" = "Address";
+"TransferScene.Row.Recipient" = "Adresse";
/* Transfer: Send button */
-"TransferScene.Row.Send" = "Send";
+"TransferScene.Row.Send" = "Senden";
/* Transfer: total amount of transaction: money to transfer adding fee */
-"TransferScene.Row.Total" = "Total";
+"TransferScene.Row.Total" = "Insgesamt";
/* Transfer: transfer fee */
-"TransferScene.Row.TransactionFee" = "Fee";
+"TransferScene.Row.TransactionFee" = "Gebühr";
/* Transfer: 'Transfer info' section */
-"TransferScene.Section.TransferInfo" = "Transfer Info";
+"TransferScene.Section.TransferInfo" = "Transaktionsinfo";
/* Transfer: 'Your wallet' section */
-"TransferScene.Section.YourWallet" = "Your Wallet";
+"TransferScene.Section.YourWallet" = "Ihre Wallet";
/* Transfer: Confirm transfer alert: Send tokens button */
-"TransferScene.Send" = "Send";
+"TransferScene.Send" = "Senden";
/* Transfer: Confirm transfer %1$@ tokens to %2$@ message. Note two variables: at runtime %1$@ will be amount (with ADM suffix), and %2$@ will be recipient address. You can use address before amount with this so called 'position tokens'. */
-"TransferScene.SendConfirmFormat" = "Send %1$@ to %2$@?";
+"TransferScene.SendConfirmFormat" = "%1$@ zu %2$@? senden";
/* Transfer: Processing message */
-"TransferScene.SendingFundsProgress" = "Sending tokens…";
+"TransferScene.SendingFundsProgress" = "Senden…";
/* Transfer: Tokens transfered successfully message */
-"TransferScene.TransferSuccessMessage" = "Done!";
+"TransferScene.TransferSuccessMessage" = "Fertig!";
From eed58b1581eaa6e21477b3fd7a7b4335c7c07d73 Mon Sep 17 00:00:00 2001
From: Pavel Anokhov
Date: Fri, 22 Jun 2018 19:22:01 +0300
Subject: [PATCH 03/21] ChatType: extended, crashproof
---
Adamant/Models/ChatType.swift | 45 ++++++++++++++++++++++++++++++++---
1 file changed, 42 insertions(+), 3 deletions(-)
diff --git a/Adamant/Models/ChatType.swift b/Adamant/Models/ChatType.swift
index c3999b816..761aa6081 100644
--- a/Adamant/Models/ChatType.swift
+++ b/Adamant/Models/ChatType.swift
@@ -10,7 +10,46 @@ import Foundation
/// - messageExpensive: Old message type, with 0.005 transaction fee
/// - message: new and main message type, with 0.001 transaction fee
-enum ChatType: Int16, Codable {
- case messageOld = 0
- case message = 1
+/// - richMessage: json with additional data
+/// - signal: hidden system message for/from services
+enum ChatType: Codable {
+ case unknown(raw: Int16)
+ case messageOld // 0
+ case message // 1
+ case richMessage // 2
+ case signal // 3
+
+ init(from int16: Int16) {
+ switch int16 {
+ case 0: self = .messageOld
+ case 1: self = .message
+ case 2: self = .richMessage
+ case 3: self = .signal
+
+ default: self = .unknown(raw: int16)
+ }
+ }
+
+ var rawValue: Int16 {
+ switch self {
+ case .messageOld: return 0
+ case .message: return 1
+ case .richMessage: return 2
+ case .signal: return 3
+
+ case .unknown(let raw): return raw
+ }
+ }
+
+ init(from decoder: Decoder) throws {
+ let container = try decoder.singleValueContainer()
+ let type = try container.decode(Int16.self)
+
+ self.init(from: type)
+ }
+
+ func encode(to encoder: Encoder) throws {
+ var container = encoder.singleValueContainer()
+ try container.encode(rawValue)
+ }
}
From 65934ac081b539c1ad74a722092fe12644589211 Mon Sep 17 00:00:00 2001
From: Pavel Anokhov
Date: Fri, 22 Jun 2018 20:01:37 +0300
Subject: [PATCH 04/21] Other transactions types extended to be crashproof
---
Adamant/Models/ChatType.swift | 45 ++++++-----
Adamant/Models/StateType.swift | 35 ++++++++-
Adamant/Models/TransactionType.swift | 76 ++++++++++++++++---
.../AdamantChatsProvider+fakeMessages.swift | 4 +-
.../DataProviders/AdamantChatsProvider.swift | 2 +-
5 files changed, 127 insertions(+), 35 deletions(-)
diff --git a/Adamant/Models/ChatType.swift b/Adamant/Models/ChatType.swift
index 761aa6081..035c38489 100644
--- a/Adamant/Models/ChatType.swift
+++ b/Adamant/Models/ChatType.swift
@@ -12,25 +12,18 @@ import Foundation
/// - message: new and main message type, with 0.001 transaction fee
/// - richMessage: json with additional data
/// - signal: hidden system message for/from services
-enum ChatType: Codable {
- case unknown(raw: Int16)
- case messageOld // 0
- case message // 1
- case richMessage // 2
- case signal // 3
+enum ChatType {
+ case unknown(raw: Int)
+ case messageOld // 0
+ case message // 1
+ case richMessage // 2
+ case signal // 3
- init(from int16: Int16) {
- switch int16 {
- case 0: self = .messageOld
- case 1: self = .message
- case 2: self = .richMessage
- case 3: self = .signal
-
- default: self = .unknown(raw: int16)
- }
+ init(from int: Int) {
+ self = int.toChatType()
}
- var rawValue: Int16 {
+ var rawValue: Int {
switch self {
case .messageOld: return 0
case .message: return 1
@@ -40,12 +33,14 @@ enum ChatType: Codable {
case .unknown(let raw): return raw
}
}
-
+}
+
+extension ChatType: Codable {
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
- let type = try container.decode(Int16.self)
+ let type = try container.decode(Int.self)
- self.init(from: type)
+ self = type.toChatType()
}
func encode(to encoder: Encoder) throws {
@@ -53,3 +48,15 @@ enum ChatType: Codable {
try container.encode(rawValue)
}
}
+
+fileprivate extension Int {
+ func toChatType() -> ChatType {
+ switch self {
+ case 0: return .messageOld
+ case 1: return .message
+ case 2: return .richMessage
+ case 3: return .signal
+ default: return .unknown(raw: self)
+ }
+ }
+}
diff --git a/Adamant/Models/StateType.swift b/Adamant/Models/StateType.swift
index 4b8f6402c..b5c631202 100644
--- a/Adamant/Models/StateType.swift
+++ b/Adamant/Models/StateType.swift
@@ -8,6 +8,37 @@
import Foundation
-enum StateType: Int16, Codable {
- case keyValue = 0
+enum StateType {
+ case unknown(raw: Int)
+ case keyValue // 0
+
+ var rawValue: Int {
+ switch self {
+ case .keyValue: return 0
+ case .unknown(let raw): return raw
+ }
+ }
+}
+
+extension StateType: Codable {
+ init(from decoder: Decoder) throws {
+ let container = try decoder.singleValueContainer()
+ let type = try container.decode(Int.self)
+ self = type.toStateType()
+ }
+
+ func encode(to encoder: Encoder) throws {
+ var container = encoder.singleValueContainer()
+ try container.encode(self.rawValue)
+ }
+}
+
+fileprivate extension Int {
+ func toStateType() -> StateType {
+ switch self {
+ case 0: return .keyValue
+
+ default: return .unknown(raw: self)
+ }
+ }
}
diff --git a/Adamant/Models/TransactionType.swift b/Adamant/Models/TransactionType.swift
index 84e4dddd6..9529f1543 100644
--- a/Adamant/Models/TransactionType.swift
+++ b/Adamant/Models/TransactionType.swift
@@ -8,15 +8,69 @@
import Foundation
-enum TransactionType: Int, Codable {
- case send = 0
- case signature
- case delegate
- case vote
- case multi
- case dapp
- case inTransfer
- case outTransfer
- case chatMessage
- case state
+enum TransactionType {
+ case unknown(raw: Int)
+ case send // 0
+ case signature // 1
+ case delegate // 2
+ case vote // 3
+ case multi // 4
+ case dapp // 5
+ case inTransfer // 6
+ case outTransfer // 7
+ case chatMessage // 8
+ case state // 9
+
+ init(from int: Int) {
+ self = int.toTransactionType()
+ }
+
+ var rawValue: Int {
+ switch self {
+ case .send: return 0
+ case .signature: return 1
+ case .delegate: return 2
+ case .vote: return 3
+ case .multi: return 4
+ case .dapp: return 5
+ case .inTransfer: return 6
+ case .outTransfer: return 7
+ case .chatMessage: return 8
+ case .state: return 9
+
+ case .unknown(let raw): return raw
+ }
+ }
+}
+
+extension TransactionType: Codable {
+ init(from decoder: Decoder) throws {
+ let container = try decoder.singleValueContainer()
+ let type = try container.decode(Int.self)
+ self = type.toTransactionType()
+ }
+
+ func encode(to encoder: Encoder) throws {
+ var container = encoder.singleValueContainer()
+ try container.encode(self.rawValue)
+ }
+}
+
+fileprivate extension Int {
+ func toTransactionType() -> TransactionType {
+ switch self {
+ case 0: return .send
+ case 1: return .signature
+ case 2: return .delegate
+ case 3: return .vote
+ case 4: return .multi
+ case 5: return .dapp
+ case 6: return .inTransfer
+ case 7: return .outTransfer
+ case 8: return .chatMessage
+ case 9: return .state
+
+ default: return .unknown(raw: self)
+ }
+ }
}
diff --git a/Adamant/Services/DataProviders/AdamantChatsProvider+fakeMessages.swift b/Adamant/Services/DataProviders/AdamantChatsProvider+fakeMessages.swift
index 5b00081f4..dd32564da 100644
--- a/Adamant/Services/DataProviders/AdamantChatsProvider+fakeMessages.swift
+++ b/Adamant/Services/DataProviders/AdamantChatsProvider+fakeMessages.swift
@@ -95,7 +95,7 @@ extension AdamantChatsProvider {
transaction.date = date as NSDate
transaction.recipientId = recipient.address
transaction.senderId = loggedAddress
- transaction.type = ChatType.message.rawValue
+ transaction.type = Int16(ChatType.message.rawValue)
transaction.isOutgoing = true
transaction.message = text
transaction.isUnread = false
@@ -131,7 +131,7 @@ extension AdamantChatsProvider {
transaction.date = date as NSDate
transaction.recipientId = loggedAddress
transaction.senderId = sender.address
- transaction.type = ChatType.message.rawValue
+ transaction.type = Int16(ChatType.message.rawValue)
transaction.isOutgoing = false
transaction.message = text
transaction.isUnread = unread
diff --git a/Adamant/Services/DataProviders/AdamantChatsProvider.swift b/Adamant/Services/DataProviders/AdamantChatsProvider.swift
index 6810dacc9..ba8efc55f 100644
--- a/Adamant/Services/DataProviders/AdamantChatsProvider.swift
+++ b/Adamant/Services/DataProviders/AdamantChatsProvider.swift
@@ -333,7 +333,7 @@ extension AdamantChatsProvider {
transaction.date = Date() as NSDate
transaction.recipientId = recipientId
transaction.senderId = senderId
- transaction.type = ChatType.message.rawValue
+ transaction.type = Int16(ChatType.message.rawValue)
transaction.isOutgoing = true
transaction.message = text
From a88a3c30cb3f8b84a422351a6a6a8931ef5c14c8 Mon Sep 17 00:00:00 2001
From: Pavel Anokhov
Date: Sat, 23 Jun 2018 15:57:20 +0300
Subject: [PATCH 05/21] URLScheme.default, URLScheme.defaultPort
NOdeEditorViewController: showing default port as placeholder for port row.
Fixed Scheme saving.
---
.../Assets/l18n/en.lproj/Localizable.strings | 3 --
.../Assets/l18n/ru.lproj/Localizable.strings | 3 --
Adamant/Models/Node.swift | 30 +++++++++++-
.../NodeEditorViewController.swift | 47 +++++++++++++------
4 files changed, 60 insertions(+), 23 deletions(-)
diff --git a/Adamant/Assets/l18n/en.lproj/Localizable.strings b/Adamant/Assets/l18n/en.lproj/Localizable.strings
index c28535da0..8e495614b 100755
--- a/Adamant/Assets/l18n/en.lproj/Localizable.strings
+++ b/Adamant/Assets/l18n/en.lproj/Localizable.strings
@@ -277,9 +277,6 @@
/* NodesEditor: Delete node button */
"NodesEditor.DeleteNodeButton" = "Delete";
-/* NodesEditor: Port row placeholder */
-"NodesEditor.PortRow.Placeholder" = "443";
-
/* NodesEditor: Host row placeholder */
"NodesEditor.HostRow.Placeholder" = "ip or host name";
diff --git a/Adamant/Assets/l18n/ru.lproj/Localizable.strings b/Adamant/Assets/l18n/ru.lproj/Localizable.strings
index b6f95a93a..08b5f2a68 100644
--- a/Adamant/Assets/l18n/ru.lproj/Localizable.strings
+++ b/Adamant/Assets/l18n/ru.lproj/Localizable.strings
@@ -280,9 +280,6 @@
/* NodesEditor: Delete node button */
"NodesEditor.DeleteNodeButton" = "Удалить ноду";
-/* NodesEditor: Port row placeholder */
-"NodesEditor.PortRow.Placeholder" = "443";
-
/* NodesEditor: Host row placeholder */
"NodesEditor.HostRow.Placeholder" = "ip или имя хоста";
diff --git a/Adamant/Models/Node.swift b/Adamant/Models/Node.swift
index 9fe96f0b6..3bf0c767b 100644
--- a/Adamant/Models/Node.swift
+++ b/Adamant/Models/Node.swift
@@ -10,6 +10,15 @@ import Foundation
enum URLScheme: String, Codable {
case http, https
+
+ static let `default`: URLScheme = .https
+
+ var defaultPort: Int {
+ switch self {
+ case .http: return 36666
+ case .https: return 443
+ }
+ }
}
struct Node: Equatable, Codable {
@@ -18,14 +27,31 @@ struct Node: Equatable, Codable {
let port: Int?
func asString() -> String {
- return asURL()?.absoluteString ?? host
+ if let url = asURL(forcePort: scheme != URLScheme.default) {
+ return url.absoluteString
+ } else {
+ return host
+ }
}
+
+ /// Builds URL, using specified port, or default scheme's port, if nil
+ ///
+ /// - Returns: URL, if no errors were thrown
func asURL() -> URL? {
+ return asURL(forcePort: true)
+ }
+
+ private func asURL(forcePort: Bool) -> URL? {
var components = URLComponents()
components.scheme = scheme.rawValue
components.host = host
- components.port = port
+
+ if let port = port {
+ components.port = port
+ } else if forcePort {
+ components.port = port ?? scheme.defaultPort
+ }
return try? components.asURL()
}
diff --git a/Adamant/Stories/NodesEditor/NodeEditorViewController.swift b/Adamant/Stories/NodesEditor/NodeEditorViewController.swift
index 6926ce244..770e7bff2 100644
--- a/Adamant/Stories/NodesEditor/NodeEditorViewController.swift
+++ b/Adamant/Stories/NodesEditor/NodeEditorViewController.swift
@@ -58,9 +58,8 @@ class NodeEditorViewController: FormViewController {
var placeholder: String? {
switch self {
- case .port: return NSLocalizedString("NodesEditor.PortRow.Placeholder", comment: "NodesEditor: Port row placeholder")
case .host: return NSLocalizedString("NodesEditor.HostRow.Placeholder", comment: "NodesEditor: Host row placeholder")
- case .scheme, .testButton, .deleteButton: return nil
+ case .port, .scheme, .testButton, .deleteButton: return nil
}
}
@@ -163,23 +162,37 @@ class NodeEditorViewController: FormViewController {
<<< IntRow() {
$0.title = Rows.port.localized
$0.tag = Rows.port.tag
- $0.placeholder = Rows.port.placeholder
- $0.value = node?.port
+ if let node = node {
+ $0.value = node.port
+ $0.placeholder = String(node.scheme.defaultPort)
+ } else {
+ $0.placeholder = String(URLScheme.default.defaultPort)
+ }
}.onChange({ [weak self] (_) in
self?.testState = .notTested
})
- // Protocol
+ // Scheme
<<< PickerInlineRow() {
$0.title = Rows.scheme.localized
$0.tag = Rows.scheme.tag
- $0.value = node?.scheme ?? URLScheme.https
+ $0.value = node?.scheme ?? URLScheme.default
$0.options = [.https, .http]
}.onExpandInlineRow({ (_, _, inlineRow) in
inlineRow.cell.height = { 100 }
- }).onChange({ [weak self] (_) in
+ }).onChange({ [weak self] row in
self?.testState = .notTested
+
+ if let portRow: IntRow = self?.form.rowBy(tag: Rows.port.tag) {
+ if let scheme = row.value {
+ portRow.placeholder = String(scheme.defaultPort)
+ } else {
+ portRow.placeholder = String(URLScheme.default.defaultPort)
+ }
+
+ portRow.updateCell()
+ }
})
@@ -239,15 +252,19 @@ extension NodeEditorViewController {
}
// Scheme
- if let row = form.rowBy(tag: Rows.scheme.tag), let scheme = row.baseValue as? URLScheme {
- components.scheme = scheme.rawValue
+ let scheme: URLScheme
+ if let row = form.rowBy(tag: Rows.scheme.tag), let value = row.baseValue as? URLScheme {
+ scheme = value
} else {
- components.scheme = "https"
+ scheme = URLScheme.default
}
+ components.scheme = scheme.rawValue
// Port
if let row: IntRow = form.rowBy(tag: Rows.port.tag), let port = row.value {
components.port = port
+ } else {
+ components.port = scheme.defaultPort
}
let url: URL
@@ -298,11 +315,11 @@ extension NodeEditorViewController {
let host = rawUrl.trimmingCharacters(in: .whitespaces)
- let prot: URLScheme
- if let row: PickerRow = form.rowBy(tag: Rows.scheme.tag), let pr = row.value {
- prot = pr
+ let scheme: URLScheme
+ if let row = form.rowBy(tag: Rows.scheme.tag), let value = row.baseValue as? URLScheme {
+ scheme = value
} else {
- prot = .https
+ scheme = URLScheme.default
}
let port: Int?
@@ -312,7 +329,7 @@ extension NodeEditorViewController {
port = nil
}
- let node = Node(scheme: prot, host: host, port: port)
+ let node = Node(scheme: scheme, host: host, port: port)
let result: NodeEditorResult
if self.node != nil, let tag = nodeTag {
From edfb0158f0b3a0a945dd1a5bd366fd4f115598d2 Mon Sep 17 00:00:00 2001
From: Pavel Anokhov
Date: Sat, 23 Jun 2018 15:57:27 +0300
Subject: [PATCH 06/21] Node added
---
Adamant/AppDelegate.swift | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/Adamant/AppDelegate.swift b/Adamant/AppDelegate.swift
index 734246a07..9da26adf1 100644
--- a/Adamant/AppDelegate.swift
+++ b/Adamant/AppDelegate.swift
@@ -38,7 +38,8 @@ struct AdamantResources {
static let nodes: [Node] = [
Node(scheme: .https, host: "endless.adamant.im", port: nil),
Node(scheme: .https, host: "clown.adamant.im", port: nil),
- Node(scheme: .https, host: "lake.adamant.im", port: nil)
+ Node(scheme: .https, host: "lake.adamant.im", port: nil),
+ Node(scheme: .http, host: "80.211.177.181", port: nil)
]
static let iosAppSupportEmail = "ios@adamant.im"
From 1da4394d2aa76bf86d554a0f7b73104bd6f4e05a Mon Sep 17 00:00:00 2001
From: mercuriosilber <35112265+mercuriosilber@users.noreply.github.com>
Date: Sat, 23 Jun 2018 23:06:48 +0200
Subject: [PATCH 07/21] Updated german strings
Must see how it looks in the app but I hope I translated right strings :)
---
Adamant/Assets/l18n/de.lproj/Localizable.stringsdict | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/Adamant/Assets/l18n/de.lproj/Localizable.stringsdict b/Adamant/Assets/l18n/de.lproj/Localizable.stringsdict
index df7859949..ea2e9c282 100644
--- a/Adamant/Assets/l18n/de.lproj/Localizable.stringsdict
+++ b/Adamant/Assets/l18n/de.lproj/Localizable.stringsdict
@@ -5,7 +5,7 @@
NotificationsService.NewMessage.BodyFormat
NSStringLocalizedFormatKey
- You have %#@messages@
+ Sie haben %#@messages@
messages
NSStringFormatSpecTypeKey
@@ -13,15 +13,15 @@
NSStringFormatValueTypeKey
d
one
- %d new message
+ %d neue Nachricht
other
- %d new messages
+ %d neue Nachrichten
NotificationsService.NewTransfer.BodyFormat
NSStringLocalizedFormatKey
- You have %#@transfers@
+ Sie haben %#@transfers@
transfers
NSStringFormatSpecTypeKey
@@ -29,9 +29,9 @@
NSStringFormatValueTypeKey
d
one
- %d new transaction
+ %d neue Transaktion
other
- %d new transactions
+ %d neue Transaktionen
From c870c359705adf6a06daef8e6d96939153890fe3 Mon Sep 17 00:00:00 2001
From: Pavel Anokhov
Date: Sun, 24 Jun 2018 20:55:14 +0300
Subject: [PATCH 08/21] Api: chatType
---
Adamant/ServiceProtocols/ApiService.swift | 2 +-
Adamant/Services/ApiService/AdamantApi+Chats.swift | 6 +++---
.../Services/DataProviders/AdamantChatsProvider.swift | 11 ++++++-----
3 files changed, 10 insertions(+), 9 deletions(-)
diff --git a/Adamant/ServiceProtocols/ApiService.swift b/Adamant/ServiceProtocols/ApiService.swift
index 273c5ff08..b9044d645 100644
--- a/Adamant/ServiceProtocols/ApiService.swift
+++ b/Adamant/ServiceProtocols/ApiService.swift
@@ -109,5 +109,5 @@ protocol ApiService: class {
/// Send text message
/// - completion: Contains processed transactionId, if success, or AdamantError, if fails.
- func sendMessage(senderId: String, recipientId: String, keypair: Keypair, message: String, nonce: String, completion: @escaping (ApiServiceResult) -> Void)
+ func sendMessage(senderId: String, recipientId: String, keypair: Keypair, message: String, type: ChatType, nonce: String, completion: @escaping (ApiServiceResult) -> Void)
}
diff --git a/Adamant/Services/ApiService/AdamantApi+Chats.swift b/Adamant/Services/ApiService/AdamantApi+Chats.swift
index a761782d9..51490616b 100644
--- a/Adamant/Services/ApiService/AdamantApi+Chats.swift
+++ b/Adamant/Services/ApiService/AdamantApi+Chats.swift
@@ -52,7 +52,7 @@ extension AdamantApiService {
}
}
- func sendMessage(senderId: String, recipientId: String, keypair: Keypair, message: String, nonce: String, completion: @escaping (ApiServiceResult) -> Void) {
+ func sendMessage(senderId: String, recipientId: String, keypair: Keypair, message: String, type: ChatType, nonce: String, completion: @escaping (ApiServiceResult) -> Void) {
// MARK: 1. Prepare params
let params: [String : Any] = [
"type": TransactionType.chatMessage.rawValue,
@@ -61,7 +61,7 @@ extension AdamantApiService {
"publicKey": keypair.publicKey,
"message": message,
"own_message": nonce,
- "message_type": ChatType.message.rawValue
+ "message_type": type.rawValue
]
let headers = [
@@ -112,7 +112,7 @@ extension AdamantApiService {
"chat": [
"message": message,
"own_message": nonce,
- "type": ChatType.message.rawValue
+ "type": type.rawValue
]
]
]
diff --git a/Adamant/Services/DataProviders/AdamantChatsProvider.swift b/Adamant/Services/DataProviders/AdamantChatsProvider.swift
index ba8efc55f..9301eb0c1 100644
--- a/Adamant/Services/DataProviders/AdamantChatsProvider.swift
+++ b/Adamant/Services/DataProviders/AdamantChatsProvider.swift
@@ -329,11 +329,12 @@ extension AdamantChatsProvider {
// MARK: 3. Create chat transaction
+ let type = ChatType.message
let transaction = MessageTransaction(entity: MessageTransaction.entity(), insertInto: privateContext)
transaction.date = Date() as NSDate
transaction.recipientId = recipientId
transaction.senderId = senderId
- transaction.type = Int16(ChatType.message.rawValue)
+ transaction.type = Int16(type.rawValue)
transaction.isOutgoing = true
transaction.message = text
@@ -368,7 +369,7 @@ extension AdamantChatsProvider {
// MARK: 6. Send
- sendTransaction(transaction, keypair: keypair, recipientPublicKey: recipientPublicKey) { result in
+ sendTransaction(transaction, type: type, keypair: keypair, recipientPublicKey: recipientPublicKey) { result in
switch result {
case .success:
do {
@@ -436,7 +437,7 @@ extension AdamantChatsProvider {
// MARK: 3. Send
- sendTransaction(transaction, keypair: keypair, recipientPublicKey: recipientPublicKey) { result in
+ sendTransaction(transaction, type: .message, keypair: keypair, recipientPublicKey: recipientPublicKey) { result in
switch result {
case .success:
do {
@@ -488,7 +489,7 @@ extension AdamantChatsProvider {
///
/// If success - update transaction's id and add it to unconfirmed transactions.
/// If fails - set transaction status to .failed
- private func sendTransaction(_ transaction: MessageTransaction, keypair: Keypair, recipientPublicKey: String, completion: @escaping (ChatsProviderResult) -> Void) {
+ private func sendTransaction(_ transaction: MessageTransaction, type: ChatType, keypair: Keypair, recipientPublicKey: String, completion: @escaping (ChatsProviderResult) -> Void) {
// MARK: 0. Prepare
guard let senderId = transaction.senderId,
let recipientId = transaction.recipientId else {
@@ -503,7 +504,7 @@ extension AdamantChatsProvider {
}
// MARK: 2. Send
- apiService.sendMessage(senderId: senderId, recipientId: recipientId, keypair: keypair, message: encodedMessage.message, nonce: encodedMessage.nonce) { result in
+ apiService.sendMessage(senderId: senderId, recipientId: recipientId, keypair: keypair, message: encodedMessage.message, type: type, nonce: encodedMessage.nonce) { result in
switch result {
case .success(let id):
// Update ID with recieved, add to unconfirmed transactions.
From 2119e9233d894b2d596e92c2d3b2e7f61bf82582 Mon Sep 17 00:00:00 2001
From: Pavel Anokhov
Date: Sun, 24 Jun 2018 21:37:27 +0300
Subject: [PATCH 09/21] Sending ANS signal updated.
---
Adamant/AppDelegate.swift | 52 +++++++++++++++++--
.../Assets/l18n/de.lproj/Localizable.strings | 3 ++
.../Assets/l18n/en.lproj/Localizable.strings | 3 ++
.../Assets/l18n/ru.lproj/Localizable.strings | 3 ++
4 files changed, 56 insertions(+), 5 deletions(-)
diff --git a/Adamant/AppDelegate.swift b/Adamant/AppDelegate.swift
index 9da26adf1..fd17b28cf 100644
--- a/Adamant/AppDelegate.swift
+++ b/Adamant/AppDelegate.swift
@@ -19,6 +19,10 @@ extension String.adamantLocalized {
static let chats = NSLocalizedString("Tabs.Chats", comment: "Main tab bar: Chats page")
static let settings = NSLocalizedString("Tabs.Settings", comment: "Main tab bar: Settings page")
}
+
+ struct application {
+ static let deviceTokenSendFailed = NSLocalizedString("Application.deviceTokenErrorFormat", comment: "Application: Failed to send deviceToken to ANS error format. %@ for error description")
+ }
}
extension StoreKey {
@@ -44,6 +48,10 @@ struct AdamantResources {
static let iosAppSupportEmail = "ios@adamant.im"
+ // ANS Contact
+ static let ansAddress = ""
+ static let ansPublicKey = ""
+
private init() {}
}
@@ -209,6 +217,11 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
// MARK: - Remote notifications
extension AppDelegate {
+ private struct RegistrationPayload: Codable {
+ let token: String
+ let provider: String = "ans"
+ }
+
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
guard let address = accountService.account?.address, let keypair = accountService.keypair else {
print("Trying to register with no user logged")
@@ -218,7 +231,7 @@ extension AppDelegate {
let token = deviceToken.map { String(format: "%02.2hhx", $0) }.joined()
- // Checking, if device token had not changed
+ // MARK: 1. Checking, if device token had not changed
guard let securedStore = container.resolve(SecuredStore.self) else {
fatalError("can't get secured store to get device token hash")
}
@@ -231,19 +244,48 @@ extension AppDelegate {
securedStore.set(tokenHash, for: StoreKey.application.deviceTokenHash)
}
- // Storing new token in blockchain
+ // MARK: 2. Preparing message
+ guard let adamantCore = container.resolve(AdamantCore.self) else {
+ fatalError("Can't get AdamantCore to register device token")
+ }
+
+ let payload: String
+ do {
+ let data = try JSONEncoder().encode(RegistrationPayload(token: token))
+ payload = String(data: data, encoding: String.Encoding.utf8)!
+ } catch {
+ dialogService.showError(withMessage: "Failed to prepare ANS signal payload", error: error)
+ return
+ }
+
+ guard let encodedPayload = adamantCore.encodeMessage(payload, recipientPublicKey: AdamantResources.ansPublicKey, privateKey: keypair.privateKey) else {
+ dialogService.showError(withMessage: "Failed to encode ANS signal. Payload: \(payload)", error: nil)
+ return
+ }
+
+ // MARK: 3. Send signal to ANS
guard let apiService = container.resolve(ApiService.self) else {
fatalError("can't get api service to register device token")
}
- apiService.store(key: "deviceToken", value: token, type: StateType.keyValue, sender: address, keypair: keypair) { [weak self] result in
+ apiService.sendMessage(senderId: address, recipientId: AdamantResources.ansAddress, keypair: keypair, message: encodedPayload.message, type: ChatType.signal, nonce: encodedPayload.nonce) { [unowned self] result in
switch result {
case .success:
return
case .failure(let error):
- print("Failed to store device token: \(error)")
- self?.notificationService?.setNotificationsMode(.disabled, completion: nil)
+ self.notificationService?.setNotificationsMode(.disabled, completion: nil)
+
+ switch error {
+ case .networkError, .notLogged:
+ self.dialogService.showWarning(withMessage: String.localizedStringWithFormat(String.adamantLocalized.application.deviceTokenSendFailed, error.localized))
+
+ case .accountNotFound, .serverError:
+ self.dialogService.showError(withMessage: String.localizedStringWithFormat(String.adamantLocalized.application.deviceTokenSendFailed, error.localized), error: error)
+
+ case .internalError(let message, _):
+ self.dialogService.showError(withMessage: String.localizedStringWithFormat(String.adamantLocalized.application.deviceTokenSendFailed, message), error: error)
+ }
}
}
}
diff --git a/Adamant/Assets/l18n/de.lproj/Localizable.strings b/Adamant/Assets/l18n/de.lproj/Localizable.strings
index a30e7c192..b2593cce7 100755
--- a/Adamant/Assets/l18n/de.lproj/Localizable.strings
+++ b/Adamant/Assets/l18n/de.lproj/Localizable.strings
@@ -61,6 +61,9 @@
/* Product name */
"ADAMANT" = "ADAMANT";
+/* Application: Failed to send deviceToken to ANS error format. %@ for error description */
+"Application.deviceTokenErrorFormat" = "Failed to reginser in ANS: %@";
+
/* ApiService: No connection message. Generally bad network. */
"ApiService.Error.NoConnection" = "Keine Internetverbindung";
diff --git a/Adamant/Assets/l18n/en.lproj/Localizable.strings b/Adamant/Assets/l18n/en.lproj/Localizable.strings
index 8e495614b..7e60730d3 100755
--- a/Adamant/Assets/l18n/en.lproj/Localizable.strings
+++ b/Adamant/Assets/l18n/en.lproj/Localizable.strings
@@ -64,6 +64,9 @@
/* Product name */
"ADAMANT" = "ADAMANT";
+/* Application: Failed to send deviceToken to ANS error format. %@ for error description */
+"Application.deviceTokenErrorFormat" = "Failed to reginser in ANS: %@";
+
/* ApiService: No connection message. Generally bad network. */
"ApiService.Error.NoConnection" = "No connection to the Internet";
diff --git a/Adamant/Assets/l18n/ru.lproj/Localizable.strings b/Adamant/Assets/l18n/ru.lproj/Localizable.strings
index 08b5f2a68..062b87222 100644
--- a/Adamant/Assets/l18n/ru.lproj/Localizable.strings
+++ b/Adamant/Assets/l18n/ru.lproj/Localizable.strings
@@ -64,6 +64,9 @@
/* Product name */
"ADAMANT" = "АДАМАНТ";
+/* Application: Failed to send deviceToken to ANS error format. %@ for error description */
+"Application.deviceTokenErrorFormat" = "Ошибка регистрации в ANS: %@";
+
/* ApiService: No connection message. Generally bad network. */
"ApiService.Error.NoConnection" = "Нет соединения с сетью";
From b1c86ba28af147209284b5cbef6de5d160d3e3f6 Mon Sep 17 00:00:00 2001
From: Pavel Anokhov
Date: Sun, 24 Jun 2018 22:41:16 +0300
Subject: [PATCH 10/21] NotificationsViewController: Description is back.
AppDelegate resources: ANS address and public key. Cleanups.
---
Adamant/AppDelegate.swift | 4 +-
.../Assets/l18n/de.lproj/Localizable.strings | 9 ++-
.../Assets/l18n/en.lproj/Localizable.strings | 3 -
.../Assets/l18n/ru.lproj/Localizable.strings | 18 -----
Adamant/Helpers/String+localized.swift | 2 +-
Adamant/ServiceProtocols/ApiService.swift | 4 +-
.../Services/AdamantNotificationService.swift | 16 +----
.../NotificationsViewController.swift | 69 +++++++++++++------
8 files changed, 60 insertions(+), 65 deletions(-)
diff --git a/Adamant/AppDelegate.swift b/Adamant/AppDelegate.swift
index fd17b28cf..b9f7ade87 100644
--- a/Adamant/AppDelegate.swift
+++ b/Adamant/AppDelegate.swift
@@ -49,8 +49,8 @@ struct AdamantResources {
static let iosAppSupportEmail = "ios@adamant.im"
// ANS Contact
- static let ansAddress = ""
- static let ansPublicKey = ""
+ static let ansAddress = "U10629337621822775991"
+ static let ansPublicKey = "188b24bd116a556ac8ba905bbbdaa16e237dfb14269f5a4f9a26be77537d977c"
private init() {}
}
diff --git a/Adamant/Assets/l18n/de.lproj/Localizable.strings b/Adamant/Assets/l18n/de.lproj/Localizable.strings
index b2593cce7..ac6656eb6 100755
--- a/Adamant/Assets/l18n/de.lproj/Localizable.strings
+++ b/Adamant/Assets/l18n/de.lproj/Localizable.strings
@@ -1,3 +1,6 @@
+/* System accounts: ADAMANT Tokens */
+"Accounts.AdamantTokens" = "ADAMANT Tokens";
+
/* AccountsProvider: Address not valid error, %@ for address */
"AccountsProvider.Error.AddressNotValidFormat" = "Ungültige Adresse: %@";
@@ -64,9 +67,6 @@
/* Application: Failed to send deviceToken to ANS error format. %@ for error description */
"Application.deviceTokenErrorFormat" = "Failed to reginser in ANS: %@";
-/* ApiService: No connection message. Generally bad network. */
-"ApiService.Error.NoConnection" = "Keine Internetverbindung";
-
/* Serious internal error: Failed to build endpoint url */
"ApiService.InternalError.EndpointBuildFailed" = "Endpoint Build fehlgeschlagen. Bericht senden";
@@ -247,6 +247,9 @@
/* NodesList: 'Node url' plaseholder */
"NodesList.NodeUrl" = "Node-URL";
+/* Notifications: scene title */
+"Notifications.Title" = "Benachrichtigungen";
+
/* Notifications: Modes description. Markdown supported. */
"Notifications.ModesDescription" = "#### Benachrichtigungsmodi\n\n#### Deaktiviert\nKeine Benachrichtigungen.\n\n#### Hintergrundaktualisierung\nIhr Gerät erhält neue Nachrichteninformationen automatisch. Keine externen Aufrufe. Die Hintergrundaktualisierung wird von iOS verwaltet, die Zeit wird vom Betriebssystem bestimmt und ist von vielen Faktoren wie Akkustand, Netzwerkauslastung, Nutzungsmuster abhängig und kann nicht vorhergesagt werden. Es können 20 Minuten, 6 Stunden, oder ein Tag sein. Sie können die App trotzdem öffnen und nachschauen, ob eine Nachricht angekommen ist.\n\n#### Push\nBenachrichtigungen werden auf Ihr Gerät vom ADAMANT Benachrichtigungsservice gesendet. Sie erhalten eine Benachrichtigung umgehend, nachdem die Nachricht versendet und von der Blockchain bestätigt wurde - mit einer kleinen Verzögerung. Jedoch erfordert dieser Modus, dass der Gerätetoken Ihres Geräts in der Servicedatenbank registriert ist. Gerätetokens sind sicher, und diese Option ist zu empfehlen.\n\nSie können mehr über die Geräteregistrierung auf der ADAMANTs Github-Seite nachlesen.";
diff --git a/Adamant/Assets/l18n/en.lproj/Localizable.strings b/Adamant/Assets/l18n/en.lproj/Localizable.strings
index 7e60730d3..a4ca3fa30 100755
--- a/Adamant/Assets/l18n/en.lproj/Localizable.strings
+++ b/Adamant/Assets/l18n/en.lproj/Localizable.strings
@@ -67,9 +67,6 @@
/* Application: Failed to send deviceToken to ANS error format. %@ for error description */
"Application.deviceTokenErrorFormat" = "Failed to reginser in ANS: %@";
-/* ApiService: No connection message. Generally bad network. */
-"ApiService.Error.NoConnection" = "No connection to the Internet";
-
/* Serious internal error: Failed to build endpoint url */
"ApiService.InternalError.EndpointBuildFailed" = "Endpoint build failed. Report a bug";
diff --git a/Adamant/Assets/l18n/ru.lproj/Localizable.strings b/Adamant/Assets/l18n/ru.lproj/Localizable.strings
index 062b87222..a0dc5bb70 100644
--- a/Adamant/Assets/l18n/ru.lproj/Localizable.strings
+++ b/Adamant/Assets/l18n/ru.lproj/Localizable.strings
@@ -67,9 +67,6 @@
/* Application: Failed to send deviceToken to ANS error format. %@ for error description */
"Application.deviceTokenErrorFormat" = "Ошибка регистрации в ANS: %@";
-/* ApiService: No connection message. Generally bad network. */
-"ApiService.Error.NoConnection" = "Нет соединения с сетью";
-
/* Serious internal error: Failed to build endpoint url */
"ApiService.InternalError.EndpointBuildFailed" = "Ошибка взаимодействия с блокчейном. Сообщите разработчикам";
@@ -340,21 +337,6 @@
/* Notifications: User has disabled notifications. Head him into settings */
"NotificationsService.NotificationsDisabled" = "Уведомления отключены. Вы можете включить их в Настройках";
-/* Notifications: New message notification title */
-"NotificationsService.NewMessage.Title" = "Новое сообщение";
-
-/* Notifications: New single message notification body */
-"NotificationsService.NewMessage.BodySingle" = "Новое сообщение";
-
-/* Notifications: New transfer transaction title */
-"NotificationsService.NewTransfer.Title" = "Новый перевод";
-
-/* Notifications: New single transfer transaction body */
-"NotificationsService.NewTransfer.BodySingle" = "Новый перевод";
-
-/* Notifications: User has disabled notifications. Head him into settings */
-"NotificationsService.NotificationsDisabled" = "Уведомления отключены. Вы можете включить их в Настройках";
-
/* Pinpad: Ask user to create new pin */
"Pinpad.EnterNewPin" = "Введите новый PIN-код";
diff --git a/Adamant/Helpers/String+localized.swift b/Adamant/Helpers/String+localized.swift
index 39e0e6dc9..127129e48 100644
--- a/Adamant/Helpers/String+localized.swift
+++ b/Adamant/Helpers/String+localized.swift
@@ -52,7 +52,7 @@ extension String {
}
static func remoteServerError(message: String) -> String {
- return String.localizedStringWithFormat(NSLocalizedString("Error.RemoteErrorFormat", comment: "Shared error: Remote error format, %@ for message"), message)
+ return String.localizedStringWithFormat(NSLocalizedString("Error.RemoteServerErrorFormat", comment: "Shared error: Remote error format, %@ for message"), message)
}
}
diff --git a/Adamant/ServiceProtocols/ApiService.swift b/Adamant/ServiceProtocols/ApiService.swift
index b9044d645..b8e98eb96 100644
--- a/Adamant/ServiceProtocols/ApiService.swift
+++ b/Adamant/ServiceProtocols/ApiService.swift
@@ -28,8 +28,8 @@ enum ApiServiceError: Error {
case .accountNotFound:
return String.adamantLocalized.sharedErrors.accountNotFound
- case .serverError(error: let error):
- return String.localizedStringWithFormat(NSLocalizedString("ApiService.Error.RemoteServerErrorFormat", comment: "ApiService: Remote server returned an error. Using %@ for error description"), error)
+ case .serverError(let error):
+ return String.adamantLocalized.sharedErrors.remoteServerError(message: error)
case .internalError(let msg, let error):
let message: String
diff --git a/Adamant/Services/AdamantNotificationService.swift b/Adamant/Services/AdamantNotificationService.swift
index 730095f04..4db551250 100644
--- a/Adamant/Services/AdamantNotificationService.swift
+++ b/Adamant/Services/AdamantNotificationService.swift
@@ -89,21 +89,7 @@ extension AdamantNotificationsService {
completion?(.success)
return
- case .backgroundFetch:
- authorizeNotifications { [weak self] (success, error) in
- guard success else {
- completion?(.denied(error: error))
- return
- }
-
- AdamantNotificationsService.configureUIApplicationFor(mode: mode)
- self?.securedStore.set(mode.toRaw(), for: StoreKey.notificationsService.notificationsMode)
- self?.notificationsMode = mode
- NotificationCenter.default.post(name: Notification.Name.AdamantNotificationService.notificationsModeChanged, object: self)
- completion?(.success)
- }
-
- case .push:
+ case .backgroundFetch, .push:
authorizeNotifications { [weak self] (success, error) in
guard success else {
completion?(.denied(error: error))
diff --git a/Adamant/Stories/Settings/NotificationsViewController.swift b/Adamant/Stories/Settings/NotificationsViewController.swift
index e110e09b8..12bfd2b71 100644
--- a/Adamant/Stories/Settings/NotificationsViewController.swift
+++ b/Adamant/Stories/Settings/NotificationsViewController.swift
@@ -111,7 +111,6 @@ class NotificationsViewController: FormViewController {
// MARK: Properties
private static let githubUrl = URL.init(string: "https://github.com/Adamant-im/AdamantNotificationService/blob/master/README.md")
- private var notificationTypesHidden: Bool = true
// MARK: Lifecycle
@@ -121,6 +120,17 @@ class NotificationsViewController: FormViewController {
self.navigationItem.title = String.adamantLocalized.notificationsScene.title
navigationOptions = .Disabled
+ NotificationCenter.default.addObserver(forName: Notification.Name.AdamantNotificationService.notificationsModeChanged, object: nil, queue: OperationQueue.main) { [weak self] _ in
+ guard let mode = self?.notificationsService.notificationsMode else {
+ return
+ }
+
+ if let row: ActionSheetRow = self?.form.rowBy(tag: Rows.notificationsMode.tag) {
+ row.value = mode
+ row.updateCell()
+ }
+ }
+
// MARK: Modes
form +++ Section(Sections.settings.localized) {
$0.tag = Sections.settings.tag
@@ -130,19 +140,51 @@ class NotificationsViewController: FormViewController {
$0.tag = Rows.notificationsMode.tag
$0.title = Rows.notificationsMode.localized
$0.selectorTitle = Rows.notificationsMode.localized
- $0.options = [.disabled, .backgroundFetch]
+ $0.options = [.disabled, .backgroundFetch, .push]
$0.value = notificationsService.notificationsMode
}.onChange({ [weak self] row in
- guard let mode = row.value else {
- return
+ if let mode = row.value {
+ self?.setNotificationMode(mode)
}
-
- self?.setNotificationMode(mode)
}).cellUpdate({ (cell, _) in
cell.accessoryType = .disclosureIndicator
})
+
+
+ // MARK: ANS
+
+ +++ Section(Sections.ans.localized) {
+ $0.tag = Sections.ans.tag
+ }
+
+ <<< TextAreaRow() {
+ $0.textAreaHeight = .dynamic(initialTextViewHeight: 44)
+ $0.tag = Rows.description.tag
+ }.cellUpdate({ (cell, _) in
+ let parser = MarkdownParser(font: UIFont.systemFont(ofSize: UIFont.systemFontSize))
+ cell.textView.attributedText = parser.parse(Rows.description.localized)
+ cell.textView.isSelectable = false
+ cell.textView.isEditable = false
+ })
+
+ <<< LabelRow() {
+ $0.title = Rows.github.localized
+ $0.tag = Rows.github.tag
+ }.cellSetup({ (cell, _) in
+ cell.selectionStyle = .gray
+ cell.accessoryType = .disclosureIndicator
+ }).onCellSelection({ [weak self] (_, row) in
+ guard let url = NotificationsViewController.githubUrl else {
+ return
+ }
+
+ let safari = SFSafariViewController(url: url)
+ safari.preferredControlTintColor = UIColor.adamantPrimary
+ self?.present(safari, animated: true, completion: nil)
+ })
}
+
// MARK: Logic
func setNotificationMode(_ mode: NotificationsMode) {
@@ -150,19 +192,6 @@ class NotificationsViewController: FormViewController {
return
}
- switch mode {
- case .backgroundFetch:
- notificationTypesHidden = false
-
- if let msgs: SwitchRow = form.rowBy(tag: Rows.messages.tag),
- let tgs: SwitchRow = form.rowBy(tag: Rows.transfers.tag) {
- msgs.value = true
- tgs.value = true
- }
-
- default: notificationTypesHidden = true
- }
-
notificationsService.setNotificationsMode(mode) { [weak self] result in
switch result {
case .success:
@@ -174,8 +203,6 @@ class NotificationsViewController: FormViewController {
row.updateCell()
}
- self?.notificationTypesHidden = true
-
if let section = self?.form.sectionBy(tag: Sections.types.tag) {
section.evaluateHidden()
}
From 036fed9712c135f6dba4b3203953145194e9a342 Mon Sep 17 00:00:00 2001
From: Pavel Anokhov
Date: Mon, 25 Jun 2018 00:09:16 +0300
Subject: [PATCH 11/21] Version
---
Adamant/Info.plist | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/Adamant/Info.plist b/Adamant/Info.plist
index 232710525..264fe0c13 100644
--- a/Adamant/Info.plist
+++ b/Adamant/Info.plist
@@ -17,9 +17,9 @@
CFBundlePackageType
APPL
CFBundleShortVersionString
- 0.3.12
+ 0.4
CFBundleVersion
- 32
+ 33
LSRequiresIPhoneOS
NSCameraUsageDescription
From a2bace4af5a236a400783a01586472928af2174a Mon Sep 17 00:00:00 2001
From: Pavel Anokhov
Date: Mon, 25 Jun 2018 12:47:15 +0300
Subject: [PATCH 12/21] typo...
---
Adamant/AppDelegate.swift | 2 +-
Adamant/Info.plist | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/Adamant/AppDelegate.swift b/Adamant/AppDelegate.swift
index b9f7ade87..8b3770962 100644
--- a/Adamant/AppDelegate.swift
+++ b/Adamant/AppDelegate.swift
@@ -219,7 +219,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
extension AppDelegate {
private struct RegistrationPayload: Codable {
let token: String
- let provider: String = "ans"
+ let provider: String = "apns"
}
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
diff --git a/Adamant/Info.plist b/Adamant/Info.plist
index 264fe0c13..00736678f 100644
--- a/Adamant/Info.plist
+++ b/Adamant/Info.plist
@@ -19,7 +19,7 @@
CFBundleShortVersionString
0.4
CFBundleVersion
- 33
+ 34
LSRequiresIPhoneOS
NSCameraUsageDescription
From c47469b1362b71a1c6663717fb351454eeafbb7d Mon Sep 17 00:00:00 2001
From: Pavel Anokhov
Date: Mon, 25 Jun 2018 23:26:52 +0300
Subject: [PATCH 13/21] 'apns-sandbox' provider for Debug builds
---
Adamant/AppDelegate.swift | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/Adamant/AppDelegate.swift b/Adamant/AppDelegate.swift
index 8b3770962..3107eaa7e 100644
--- a/Adamant/AppDelegate.swift
+++ b/Adamant/AppDelegate.swift
@@ -219,7 +219,12 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
extension AppDelegate {
private struct RegistrationPayload: Codable {
let token: String
- let provider: String = "apns"
+
+ #if DEBUG
+ let provider: String = "apns-sandbox"
+ #else
+ let provider: String = "apns"
+ #endif
}
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
From b9bc903c1b402337d15f2007802b37b04044359e Mon Sep 17 00:00:00 2001
From: mercuriosilber <35112265+mercuriosilber@users.noreply.github.com>
Date: Mon, 25 Jun 2018 22:53:37 +0200
Subject: [PATCH 14/21] Updated German translation
(25.06.2018)
---
Adamant/Assets/l18n/de.lproj/Localizable.strings | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/Adamant/Assets/l18n/de.lproj/Localizable.strings b/Adamant/Assets/l18n/de.lproj/Localizable.strings
index a30e7c192..410c0602b 100755
--- a/Adamant/Assets/l18n/de.lproj/Localizable.strings
+++ b/Adamant/Assets/l18n/de.lproj/Localizable.strings
@@ -26,7 +26,7 @@
"AccountTab.Row.Balance" = "Kontostand";
/* Account tab: 'Join the ICO' button */
-"AccountTab.Row.JoinIco" = "Zur ICO";
+"AccountTab.Row.JoinIco" = "Zum ICO";
/* Account tab: 'Logout' button */
"AccountTab.Row.Logout" = "Ausloggen";
@@ -53,7 +53,7 @@
"AccountTab.TransferBlocked.GoToPWA" = "msg.adamant.im";
/* Account tab: Inform user that sending tokens not allowed by Apple until the end of ICO */
-"AccountTab.TransferBlocked.Message" = "Aufgrund von Richtlinien von Apple, Versenden von Tokens ist während des ICO nicht möglich. Zurzeit können Sie Tokens mit der WebApp unter msg.adamant.im versenden";
+"AccountTab.TransferBlocked.Message" = "Aufgrund von Richtlinien von Apple, Versenden von Tokens ist während des ICOs nicht möglich. Zurzeit können Sie Tokens mit der WebApp unter msg.adamant.im versenden";
/* Account tab: 'Transfer not allowed' alert title */
"AccountTab.TransferBlocked.Title" = "Sorry!";
@@ -98,7 +98,7 @@
"ChatsProvider.Validation.MessageTooLong" = "Nachricht ist zu lang";
/* Known contacts: Adamant ICO message. Markdown supported. */
-"Chats.IcoMessage" = "Sie haben die Möglichkeit, **am ICO** von ADAMANT, dem sichersten Messenger überhaupt, teilzunehmen. Mehr dazu auf Adamant.im oder im Whitepaper.\n\nBitte vergewissern Sie sich, dass sie die Passphrase für dieses Konto gemerkt haben — loggen Sie sich aus und wieder ein. Wir empfehlen Ihnen, diese auf einem Blatt Papier aufzuschreiben. Bitte beachten: nur **Sie sind für die Sicherheit der Passphrase zuständig**. Sie kann nicht wiederhergestellt werden. Wenn Dritte an die Passphrase kommen, Ihre Tokens werden gestohlen. Behandeln sie die Passphrase so, als würden in Ihrer Wallet mehrere Millionen Dollar liegen.\n\nUm die ADM Tokens zu kaufen, gehen Sie zur Wallet und klicken Sie Zur ICO oder öffnen Sie Adamant.im/ico/ im Browser. Im ICO-Formular geben Sie Ihre ADAMANT-Adresse ein. Als Nächstes, wählen Sie Kryptowährung, mit der Sie zahlen möchten, und den Betrag. Es wird angezeigt, wie viele ADM Tokens Sie bekommen, inklusive Bonus: 20–30 ETH: +20%, 30–50 ETH: +30%, 50–90 ETH: +40%, 90+ ETH: +50%. Klicken Sie auf ADAMANT Tokens kaufen. Es wird eine Adresse angezeigt, wo Sie Kryptowährung senden sollen. Sobald Ihre Transaktion bestätigt wurde, erhalten Sie Ihre ADM Tokens. Die Zahlung kann **von jeder Wallet oder Börse** getätigt werden. Es ist nicht notwendig, den exakten Betrag inklusive der Transaktionsgebühren zu senden, die Zahlung wird trotzdem bearbeitet.\nNachdem Sie Ihre ADM Tokens erhalten haben, empfehlen wir Ihnen, sie so lange wie möglich zu behalten. Alle während des ICO nicht verkaufte Tokens werden unter den Wallets verteilt, 5% im Monat werden werden hinzugefügt. Zusätzliche Informationen finden Sie auf Adamant.im und im Whitepaper.\n\nBitte nicht auf diese Nachricht antworten, sie wurde maschinell erstellt. Wenn Sie noch Fragen haben, schreiben Sie **U15677078342684640219** an";
+"Chats.IcoMessage" = "Sie haben die Möglichkeit, **am ICO** von ADAMANT, dem sichersten Messenger überhaupt, teilzunehmen. Mehr dazu auf Adamant.im oder im Whitepaper.\n\nBitte vergewissern Sie sich, dass sie die Passphrase für dieses Konto gemerkt haben — loggen Sie sich aus und wieder ein. Wir empfehlen Ihnen, diese auf einem Blatt Papier aufzuschreiben. Bitte beachten: nur **Sie sind für die Sicherheit der Passphrase zuständig**. Sie kann nicht wiederhergestellt werden. Wenn Dritte an die Passphrase kommen, Ihre Tokens werden gestohlen. Behandeln sie die Passphrase so, als würden in Ihrer Wallet mehrere Millionen Dollar liegen.\n\nUm die ADM Tokens zu kaufen, gehen Sie zur Wallet und klicken Sie Zum ICO oder öffnen Sie Adamant.im/ico/ im Browser. Im ICO-Formular geben Sie Ihre ADAMANT-Adresse ein. Als Nächstes, wählen Sie Kryptowährung, mit der Sie zahlen möchten, und den Betrag. Es wird angezeigt, wie viele ADM Tokens Sie bekommen, inklusive Bonus: 20–30 ETH: +20%, 30–50 ETH: +30%, 50–90 ETH: +40%, 90+ ETH: +50%. Klicken Sie auf ADAMANT Tokens kaufen. Es wird eine Adresse angezeigt, wo Sie Kryptowährung senden sollen. Sobald Ihre Transaktion bestätigt wurde, erhalten Sie Ihre ADM Tokens. Die Zahlung kann **von jeder Wallet oder Börse** getätigt werden. Es ist nicht notwendig, den exakten Betrag inklusive der Transaktionsgebühren zu senden, die Zahlung wird trotzdem bearbeitet.\nNachdem Sie Ihre ADM Tokens erhalten haben, empfehlen wir Ihnen, sie so lange wie möglich zu behalten. Alle während des ICOs nicht verkaufte Tokens werden unter den Wallets verteilt, 5% im Monat werden werden hinzugefügt. Zusätzliche Informationen finden Sie auf Adamant.im und im Whitepaper.\n\nBitte nicht auf diese Nachricht antworten, sie wurde maschinell erstellt. Wenn Sie noch Fragen haben, schreiben Sie **U15677078342684640219** an";
/* Known contacts: Adamant pre ICO message */
"Chats.PreIcoMessage" = "You have a possibility to invest in ADAMANT, the most secure and anonymous messenger. Now is a Pre-ICO stage — the most profitable for investors. Learn more on Adamant.im website or in the Whitepaper. To participate just reply to this message and we will assist. We are eager to answer quickly, but sometimes delays for a couple of hours are possible.\nAfter you invest and receive ADM tokens, we recommend to keep them as long as possible. All of unsold tokens during ICO will be distributed among users wallets, adding 5% monthly. Additional info is on Adamant.im website and in the Whitepaper.";
From b55e27ba6f4b3904d25f825fae3ddb3ed92a7b41 Mon Sep 17 00:00:00 2001
From: Anton B
Date: Wed, 27 Jun 2018 16:44:44 +0300
Subject: [PATCH 15/21] Bugfix
Fix NodeEditor checking issue
Fix Error Email issue
---
Adamant/Assets/l18n/en.lproj/Localizable.strings | 3 +++
Adamant/Assets/l18n/ru.lproj/Localizable.strings | 3 +++
Adamant/ServiceProtocols/DialogService.swift | 2 ++
Adamant/Services/AdamantDialogService.swift | 15 +++++++++++----
.../NodesEditor/NodeEditorViewController.swift | 16 ++++++++--------
5 files changed, 27 insertions(+), 12 deletions(-)
diff --git a/Adamant/Assets/l18n/en.lproj/Localizable.strings b/Adamant/Assets/l18n/en.lproj/Localizable.strings
index 8e495614b..a78098f59 100755
--- a/Adamant/Assets/l18n/en.lproj/Localizable.strings
+++ b/Adamant/Assets/l18n/en.lproj/Localizable.strings
@@ -412,6 +412,9 @@
/* Shared alert notification: body message for no internet connection. */
"Shared.NoInternet.Body" = "Please check your internet connection and try again";
+/* Shared alert notification: message for no Mail services. */
+"Shared.NoMail" = "Mail services are not available";
+
/* Shared alert 'Ok' button. Used anywhere */
"Shared.Ok" = "Ok";
diff --git a/Adamant/Assets/l18n/ru.lproj/Localizable.strings b/Adamant/Assets/l18n/ru.lproj/Localizable.strings
index 08b5f2a68..4946553e5 100644
--- a/Adamant/Assets/l18n/ru.lproj/Localizable.strings
+++ b/Adamant/Assets/l18n/ru.lproj/Localizable.strings
@@ -430,6 +430,9 @@
/* Shared alert notification: body message for no internet connection. */
"Shared.NoInternet.Body" = "Проверьте соединение и повторите попытку";
+/* Shared alert notification: message for no Mail services. */
+"Shared.NoMail" = "Оправка почты не доступна";
+
/* Shared alert 'Ok' button. Used anywhere */
"Shared.Ok" = "Ок";
diff --git a/Adamant/ServiceProtocols/DialogService.swift b/Adamant/ServiceProtocols/DialogService.swift
index 679d0ab3e..607bf7ddd 100644
--- a/Adamant/ServiceProtocols/DialogService.swift
+++ b/Adamant/ServiceProtocols/DialogService.swift
@@ -14,6 +14,8 @@ extension String.adamantLocalized.alert {
static let share = NSLocalizedString("Shared.Share", comment: "Shared alert 'Share' button. Used anywhere for presenting standart iOS 'Share' menu.")
static let generateQr = NSLocalizedString("Shared.GenerateQRCode", comment: "Shared alert 'Generate QR' button. Used to generate QR codes with addresses and passphrases. Used with sharing and saving, anywhere.")
static let saveToPhotolibrary = NSLocalizedString("Shared.SaveToPhotolibrary", comment: "Shared alert 'Save to Photos'. Used with saving images to photolibrary")
+
+ static let noMailService = NSLocalizedString("Shared.NoMail", comment: "Shared alert notification: message for no Mail services")
}
enum ShareType {
diff --git a/Adamant/Services/AdamantDialogService.swift b/Adamant/Services/AdamantDialogService.swift
index fdef3033d..910e0bbef 100644
--- a/Adamant/Services/AdamantDialogService.swift
+++ b/Adamant/Services/AdamantDialogService.swift
@@ -115,12 +115,19 @@ extension AdamantDialogService {
let supportBtn = PMAlertAction(title: AdamantResources.iosAppSupportEmail, style: .default) {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in
- guard let presenter = self else {
- return
- }
+ guard let presenter = self else {
+ print("Lost connecting with dialog service")
+ return
+ }
+
+ if !MFMailComposeViewController.canSendMail() {
+ print("Mail services are not available")
+ presenter.showWarning(withMessage: String.adamantLocalized.alert.noMailService)
+ return
+ }
let mailVC = MFMailComposeViewController()
- mailVC.mailComposeDelegate = self?.mailDelegate
+ mailVC.mailComposeDelegate = presenter.mailDelegate
mailVC.setToRecipients([AdamantResources.iosAppSupportEmail])
mailVC.setSubject(String.adamantLocalized.alert.emailErrorMessageTitle)
diff --git a/Adamant/Stories/NodesEditor/NodeEditorViewController.swift b/Adamant/Stories/NodesEditor/NodeEditorViewController.swift
index 770e7bff2..2c3a7d3bb 100644
--- a/Adamant/Stories/NodesEditor/NodeEditorViewController.swift
+++ b/Adamant/Stories/NodesEditor/NodeEditorViewController.swift
@@ -277,16 +277,16 @@ extension NodeEditorViewController {
}
dialogService.showProgress(withMessage: String.adamantLocalized.nodesEditor.testInProgressMessage, userInteractionEnable: false)
- apiService.getNodeVersion(url: url) { [weak self] result in
+ apiService.getNodeVersion(url: url) { result in
switch result {
case .success(_):
- self?.dialogService.dismissProgress()
- self?.testState = .passed
+ self.dialogService.dismissProgress()
+ self.testState = .passed
completion?(true)
case .failure(let error):
- self?.dialogService.showWarning(withMessage: error.localized)
- self?.testState = .failed
+ self.dialogService.showWarning(withMessage: error.localized)
+ self.testState = .failed
completion?(false)
}
}
@@ -295,9 +295,9 @@ extension NodeEditorViewController {
@objc func done() {
switch testState {
case .notTested, .failed:
- testNode { [weak self] success in
+ testNode { success in
if success {
- self?.saveNode()
+ self.saveNode()
}
}
@@ -351,7 +351,7 @@ extension NodeEditorViewController {
let alert = UIAlertController(title: String.adamantLocalized.nodesEditor.deleteNodeAlert, message: nil, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: String.adamantLocalized.alert.cancel, style: .cancel, handler: nil))
alert.addAction(UIAlertAction(title: String.adamantLocalized.alert.delete, style: .destructive, handler: { _ in
- self.didCallDelegate = false
+ self.didCallDelegate = true
if let node = self.node, let tag = self.nodeTag {
self.delegate?.nodeEditorViewController(self, didFinishEditingWithResult: .delete(node: node, tag: tag))
From 53b6101638d2bdbb6afc38f5228bb75a4e53e7a5 Mon Sep 17 00:00:00 2001
From: Anton B
Date: Wed, 27 Jun 2018 17:06:50 +0300
Subject: [PATCH 16/21] Update system Avatar
---
.../avatar_bots.imageset/Contents.json | 2 +-
.../avatar_bots.imageset/avatar_bots.png | Bin 1716 -> 2388 bytes
.../avatar_bots.imageset/avatar_bots@2x.png | Bin 5007 -> 5678 bytes
.../avatar_bots.imageset/avatar_bots@3x.png | Bin 9242 -> 9676 bytes
4 files changed, 1 insertion(+), 1 deletion(-)
diff --git a/Adamant/Assets/Assets.xcassets/avatar_bots.imageset/Contents.json b/Adamant/Assets/Assets.xcassets/avatar_bots.imageset/Contents.json
index 61fc4c409..478dea43f 100644
--- a/Adamant/Assets/Assets.xcassets/avatar_bots.imageset/Contents.json
+++ b/Adamant/Assets/Assets.xcassets/avatar_bots.imageset/Contents.json
@@ -21,6 +21,6 @@
"author" : "xcode"
},
"properties" : {
- "template-rendering-intent" : "template"
+ "template-rendering-intent" : "original"
}
}
\ No newline at end of file
diff --git a/Adamant/Assets/Assets.xcassets/avatar_bots.imageset/avatar_bots.png b/Adamant/Assets/Assets.xcassets/avatar_bots.imageset/avatar_bots.png
index 0c60c766257eecd950ffa9660002aaabd9ae3460..7e17fd660c21342dea80cd6de7e2b5d4aafc3318 100644
GIT binary patch
delta 2377
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
literal 5007
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
LT3)F(#F76WkZ=ch
From b8153c5c1c2344bf60521302b50e2d4be7a6f334 Mon Sep 17 00:00:00 2001
From: Anton B
Date: Thu, 28 Jun 2018 11:53:21 +0300
Subject: [PATCH 17/21] Test nodes before use
---
Adamant/ServiceProtocols/NodesSource.swift | 1 +
Adamant/Services/AdamantNodesSource.swift | 61 ++++++++++++++++++++++
Adamant/SwinjectDependencies.swift | 13 +++--
3 files changed, 70 insertions(+), 5 deletions(-)
diff --git a/Adamant/ServiceProtocols/NodesSource.swift b/Adamant/ServiceProtocols/NodesSource.swift
index f23198fce..cbdfd9741 100644
--- a/Adamant/ServiceProtocols/NodesSource.swift
+++ b/Adamant/ServiceProtocols/NodesSource.swift
@@ -41,6 +41,7 @@ protocol NodesSource {
var defaultNodes: [Node] { get }
func getNewNode() -> Node
+ func getValidNode(completion: @escaping ((Node?) -> Void))
func saveNodes()
func reloadNodes()
diff --git a/Adamant/Services/AdamantNodesSource.swift b/Adamant/Services/AdamantNodesSource.swift
index 93e568a9d..71d75a5e8 100644
--- a/Adamant/Services/AdamantNodesSource.swift
+++ b/Adamant/Services/AdamantNodesSource.swift
@@ -10,6 +10,7 @@ import Foundation
class AdamantNodesSource: NodesSource {
// MARK: - Dependencies
+ var apiService: ApiService!
var securedStore: SecuredStore! {
didSet {
reloadNodes()
@@ -22,6 +23,7 @@ class AdamantNodesSource: NodesSource {
didSet {
if nodes.count == 0 {
nodes = defaultNodes
+ currentNodes = nodes
}
NotificationCenter.default.post(name: Notification.Name.NodesSource.nodesChanged, object: self, userInfo: [AdamantUserInfoKey.nodesSource.nodes: nodes])
@@ -29,6 +31,8 @@ class AdamantNodesSource: NodesSource {
}
var defaultNodes: [Node]
+
+ private var currentNodes: [Node] = [Node]()
// MARK: - Ctor
@@ -36,6 +40,7 @@ class AdamantNodesSource: NodesSource {
init(defaultNodes: [Node]) {
self.defaultNodes = defaultNodes
self.nodes = defaultNodes
+ self.currentNodes = defaultNodes
}
@@ -45,6 +50,26 @@ class AdamantNodesSource: NodesSource {
let index = Int(arc4random_uniform(UInt32(nodes.count)))
return nodes[index]
}
+
+ func getValidNode(completion: @escaping ((Node?) -> Void)) {
+ if let node = currentNodes.first {
+ testNode(node: node) { (result) in
+ switch result {
+ case .passed:
+ completion(node)
+ break
+ case .failed, .notTested:
+ if let index = self.currentNodes.index(of: node) {
+ self.currentNodes.remove(at: index)
+ }
+ self.getValidNode(completion: completion)
+ break
+ }
+ }
+ } else {
+ completion(nil)
+ }
+ }
// MARK: - Tools
func saveNodes() {
@@ -74,4 +99,40 @@ class AdamantNodesSource: NodesSource {
print(error.localizedDescription)
}
}
+
+ private func testNode(node: Node, completion: @escaping ((NodeEditorViewController.TestState) -> Void)) {
+ var components = URLComponents()
+
+ components.host = node.host
+ components.scheme = node.scheme.rawValue
+
+ var testState: NodeEditorViewController.TestState = .notTested
+
+ if let port = node.port {
+ components.port = port
+ } else {
+ components.port = node.scheme.defaultPort
+ }
+
+ let url: URL
+ do {
+ url = try components.asURL()
+ } catch {
+ testState = .failed
+ completion(testState)
+ return
+ }
+
+ self.apiService.getNodeVersion(url: url) { result in
+ switch result {
+ case .success(_):
+ testState = .passed
+
+ case .failure(let error):
+ print(error)
+ testState = .failed
+ }
+ completion(testState)
+ }
+ }
}
diff --git a/Adamant/SwinjectDependencies.swift b/Adamant/SwinjectDependencies.swift
index fbfc0764c..0ec5ddc08 100644
--- a/Adamant/SwinjectDependencies.swift
+++ b/Adamant/SwinjectDependencies.swift
@@ -63,17 +63,20 @@ extension Container {
// MARK: NodesSource
self.register(NodesSource.self) { r in
let service = AdamantNodesSource(defaultNodes: AdamantResources.nodes)
+ service.apiService = r.resolve(ApiService.self)!
service.securedStore = r.resolve(SecuredStore.self)
return service
- }.inObjectScope(.container)
+ }.inObjectScope(.container)
// MARK: ApiService
- self.register(ApiService.self) { r in
+ self.register(ApiService.self) { r in
let service = AdamantApiService()
- service.adamantCore = r.resolve(AdamantCore.self)
- service.nodesSource = r.resolve(NodesSource.self)
+ service.adamantCore = r.resolve(AdamantCore.self)
return service
- }.inObjectScope(.container)
+ }.initCompleted { (r, c) in // Weak reference
+ let service = c as! AdamantApiService
+ service.nodesSource = r.resolve(NodesSource.self)
+ }.inObjectScope(.container)
// MARK: AccountService
self.register(AccountService.self) { r in
From de6a2336038b9200c247cb58fb6876ce10d8ea42 Mon Sep 17 00:00:00 2001
From: Pavel Anokhov
Date: Thu, 28 Jun 2018 13:36:57 +0300
Subject: [PATCH 18/21] Fixed saving nodes after reset to default.
---
Adamant/Stories/NodesEditor/NodesListViewController.swift | 1 +
1 file changed, 1 insertion(+)
diff --git a/Adamant/Stories/NodesEditor/NodesListViewController.swift b/Adamant/Stories/NodesEditor/NodesListViewController.swift
index 518fef61e..8cbfe8664 100644
--- a/Adamant/Stories/NodesEditor/NodesListViewController.swift
+++ b/Adamant/Stories/NodesEditor/NodesListViewController.swift
@@ -208,6 +208,7 @@ extension NodesListViewController {
}
self?.setNodes(nodes: nodes)
+ self?.nodesSource.saveNodes()
}))
present(alert, animated: true, completion: nil)
From 011737ab96fc41d143e23f4ab5e26135df3d15b2 Mon Sep 17 00:00:00 2001
From: Pavel Anokhov
Date: Thu, 28 Jun 2018 14:20:33 +0300
Subject: [PATCH 19/21] Hidden system chats.
---
.../ChatModels.xcdatamodel/contents | 5 +++--
Adamant/CoreData/Chatroom+CoreDataProperties.swift | 5 +++--
.../ServiceProtocols/DataProviders/AccountsProvider.swift | 7 +++++++
.../Services/DataProviders/AdamantAccountsProvider.swift | 3 +++
Adamant/Services/DataProviders/AdamantChatsProvider.swift | 2 +-
5 files changed, 17 insertions(+), 5 deletions(-)
diff --git a/Adamant/CoreData/ChatModels.xcdatamodeld/ChatModels.xcdatamodel/contents b/Adamant/CoreData/ChatModels.xcdatamodeld/ChatModels.xcdatamodel/contents
index aaf1cd775..b0166528a 100644
--- a/Adamant/CoreData/ChatModels.xcdatamodeld/ChatModels.xcdatamodel/contents
+++ b/Adamant/CoreData/ChatModels.xcdatamodeld/ChatModels.xcdatamodel/contents
@@ -1,5 +1,5 @@
-
+
@@ -15,6 +15,7 @@
+
@@ -48,7 +49,7 @@
-
+
diff --git a/Adamant/CoreData/Chatroom+CoreDataProperties.swift b/Adamant/CoreData/Chatroom+CoreDataProperties.swift
index 07fb455b6..f5c7ab8f8 100644
--- a/Adamant/CoreData/Chatroom+CoreDataProperties.swift
+++ b/Adamant/CoreData/Chatroom+CoreDataProperties.swift
@@ -2,7 +2,7 @@
// Chatroom+CoreDataProperties.swift
// Adamant
//
-// Created by Anokhov Pavel on 02.06.2018.
+// Created by Anokhov Pavel on 28.06.2018.
// Copyright © 2018 Adamant. All rights reserved.
//
//
@@ -18,9 +18,10 @@ extension Chatroom {
}
@NSManaged public var hasUnreadMessages: Bool
+ @NSManaged public var isReadonly: Bool
@NSManaged public var title: String?
@NSManaged public var updatedAt: NSDate?
- @NSManaged public var isReadonly: Bool
+ @NSManaged public var isHidden: Bool
@NSManaged public var lastTransaction: ChatTransaction?
@NSManaged public var partner: CoreDataAccount?
@NSManaged public var transactions: NSSet?
diff --git a/Adamant/ServiceProtocols/DataProviders/AccountsProvider.swift b/Adamant/ServiceProtocols/DataProviders/AccountsProvider.swift
index 9b4f5ce05..b2ee65fa0 100644
--- a/Adamant/ServiceProtocols/DataProviders/AccountsProvider.swift
+++ b/Adamant/ServiceProtocols/DataProviders/AccountsProvider.swift
@@ -98,6 +98,13 @@ enum AdamantContacts {
return true
}
+ var isHidden: Bool {
+ switch self {
+ case .adamantBountyWallet: return true
+ case .adamantIco: return false
+ }
+ }
+
var avatar: String {
return "avatar_bots"
}
diff --git a/Adamant/Services/DataProviders/AdamantAccountsProvider.swift b/Adamant/Services/DataProviders/AdamantAccountsProvider.swift
index 5a4d50330..f9c3af1d2 100644
--- a/Adamant/Services/DataProviders/AdamantAccountsProvider.swift
+++ b/Adamant/Services/DataProviders/AdamantAccountsProvider.swift
@@ -17,12 +17,14 @@ class AdamantAccountsProvider: AccountsProvider {
let name: String
let avatar: String?
let isReadonly: Bool
+ let isHidden: Bool
fileprivate init(contact: AdamantContacts) {
self.address = contact.address
self.name = contact.name
self.avatar = contact.avatar
self.isReadonly = contact.isReadonly
+ self.isHidden = contact.isHidden
}
}
@@ -289,6 +291,7 @@ extension AdamantAccountsProvider {
coreAccount.avatar = acc.avatar
coreAccount.isSystem = true
chatroom.isReadonly = acc.isReadonly
+ chatroom.isHidden = acc.isHidden
chatroom.title = acc.name
}
diff --git a/Adamant/Services/DataProviders/AdamantChatsProvider.swift b/Adamant/Services/DataProviders/AdamantChatsProvider.swift
index 9301eb0c1..3f6a4152c 100644
--- a/Adamant/Services/DataProviders/AdamantChatsProvider.swift
+++ b/Adamant/Services/DataProviders/AdamantChatsProvider.swift
@@ -553,7 +553,7 @@ extension AdamantChatsProvider {
let request: NSFetchRequest = NSFetchRequest(entityName: Chatroom.entityName)
request.sortDescriptors = [NSSortDescriptor(key: "updatedAt", ascending: false),
NSSortDescriptor(key: "title", ascending: true)]
- request.predicate = NSPredicate(format: "partner!=nil")
+ request.predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [NSPredicate(format: "partner!=nil"), NSPredicate(format: "isHidden = false")])
let controller = NSFetchedResultsController(fetchRequest: request, managedObjectContext: stack.container.viewContext, sectionNameKeyPath: nil, cacheName: nil)
return controller
From d0b66dd33bd3ad2b61feab9a09a7aafb1921182a Mon Sep 17 00:00:00 2001
From: Pavel Anokhov
Date: Thu, 28 Jun 2018 14:41:14 +0300
Subject: [PATCH 20/21] Filtering out hidden chatrooms.
---
.../Services/DataProviders/AdamantChatsProvider.swift | 9 +++++++--
Adamant/Stories/Chats/ChatListViewController.swift | 2 +-
2 files changed, 8 insertions(+), 3 deletions(-)
diff --git a/Adamant/Services/DataProviders/AdamantChatsProvider.swift b/Adamant/Services/DataProviders/AdamantChatsProvider.swift
index 3f6a4152c..7072e5f4e 100644
--- a/Adamant/Services/DataProviders/AdamantChatsProvider.swift
+++ b/Adamant/Services/DataProviders/AdamantChatsProvider.swift
@@ -553,7 +553,9 @@ extension AdamantChatsProvider {
let request: NSFetchRequest = NSFetchRequest(entityName: Chatroom.entityName)
request.sortDescriptors = [NSSortDescriptor(key: "updatedAt", ascending: false),
NSSortDescriptor(key: "title", ascending: true)]
- request.predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [NSPredicate(format: "partner!=nil"), NSPredicate(format: "isHidden = false")])
+ request.predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [
+ NSPredicate(format: "partner!=nil"),
+ NSPredicate(format: "isHidden = false")])
let controller = NSFetchedResultsController(fetchRequest: request, managedObjectContext: stack.container.viewContext, sectionNameKeyPath: nil, cacheName: nil)
return controller
@@ -575,7 +577,10 @@ extension AdamantChatsProvider {
func getUnreadMessagesController() -> NSFetchedResultsController {
let request = NSFetchRequest(entityName: "ChatTransaction")
- request.predicate = NSPredicate(format: "isUnread == true")
+ request.predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [
+ NSPredicate(format: "isUnread == true"),
+ NSPredicate(format: "chatroom.isHidden == false")])
+
request.sortDescriptors = [NSSortDescriptor.init(key: "date", ascending: false),
NSSortDescriptor(key: "transactionId", ascending: false)]
diff --git a/Adamant/Stories/Chats/ChatListViewController.swift b/Adamant/Stories/Chats/ChatListViewController.swift
index 12a993852..b88b7a938 100644
--- a/Adamant/Stories/Chats/ChatListViewController.swift
+++ b/Adamant/Stories/Chats/ChatListViewController.swift
@@ -417,7 +417,7 @@ extension ChatListViewController {
private func showNotification(for transaction: ChatTransaction) {
// MARK: 1. Show notification only for incomming transactions
guard !transaction.silentNotification, !transaction.isOutgoing,
- let chatroom = transaction.chatroom, chatroom != presentedChatroom(),
+ let chatroom = transaction.chatroom, chatroom != presentedChatroom(), !chatroom.isHidden,
let partner = chatroom.partner else {
return
}
From e99ba87815865287485e896ce12d221464536748 Mon Sep 17 00:00:00 2001
From: Pavel Anokhov
Date: Thu, 28 Jun 2018 14:41:54 +0300
Subject: [PATCH 21/21] Version
---
Adamant/Info.plist | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Adamant/Info.plist b/Adamant/Info.plist
index 00736678f..a8b9401de 100644
--- a/Adamant/Info.plist
+++ b/Adamant/Info.plist
@@ -19,7 +19,7 @@
CFBundleShortVersionString
0.4
CFBundleVersion
- 34
+ 35
LSRequiresIPhoneOS
NSCameraUsageDescription