diff --git a/Adamant/AppDelegate.swift b/Adamant/AppDelegate.swift
index 734246a07..3107eaa7e 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 {
@@ -38,11 +42,16 @@ 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"
+ // ANS Contact
+ static let ansAddress = "U10629337621822775991"
+ static let ansPublicKey = "188b24bd116a556ac8ba905bbbdaa16e237dfb14269f5a4f9a26be77537d977c"
+
private init() {}
}
@@ -208,6 +217,16 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
// MARK: - Remote notifications
extension AppDelegate {
+ private struct RegistrationPayload: Codable {
+ let token: String
+
+ #if DEBUG
+ let provider: String = "apns-sandbox"
+ #else
+ let provider: String = "apns"
+ #endif
+ }
+
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")
@@ -217,7 +236,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")
}
@@ -230,19 +249,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/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 0c60c7662..7e17fd660 100644
Binary files a/Adamant/Assets/Assets.xcassets/avatar_bots.imageset/avatar_bots.png and b/Adamant/Assets/Assets.xcassets/avatar_bots.imageset/avatar_bots.png differ
diff --git a/Adamant/Assets/Assets.xcassets/avatar_bots.imageset/avatar_bots@2x.png b/Adamant/Assets/Assets.xcassets/avatar_bots.imageset/avatar_bots@2x.png
index 04afc0e26..0d7f673ba 100644
Binary files a/Adamant/Assets/Assets.xcassets/avatar_bots.imageset/avatar_bots@2x.png and b/Adamant/Assets/Assets.xcassets/avatar_bots.imageset/avatar_bots@2x.png differ
diff --git a/Adamant/Assets/Assets.xcassets/avatar_bots.imageset/avatar_bots@3x.png b/Adamant/Assets/Assets.xcassets/avatar_bots.imageset/avatar_bots@3x.png
index 629a9f524..ca77c4dbb 100644
Binary files a/Adamant/Assets/Assets.xcassets/avatar_bots.imageset/avatar_bots@3x.png and b/Adamant/Assets/Assets.xcassets/avatar_bots.imageset/avatar_bots@3x.png differ
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";
diff --git a/Adamant/Assets/l18n/de.lproj/Localizable.strings b/Adamant/Assets/l18n/de.lproj/Localizable.strings
index 25b5e111a..f87511432 100755
--- a/Adamant/Assets/l18n/de.lproj/Localizable.strings
+++ b/Adamant/Assets/l18n/de.lproj/Localizable.strings
@@ -1,20 +1,23 @@
+/* System accounts: ADAMANT Tokens */
+"Accounts.AdamantTokens" = "ADAMANT Tokens";
+
/* 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 +26,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" = "Zum 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 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!";
@@ -61,447 +64,450 @@
/* Product name */
"ADAMANT" = "ADAMANT";
-/* ApiService: No connection message. Generally bad network. */
-"ApiService.Error.NoConnection" = "No connection to the Internet";
+/* Application: Failed to send deviceToken to ANS error format. %@ for error description */
+"Application.deviceTokenErrorFormat" = "Failed to reginser in ANS: %@";
/* 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 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.";
/* 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: scene title */
+"Notifications.Title" = "Benachrichtigungen";
/* 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!";
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
diff --git a/Adamant/Assets/l18n/en.lproj/Localizable.strings b/Adamant/Assets/l18n/en.lproj/Localizable.strings
index c28535da0..75a732d80 100755
--- a/Adamant/Assets/l18n/en.lproj/Localizable.strings
+++ b/Adamant/Assets/l18n/en.lproj/Localizable.strings
@@ -64,8 +64,8 @@
/* Product name */
"ADAMANT" = "ADAMANT";
-/* ApiService: No connection message. Generally bad network. */
-"ApiService.Error.NoConnection" = "No connection to the Internet";
+/* Application: Failed to send deviceToken to ANS error format. %@ for error description */
+"Application.deviceTokenErrorFormat" = "Failed to reginser in ANS: %@";
/* Serious internal error: Failed to build endpoint url */
"ApiService.InternalError.EndpointBuildFailed" = "Endpoint build failed. Report a bug";
@@ -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";
@@ -415,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 b6f95a93a..718b8930d 100644
--- a/Adamant/Assets/l18n/ru.lproj/Localizable.strings
+++ b/Adamant/Assets/l18n/ru.lproj/Localizable.strings
@@ -64,8 +64,8 @@
/* Product name */
"ADAMANT" = "АДАМАНТ";
-/* ApiService: No connection message. Generally bad network. */
-"ApiService.Error.NoConnection" = "Нет соединения с сетью";
+/* Application: Failed to send deviceToken to ANS error format. %@ for error description */
+"Application.deviceTokenErrorFormat" = "Ошибка регистрации в ANS: %@";
/* Serious internal error: Failed to build endpoint url */
"ApiService.InternalError.EndpointBuildFailed" = "Ошибка взаимодействия с блокчейном. Сообщите разработчикам";
@@ -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 или имя хоста";
@@ -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-код";
@@ -433,6 +415,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/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/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/Info.plist b/Adamant/Info.plist
index 232710525..a8b9401de 100644
--- a/Adamant/Info.plist
+++ b/Adamant/Info.plist
@@ -17,9 +17,9 @@
CFBundlePackageType
APPL
CFBundleShortVersionString
- 0.3.12
+ 0.4
CFBundleVersion
- 32
+ 35
LSRequiresIPhoneOS
NSCameraUsageDescription
diff --git a/Adamant/Models/ChatType.swift b/Adamant/Models/ChatType.swift
index c3999b816..035c38489 100644
--- a/Adamant/Models/ChatType.swift
+++ b/Adamant/Models/ChatType.swift
@@ -10,7 +10,53 @@ 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 {
+ case unknown(raw: Int)
+ case messageOld // 0
+ case message // 1
+ case richMessage // 2
+ case signal // 3
+
+ init(from int: Int) {
+ self = int.toChatType()
+ }
+
+ var rawValue: Int {
+ switch self {
+ case .messageOld: return 0
+ case .message: return 1
+ case .richMessage: return 2
+ case .signal: return 3
+
+ case .unknown(let raw): return raw
+ }
+ }
+}
+
+extension ChatType: Codable {
+ init(from decoder: Decoder) throws {
+ let container = try decoder.singleValueContainer()
+ let type = try container.decode(Int.self)
+
+ self = type.toChatType()
+ }
+
+ func encode(to encoder: Encoder) throws {
+ var container = encoder.singleValueContainer()
+ 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/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/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/ServiceProtocols/ApiService.swift b/Adamant/ServiceProtocols/ApiService.swift
index 273c5ff08..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
@@ -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/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/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/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/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/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/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/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/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+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..7072e5f4e 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 = 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.
@@ -552,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 = 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
@@ -574,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
}
diff --git a/Adamant/Stories/NodesEditor/NodeEditorViewController.swift b/Adamant/Stories/NodesEditor/NodeEditorViewController.swift
index 6926ce244..2c3a7d3bb 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
@@ -260,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)
}
}
@@ -278,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()
}
}
@@ -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 {
@@ -334,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))
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)
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()
}
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