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