From c86bfc02c7b7368849907b5cf9399743ee5c69c2 Mon Sep 17 00:00:00 2001 From: Pavel Anokhov Date: Fri, 6 Apr 2018 17:00:11 +0300 Subject: [PATCH 01/34] enabling push --- Adamant.xcodeproj/project.pbxproj | 113 +++--------------------------- Adamant/Adamant.entitlements | 8 +++ 2 files changed, 17 insertions(+), 104 deletions(-) create mode 100644 Adamant/Adamant.entitlements diff --git a/Adamant.xcodeproj/project.pbxproj b/Adamant.xcodeproj/project.pbxproj index 18cc7a4e8..6e0776bd5 100644 --- a/Adamant.xcodeproj/project.pbxproj +++ b/Adamant.xcodeproj/project.pbxproj @@ -180,6 +180,7 @@ E905D39C204C13B900DDB504 /* SecuredStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecuredStore.swift; sourceTree = ""; }; E905D39E204C281400DDB504 /* LoginViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginViewController.swift; sourceTree = ""; }; E9061B96207501E40011F104 /* AdamantUserInfoKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdamantUserInfoKey.swift; sourceTree = ""; }; + E9061B982077AF8E0011F104 /* Adamant.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Adamant.entitlements; sourceTree = ""; }; E90A4942204C5ED6009F6A65 /* EurekaPassphraseRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EurekaPassphraseRow.swift; sourceTree = ""; }; E90A4944204C5F60009F6A65 /* PassphraseCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = PassphraseCell.xib; sourceTree = ""; }; E90A494A204D9EB8009F6A65 /* AdamantAuthentication.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdamantAuthentication.swift; sourceTree = ""; }; @@ -409,6 +410,7 @@ E913C8F11FFFA51D001A83F7 /* AppDelegate.swift */, E9E7CD9020026FA100DFC4DB /* SwinjectDependencies.swift */, E913C8FD1FFFA51E001A83F7 /* Info.plist */, + E9061B982077AF8E0011F104 /* Adamant.entitlements */, ); path = Adamant; sourceTree = ""; @@ -793,6 +795,9 @@ com.apple.BackgroundModes = { enabled = 1; }; + com.apple.Push = { + enabled = 1; + }; }; }; E9EC344320066D4A00C0E546 = { @@ -1240,6 +1245,7 @@ baseConfigurationReference = 871009461457F6B7B47D99C5 /* Pods-Adamant.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_ENTITLEMENTS = Adamant/Adamant.entitlements; CODE_SIGN_STYLE = Manual; DEVELOPMENT_TEAM = J2L77FMN46; DISPLAY_NAME = ADM.Dev; @@ -1248,7 +1254,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "im.adamant.adamant-messenger-dev"; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE = "37a2cc10-f98c-431d-b00f-e929c1e336fc"; + PROVISIONING_PROFILE = "e4233bbf-3705-44fe-95b0-e77475672c60"; PROVISIONING_PROFILE_SPECIFIER = "ADAMANT Debug Dev"; SWIFT_VERSION = 4.0; TARGETED_DEVICE_FAMILY = 1; @@ -1260,6 +1266,7 @@ baseConfigurationReference = D283570A539D864115735AAA /* Pods-Adamant.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_ENTITLEMENTS = Adamant/Adamant.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Manual; @@ -1270,112 +1277,13 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "im.adamant.adamant-messenger"; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE = "65b9c88e-381e-45f4-83fb-10cb9d8b343d"; + PROVISIONING_PROFILE = "bedd1b75-2f23-4a85-a0b2-14c424fcff42"; PROVISIONING_PROFILE_SPECIFIER = "ADAMANT Messenger Dev"; SWIFT_VERSION = 4.0; TARGETED_DEVICE_FAMILY = 1; }; name = Release; }; - E9722062201E654D004F2AAD /* Testing */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - CODE_SIGN_IDENTITY = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu11; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 10.0; - MTL_ENABLE_DEBUG_INFO = YES; - ONLY_ACTIVE_ARCH = YES; - PRODUCT_NAME = ADAMANT; - SDKROOT = iphoneos; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - }; - name = Testing; - }; - E9722063201E654D004F2AAD /* Testing */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 903AC078A8A55175A05E42D4 /* Pods-Adamant.testing.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CODE_SIGN_STYLE = Manual; - DEVELOPMENT_TEAM = J2L77FMN46; - DISPLAY_NAME = Adamant; - INFOPLIST_FILE = Adamant/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 10.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = "im.adamant.adamant-messenger-qa"; - PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE = "79394ceb-6016-46b9-8b60-1927d7bd227a"; - PROVISIONING_PROFILE_SPECIFIER = "ADAMANT QA Dev"; - SWIFT_VERSION = 4.0; - TARGETED_DEVICE_FAMILY = 1; - }; - name = Testing; - }; - E9722064201E654D004F2AAD /* Testing */ = { - isa = XCBuildConfiguration; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = J2L77FMN46; - INFOPLIST_FILE = AdamantTests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = im.adamant.AdamantTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; - SWIFT_VERSION = 4.0; - TARGETED_DEVICE_FAMILY = "1,2"; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Adamant.app/Adamant"; - }; - name = Testing; - }; E9EC344C20066D4A00C0E546 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -1419,7 +1327,6 @@ isa = XCConfigurationList; buildConfigurations = ( E913C8FE1FFFA51E001A83F7 /* Debug */, - E9722062201E654D004F2AAD /* Testing */, E913C8FF1FFFA51E001A83F7 /* Release */, ); defaultConfigurationIsVisible = 0; @@ -1429,7 +1336,6 @@ isa = XCConfigurationList; buildConfigurations = ( E913C9011FFFA51E001A83F7 /* Debug */, - E9722063201E654D004F2AAD /* Testing */, E913C9021FFFA51E001A83F7 /* Release */, ); defaultConfigurationIsVisible = 0; @@ -1439,7 +1345,6 @@ isa = XCConfigurationList; buildConfigurations = ( E9EC344C20066D4A00C0E546 /* Debug */, - E9722064201E654D004F2AAD /* Testing */, E9EC344D20066D4A00C0E546 /* Release */, ); defaultConfigurationIsVisible = 0; diff --git a/Adamant/Adamant.entitlements b/Adamant/Adamant.entitlements new file mode 100644 index 000000000..903def2af --- /dev/null +++ b/Adamant/Adamant.entitlements @@ -0,0 +1,8 @@ + + + + + aps-environment + development + + From 2f6aac3940872c7092661a62374347b22bb27b2f Mon Sep 17 00:00:00 2001 From: Pavel Anokhov Date: Sat, 7 Apr 2018 14:38:55 +0300 Subject: [PATCH 02/34] Working on --- Adamant/AppDelegate.swift | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/Adamant/AppDelegate.swift b/Adamant/AppDelegate.swift index 103783614..74c45af8a 100644 --- a/Adamant/AppDelegate.swift +++ b/Adamant/AppDelegate.swift @@ -150,6 +150,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate { } } +// UIApplication.shared.registerForRemoteNotifications() +// UIApplication.shared.unregisterForRemoteNotifications() + return true } @@ -182,6 +185,27 @@ class AppDelegate: UIResponder, UIApplicationDelegate { } } +// MARK: - Remote notifications +extension AppDelegate { + func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { + print(userInfo) + } + + func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { + let tokenParts = deviceToken.map { data -> String in + return String(format: "%02.2hhx", data) + } + + let token = tokenParts.joined() + print("Device Token: \(token)") + } + + func application(_ application: UIApplication, + didFailToRegisterForRemoteNotificationsWithError error: Error) { + print("Failed to register: \(error)") + } +} + // MARK: - BackgroundFetch extension AppDelegate { From 737557477fca70af966dea4e5cb700ed5e22d03d Mon Sep 17 00:00:00 2001 From: Pavel Anokhov Date: Sat, 7 Apr 2018 15:49:01 +0300 Subject: [PATCH 03/34] pod updated FTIndicator: using forked repo with new feature. Original repo not yet merged pull request. --- Adamant.xcodeproj/project.pbxproj | 16 -------- Adamant/Services/AdamantDialogService.swift | 2 +- Podfile | 2 +- Podfile.lock | 45 ++++++++++++++++----- 4 files changed, 36 insertions(+), 29 deletions(-) diff --git a/Adamant.xcodeproj/project.pbxproj b/Adamant.xcodeproj/project.pbxproj index 18cc7a4e8..1ea16f4dc 100644 --- a/Adamant.xcodeproj/project.pbxproj +++ b/Adamant.xcodeproj/project.pbxproj @@ -747,7 +747,6 @@ E913C8EB1FFFA51D001A83F7 /* Frameworks */, E913C8EC1FFFA51D001A83F7 /* Resources */, 1C93FABD2FEB3E73F76872C8 /* [CP] Embed Pods Frameworks */, - 9103110972E84A36FC64A689 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -915,21 +914,6 @@ shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Adamant/Pods-Adamant-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - 9103110972E84A36FC64A689 /* [CP] Copy Pods Resources */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "[CP] Copy Pods Resources"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Adamant/Pods-Adamant-resources.sh\"\n"; - showEnvVarsInLog = 0; - }; E40A2E5936758D20792675C8 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; diff --git a/Adamant/Services/AdamantDialogService.swift b/Adamant/Services/AdamantDialogService.swift index 4f7f22ed5..4dbf20e25 100644 --- a/Adamant/Services/AdamantDialogService.swift +++ b/Adamant/Services/AdamantDialogService.swift @@ -16,7 +16,7 @@ class AdamantDialogService: DialogService { // Configure notifications init() { FTIndicator.setIndicatorStyle(.extraLight) - FTNotificationIndicator.setDefaultNotificationDelay(4) + FTNotificationIndicator.setDefaultDismissTime(4) } } diff --git a/Podfile b/Podfile index 3aba6621a..acb3cf817 100644 --- a/Podfile +++ b/Podfile @@ -12,7 +12,7 @@ target 'Adamant' do # UI pod 'FreakingSimpleRoundImageView' # Round avatars - pod 'FTIndicator' # Notifications and activity indicator + pod 'FTIndicator', :git => 'git@github.com:RealBonus/FTIndicator' # Notifications and activity indicator pod 'Eureka' # Forms pod 'MessageKit' # Chat UI pod 'MyLittlePinpad' # Pinpad diff --git a/Podfile.lock b/Podfile.lock index 30afbc936..1580c5cec 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -1,8 +1,8 @@ PODS: - - Alamofire (4.7.0) + - Alamofire (4.7.1) - EFQRCode (4.2.1) - Eureka (4.1.1) - - FreakingSimpleRoundImageView (1.0.2) + - FreakingSimpleRoundImageView (1.1) - FTIndicator (1.2.6): - FTIndicator/FTNotificationIndicator (= 1.2.6) - FTIndicator/FTProgressIndicator (= 1.2.6) @@ -11,19 +11,19 @@ PODS: - FTIndicator/FTProgressIndicator (1.2.6) - FTIndicator/FTToastIndicator (1.2.6) - KeychainAccess (3.1.0) - - MessageKit (0.13.2) + - MessageKit (0.13.3) - MyLittlePinpad (0.2.3) - QRCodeReader.swift (8.1.1) - ReachabilitySwift (4.1.0) - RNCryptor (5.0.2) - - Swinject (2.3.0) + - Swinject (2.4.0) DEPENDENCIES: - Alamofire - EFQRCode - Eureka - FreakingSimpleRoundImageView - - FTIndicator + - "FTIndicator (from `git@github.com:RealBonus/FTIndicator`)" - KeychainAccess - MessageKit - MyLittlePinpad @@ -32,20 +32,43 @@ DEPENDENCIES: - RNCryptor - Swinject +SPEC REPOS: + https://github.com/CocoaPods/Specs.git: + - Alamofire + - EFQRCode + - Eureka + - FreakingSimpleRoundImageView + - KeychainAccess + - MessageKit + - MyLittlePinpad + - QRCodeReader.swift + - ReachabilitySwift + - RNCryptor + - Swinject + +EXTERNAL SOURCES: + FTIndicator: + :git: "git@github.com:RealBonus/FTIndicator" + +CHECKOUT OPTIONS: + FTIndicator: + :commit: 423510db2d3c8a1f7bfc49fa1c479d1e40bc6f6a + :git: "git@github.com:RealBonus/FTIndicator" + SPEC CHECKSUMS: - Alamofire: 907e0a98eb68cdb7f9d1f541a563d6ac5dc77b25 + Alamofire: 68d7d521118d49c615a8d2214d87cdf525599d30 EFQRCode: f3fd67049faa07adb575495c05d72a34e407a940 Eureka: b88fb930e42c79f8c03c373d0fcdc28c1d6c50ed - FreakingSimpleRoundImageView: e87b0ef24c9b95b74b1c2cab5b03832dbabd4873 + FreakingSimpleRoundImageView: 4a3a1cb1347beb247f8c63b5b5cae9d770b431ee FTIndicator: d2a7c204341b7b0165b6dbeb5bf6979c6d7d4bb7 KeychainAccess: 94c5540b32eabf7bc32bfb976a268e8ea05fd6da - MessageKit: 274efb4454ddc9b3408b031b3396498dd4ed2462 + MessageKit: 63c5811bdf1087384c76732fa4b5479b2ed11568 MyLittlePinpad: 4ead0ea2da9957bb37d961b39743c1dfc8431efa QRCodeReader.swift: b164a681887de276d405ff02bce854d82cd6360b ReachabilitySwift: 6849231cd4e06559f3b9ef4a97a0a0f96d41e09f RNCryptor: 59b9f92e9fa8a1af74c32dc6b1af5e30ff5e1b64 - Swinject: 6e29897396bab81fe4aa40c5ca92ba996136334b + Swinject: a1364b0f66c2736bb03c1c7cab54809e16df25da -PODFILE CHECKSUM: d73e1c940db551ee6dfc242c1b5307dc8ed560be +PODFILE CHECKSUM: ee6b879d8c9a3523dce4dcac9891d665bf9b7f39 -COCOAPODS: 1.4.0 +COCOAPODS: 1.5.0 From ced21b6f224849c06f3419afe1fc456542eb252f Mon Sep 17 00:00:00 2001 From: Pavel Anokhov Date: Sat, 21 Apr 2018 14:47:30 +0300 Subject: [PATCH 04/34] pod updated --- Podfile | 2 +- Podfile.lock | 50 +++++++++++++++++++++----------------------------- 2 files changed, 22 insertions(+), 30 deletions(-) diff --git a/Podfile b/Podfile index acb3cf817..3aba6621a 100644 --- a/Podfile +++ b/Podfile @@ -12,7 +12,7 @@ target 'Adamant' do # UI pod 'FreakingSimpleRoundImageView' # Round avatars - pod 'FTIndicator', :git => 'git@github.com:RealBonus/FTIndicator' # Notifications and activity indicator + pod 'FTIndicator' # Notifications and activity indicator pod 'Eureka' # Forms pod 'MessageKit' # Chat UI pod 'MyLittlePinpad' # Pinpad diff --git a/Podfile.lock b/Podfile.lock index 1580c5cec..f10cbca89 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -1,21 +1,21 @@ PODS: - - Alamofire (4.7.1) + - Alamofire (4.7.2) - EFQRCode (4.2.1) - Eureka (4.1.1) - FreakingSimpleRoundImageView (1.1) - - FTIndicator (1.2.6): - - FTIndicator/FTNotificationIndicator (= 1.2.6) - - FTIndicator/FTProgressIndicator (= 1.2.6) - - FTIndicator/FTToastIndicator (= 1.2.6) - - FTIndicator/FTNotificationIndicator (1.2.6) - - FTIndicator/FTProgressIndicator (1.2.6) - - FTIndicator/FTToastIndicator (1.2.6) - - KeychainAccess (3.1.0) - - MessageKit (0.13.3) - - MyLittlePinpad (0.2.3) + - FTIndicator (1.2.8): + - FTIndicator/FTNotificationIndicator (= 1.2.8) + - FTIndicator/FTProgressIndicator (= 1.2.8) + - FTIndicator/FTToastIndicator (= 1.2.8) + - FTIndicator/FTNotificationIndicator (1.2.8) + - FTIndicator/FTProgressIndicator (1.2.8) + - FTIndicator/FTToastIndicator (1.2.8) + - KeychainAccess (3.1.1) + - MessageKit (0.13.4) + - MyLittlePinpad (0.2.4) - QRCodeReader.swift (8.1.1) - ReachabilitySwift (4.1.0) - - RNCryptor (5.0.2) + - RNCryptor (5.0.3) - Swinject (2.4.0) DEPENDENCIES: @@ -23,7 +23,7 @@ DEPENDENCIES: - EFQRCode - Eureka - FreakingSimpleRoundImageView - - "FTIndicator (from `git@github.com:RealBonus/FTIndicator`)" + - FTIndicator - KeychainAccess - MessageKit - MyLittlePinpad @@ -38,6 +38,7 @@ SPEC REPOS: - EFQRCode - Eureka - FreakingSimpleRoundImageView + - FTIndicator - KeychainAccess - MessageKit - MyLittlePinpad @@ -46,29 +47,20 @@ SPEC REPOS: - RNCryptor - Swinject -EXTERNAL SOURCES: - FTIndicator: - :git: "git@github.com:RealBonus/FTIndicator" - -CHECKOUT OPTIONS: - FTIndicator: - :commit: 423510db2d3c8a1f7bfc49fa1c479d1e40bc6f6a - :git: "git@github.com:RealBonus/FTIndicator" - SPEC CHECKSUMS: - Alamofire: 68d7d521118d49c615a8d2214d87cdf525599d30 + Alamofire: e4fa87002c137ba2d8d634d2c51fabcda0d5c223 EFQRCode: f3fd67049faa07adb575495c05d72a34e407a940 Eureka: b88fb930e42c79f8c03c373d0fcdc28c1d6c50ed FreakingSimpleRoundImageView: 4a3a1cb1347beb247f8c63b5b5cae9d770b431ee - FTIndicator: d2a7c204341b7b0165b6dbeb5bf6979c6d7d4bb7 - KeychainAccess: 94c5540b32eabf7bc32bfb976a268e8ea05fd6da - MessageKit: 63c5811bdf1087384c76732fa4b5479b2ed11568 - MyLittlePinpad: 4ead0ea2da9957bb37d961b39743c1dfc8431efa + FTIndicator: 5218a42e6a3bb6623f58e4931fc36bb09b7e1b78 + KeychainAccess: 7bd430028059754a3debab3cfc0bd1fc7fb85df3 + MessageKit: b600e3f466632f93c0333c78fd9006c960c8cbe2 + MyLittlePinpad: dc5f8a7fc13a4ad6fc9dc8d3359d91f1b5b1c7e8 QRCodeReader.swift: b164a681887de276d405ff02bce854d82cd6360b ReachabilitySwift: 6849231cd4e06559f3b9ef4a97a0a0f96d41e09f - RNCryptor: 59b9f92e9fa8a1af74c32dc6b1af5e30ff5e1b64 + RNCryptor: c93d19029dcf7ff160aca0f24d6c9e7b0d82f664 Swinject: a1364b0f66c2736bb03c1c7cab54809e16df25da -PODFILE CHECKSUM: ee6b879d8c9a3523dce4dcac9891d665bf9b7f39 +PODFILE CHECKSUM: d73e1c940db551ee6dfc242c1b5307dc8ed560be COCOAPODS: 1.5.0 From c4af4839a59e1412e1464c92e01d123d576baf09 Mon Sep 17 00:00:00 2001 From: Pavel Anokhov Date: Sat, 5 May 2018 15:36:02 +0300 Subject: [PATCH 05/34] Localisation update. --- .../Assets/l18n/en.lproj/Localizable.strings | 6 ++++++ .../Assets/l18n/ru.lproj/Localizable.strings | Bin 37510 -> 38008 bytes 2 files changed, 6 insertions(+) diff --git a/Adamant/Assets/l18n/en.lproj/Localizable.strings b/Adamant/Assets/l18n/en.lproj/Localizable.strings index 2e1091a0c..7a4f9d9cf 100644 --- a/Adamant/Assets/l18n/en.lproj/Localizable.strings +++ b/Adamant/Assets/l18n/en.lproj/Localizable.strings @@ -209,9 +209,15 @@ /* Notifications: New message notification title */ "NotificationsService.NewMessage.Title" = "New Message"; +/* Notifications: New single message notification body */ +"NotificationsService.NewMessage.BodySingle" = "You have new message"; + /* Notifications: New transfer transaction title */ "NotificationsService.NewTransfer.Title" = "New Transfer"; +/* Notifications: New single transfer transaction body */ +"NotificationsService.NewTransfer.BodySingle" = "You have new transfer"; + /* Notifications: User has disabled notifications. Head him into settings */ "NotificationsService.NotificationsDisabled" = "Notifications disabled. You can enable notifications in Settings"; diff --git a/Adamant/Assets/l18n/ru.lproj/Localizable.strings b/Adamant/Assets/l18n/ru.lproj/Localizable.strings index 973e0f6b635fc9e185b7e31c6f12231bf3dddf8b..cffc78e45c767fe75b969be56d2ea5bf49f2eaea 100644 GIT binary patch delta 144 zcmZo$%JgFe(*~m$&SHj4hCGIJhMdV0^MxnJM9Z)zG2}C(FjP)%jP~Ytg0LBaA*v={ zjFz5!#YSNAJ3Anbk(;a)^GMi_A(f$=L4g5o5<}`_{TN*&bF3#Xh!MhKmgwXKL2R2Z I#cVMJ0HBmCumAu6 delta 18 acmeydf~jpO(*~oM$vUyOHvfyOG6eurt_Z>a From ccff3433db98efc4f01f5d8be35711f03d3649c4 Mon Sep 17 00:00:00 2001 From: Pavel Anokhov Date: Mon, 7 May 2018 21:45:47 +0300 Subject: [PATCH 06/34] pod: Haring --- Adamant.xcodeproj/project.pbxproj | 2 ++ Podfile | 1 + Podfile.lock | 6 +++++- 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/Adamant.xcodeproj/project.pbxproj b/Adamant.xcodeproj/project.pbxproj index b46cf24fc..1974cf0d8 100644 --- a/Adamant.xcodeproj/project.pbxproj +++ b/Adamant.xcodeproj/project.pbxproj @@ -891,6 +891,7 @@ "${BUILT_PRODUCTS_DIR}/Eureka/Eureka.framework", "${BUILT_PRODUCTS_DIR}/FTIndicator/FTIndicator.framework", "${BUILT_PRODUCTS_DIR}/FreakingSimpleRoundImageView/FreakingSimpleRoundImageView.framework", + "${BUILT_PRODUCTS_DIR}/Haring/Haring.framework", "${BUILT_PRODUCTS_DIR}/KeychainAccess/KeychainAccess.framework", "${BUILT_PRODUCTS_DIR}/MessageKit/MessageKit.framework", "${BUILT_PRODUCTS_DIR}/MyLittlePinpad/MyLittlePinpad.framework", @@ -906,6 +907,7 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Eureka.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FTIndicator.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FreakingSimpleRoundImageView.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Haring.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/KeychainAccess.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MessageKit.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MyLittlePinpad.framework", diff --git a/Podfile b/Podfile index 3aba6621a..ed9f1912a 100644 --- a/Podfile +++ b/Podfile @@ -9,6 +9,7 @@ target 'Adamant' do pod 'RNCryptor' # Cryptor pod 'Swinject' # Dependency Injection pod 'ReachabilitySwift' # Network status + pod 'Haring' # Markdown parser # UI pod 'FreakingSimpleRoundImageView' # Round avatars diff --git a/Podfile.lock b/Podfile.lock index f10cbca89..6945a7a13 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -10,6 +10,7 @@ PODS: - FTIndicator/FTNotificationIndicator (1.2.8) - FTIndicator/FTProgressIndicator (1.2.8) - FTIndicator/FTToastIndicator (1.2.8) + - Haring (2.0.7) - KeychainAccess (3.1.1) - MessageKit (0.13.4) - MyLittlePinpad (0.2.4) @@ -24,6 +25,7 @@ DEPENDENCIES: - Eureka - FreakingSimpleRoundImageView - FTIndicator + - Haring - KeychainAccess - MessageKit - MyLittlePinpad @@ -39,6 +41,7 @@ SPEC REPOS: - Eureka - FreakingSimpleRoundImageView - FTIndicator + - Haring - KeychainAccess - MessageKit - MyLittlePinpad @@ -53,6 +56,7 @@ SPEC CHECKSUMS: Eureka: b88fb930e42c79f8c03c373d0fcdc28c1d6c50ed FreakingSimpleRoundImageView: 4a3a1cb1347beb247f8c63b5b5cae9d770b431ee FTIndicator: 5218a42e6a3bb6623f58e4931fc36bb09b7e1b78 + Haring: e9fa27a6ca648045fbacccc59547abed44b26694 KeychainAccess: 7bd430028059754a3debab3cfc0bd1fc7fb85df3 MessageKit: b600e3f466632f93c0333c78fd9006c960c8cbe2 MyLittlePinpad: dc5f8a7fc13a4ad6fc9dc8d3359d91f1b5b1c7e8 @@ -61,6 +65,6 @@ SPEC CHECKSUMS: RNCryptor: c93d19029dcf7ff160aca0f24d6c9e7b0d82f664 Swinject: a1364b0f66c2736bb03c1c7cab54809e16df25da -PODFILE CHECKSUM: d73e1c940db551ee6dfc242c1b5307dc8ed560be +PODFILE CHECKSUM: 2afa7e8b57ec3b78d5cea2c6ce51fd5f71e3c1f6 COCOAPODS: 1.5.0 From 331ffc871b35247dbff303d680dcac854b8a72a8 Mon Sep 17 00:00:00 2001 From: Pavel Anokhov Date: Mon, 7 May 2018 21:46:49 +0300 Subject: [PATCH 07/34] NotificationsViewController --- Adamant.xcodeproj/project.pbxproj | 4 + .../Assets/l18n/en.lproj/Localizable.strings | 36 +++ .../Assets/l18n/ru.lproj/Localizable.strings | Bin 38008 -> 43642 bytes .../Stories/Login/LoginViewController.swift | 14 +- .../NotificationsViewController.swift | 226 ++++++++++++++++++ Adamant/Stories/Settings/SettingsRoutes.swift | 5 + .../Settings/SettingsViewController.swift | 48 ++-- 7 files changed, 312 insertions(+), 21 deletions(-) create mode 100644 Adamant/Stories/Settings/NotificationsViewController.swift diff --git a/Adamant.xcodeproj/project.pbxproj b/Adamant.xcodeproj/project.pbxproj index 1974cf0d8..c721325b3 100644 --- a/Adamant.xcodeproj/project.pbxproj +++ b/Adamant.xcodeproj/project.pbxproj @@ -42,6 +42,7 @@ E91947B020002393001362F8 /* AdamantApiService.swift in Sources */ = {isa = PBXBuildFile; fileRef = E91947AF20002393001362F8 /* AdamantApiService.swift */; }; E91947B22000246A001362F8 /* AdamantError.swift in Sources */ = {isa = PBXBuildFile; fileRef = E91947B12000246A001362F8 /* AdamantError.swift */; }; E91947B420002809001362F8 /* Account.swift in Sources */ = {isa = PBXBuildFile; fileRef = E91947B320002809001362F8 /* Account.swift */; }; + E91A063A209F05AA0018A102 /* NotificationsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E91A0639209F05AA0018A102 /* NotificationsViewController.swift */; }; E9215973206119FB0000CA5C /* ReachabilityMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9215972206119FB0000CA5C /* ReachabilityMonitor.swift */; }; E921597520611A6A0000CA5C /* AdamantReachability.swift in Sources */ = {isa = PBXBuildFile; fileRef = E921597420611A6A0000CA5C /* AdamantReachability.swift */; }; E921597B206503000000CA5C /* ButtonsStripeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E921597A206503000000CA5C /* ButtonsStripeView.swift */; }; @@ -224,6 +225,7 @@ E91947AF20002393001362F8 /* AdamantApiService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdamantApiService.swift; sourceTree = ""; }; E91947B12000246A001362F8 /* AdamantError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdamantError.swift; sourceTree = ""; }; E91947B320002809001362F8 /* Account.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Account.swift; sourceTree = ""; }; + E91A0639209F05AA0018A102 /* NotificationsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsViewController.swift; sourceTree = ""; }; E9215972206119FB0000CA5C /* ReachabilityMonitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReachabilityMonitor.swift; sourceTree = ""; }; E921597420611A6A0000CA5C /* AdamantReachability.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdamantReachability.swift; sourceTree = ""; }; E921597A206503000000CA5C /* ButtonsStripeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonsStripeView.swift; sourceTree = ""; }; @@ -626,6 +628,7 @@ E982F69B20235B4D00566AC7 /* SettingsViewController.swift */, E93D7AC12052EE21005D19DC /* SettingsViewController+StayIn.swift */, E9942B7F203C058C00C163AF /* QRGeneratorViewController.swift */, + E91A0639209F05AA0018A102 /* NotificationsViewController.swift */, ); path = Settings; sourceTree = ""; @@ -1001,6 +1004,7 @@ E93EFE13200D1156000BB482 /* ChatViewController.swift in Sources */, E9B3D39E201F99F40019EB36 /* DataProvider.swift in Sources */, E9E7CDC02003AF6D00DFC4DB /* AdamantCellFactory.swift in Sources */, + E91A063A209F05AA0018A102 /* NotificationsViewController.swift in Sources */, E93B0D742028B21400126346 /* ChatsProvider.swift in Sources */, E9150BA12066DA210065A985 /* ChatTransaction+CoreDataClass.swift in Sources */, E9CAE8D42018AC1800345E76 /* AdamantApi+Keys.swift in Sources */, diff --git a/Adamant/Assets/l18n/en.lproj/Localizable.strings b/Adamant/Assets/l18n/en.lproj/Localizable.strings index 7a4f9d9cf..57020292b 100644 --- a/Adamant/Assets/l18n/en.lproj/Localizable.strings +++ b/Adamant/Assets/l18n/en.lproj/Localizable.strings @@ -82,6 +82,9 @@ /* Unknown internal error */ "ApiService.InternalError.UnknownError" = "Unknown error. Report a bug"; +/* Eureka forms Cancel button */ +"Cancel" = "Cancel"; + /* ChatList: outgoing message preview format, like 'You: %@' */ "ChatListPage.SentMessageFormat" = "You: %@"; @@ -206,6 +209,39 @@ /* New chat: scene title */ "NewChatScene.Title" = "New Chat"; +/* 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: Notifications update mode */ +"Notifications.Section.Settings" = "Notifications settings"; + +/* Notifications: Selected notifications types */ +"Notifications.Section.NotificationsType" = "Notifications"; + +/* Notifications: About ANS */ +"Notifications.Section.AboutANS" = "About ANS"; + +/* Notifications: Disable notifications */ +"Notifications.Mode.NotificationsDisabled" = "Disabled"; + +/* Notifications: Use Background fetch notifications */ +"Notifications.Mode.BackgroundFetch" = "Background Fetch"; + +/* Notifications: Use Apple Push notifications */ +"Notifications.Mode.ApplePush" = "Push"; + +/* Notifications: Visit Github */ +"Notifications.Row.VisitGithub" = "Visit Github"; + +/* Notifications: Mode */ +"Notifications.Row.Mode" = "Notifications mode"; + +/* Notifications: Send new messages notifications */ +"Notifications.Row.Messages" = "Messages"; + +/* Notifications: Send new transfers notifications */ +"Notifications.Row.Transfers" = "Transfers"; + /* Notifications: New message notification title */ "NotificationsService.NewMessage.Title" = "New Message"; diff --git a/Adamant/Assets/l18n/ru.lproj/Localizable.strings b/Adamant/Assets/l18n/ru.lproj/Localizable.strings index cffc78e45c767fe75b969be56d2ea5bf49f2eaea..aa2d5b53852c9a12de33e7b45ca54185b299a5c9 100644 GIT binary patch delta 2898 zcmaJ@U2hvj6rEHXTBJm@365*0q?@EsTL{K+Y}c{Zc6ObQA`=Hl1w~Gc(xkW%iBrW6 z5igj$Ac0CHG>8X4NPVk#LgW|pCqzPs2Oe7f0OAcHR5)j5x4UkWDq3fD=6;-e@40vO z?|aAo`Q_M8Uqr@c#g5n(H^hfxMVR6Pv4yuy(ZsrhorYM&*@hf04$7-rG5Klnglt56 z_J4@nv*pI7DQ{P!^5^J){CEAFd{7&DegOv7pwg6GS0fi=FtY$N+c3X|lU*1zVSGp2 z!XI6=AiE}Fa<4kp8-uK?EoQ#D8)}b0IFDyUjJkiOFpZRvHZq1~lnr1s5_nG<1-$35 z%Hw3pScV%uzSfZ87+FYEjCmzhRx&SPZHl5X4J{k?az-3-Z(xshvTeL(v6wy5QB1k% zbaf73@^}(>Xue=fxbGgolyZI+ZYh<*|13PwDc{S=pN+Nr&FVQng##ii0LUd}G>Mf1 zyo)%op=jaVl6O<#wq-EkorX)0%BZ}|K`sMJCAf63yDk>Rk{E|Z#+7jtm;`L*RCX=A zr;%OS&=`EU^vdA4&kCfBTpN%o0ccDrO9PZ$L1s6AZ~<}3?27N1%rJ=|tiPs4UFKLlMsc62T$kT2~uA>xo`cJhGaC~t>LrXe>Ezr?A- z3(7I9Sv;tA+cnKhahsWBrm|S)5UR(~BwVHtD&Y=6k@=dJu6&SV; zeoi)~YV-*I+LNxqA|lnkH7PS{j<5upzLSWRNI8MHGR1|bio{*}MTlgPmON^3Q8gdL z<$rc4!itcBlPUxU!Nd)=>>^q_G{(~e4M4&RGo>m$?8>9hE=j+6D-dJUFhe*yEGHS}q)a`sx|7G6pp2GHtqJO5 zC5`nQB4Xy4Z9XR-p6MU(PUb<57Rb3Hu7e=*Q8gz2sGSa(>e?cwcxjnn7A`B7?2jw^Qlcby?Y`~@*{<6QT!_J^@=L&ehQQK4;*oMD@ zJCUw3i7)4>-wsqdRCA14|$MF#rGn delta 30 ocmV+(0O9}o)B^aZ0IQG*Ir~m)} diff --git a/Adamant/Stories/Login/LoginViewController.swift b/Adamant/Stories/Login/LoginViewController.swift index a4157b3c0..ec56a030b 100644 --- a/Adamant/Stories/Login/LoginViewController.swift +++ b/Adamant/Stories/Login/LoginViewController.swift @@ -223,13 +223,13 @@ class LoginViewController: FormViewController { $0.hidden = Condition.function([], { [weak self] form -> Bool in return self?.hideNewPassphrase ?? false }) - }.cellUpdate({ (cell, row) in - cell.textView.textAlignment = .center - cell.textView.font = UIFont.adamantPrimary(size: 14) - cell.textView.textColor = UIColor.adamantPrimary - cell.textView.isSelectable = false - cell.textView.isEditable = false - }) + }.cellUpdate({ (cell, _) in + cell.textView.textAlignment = .center + cell.textView.font = UIFont.adamantPrimary(size: 14) + cell.textView.textColor = UIColor.adamantPrimary + cell.textView.isSelectable = false + cell.textView.isEditable = false + }) // New genegated passphrase <<< PassphraseRow() { diff --git a/Adamant/Stories/Settings/NotificationsViewController.swift b/Adamant/Stories/Settings/NotificationsViewController.swift new file mode 100644 index 000000000..576358e2d --- /dev/null +++ b/Adamant/Stories/Settings/NotificationsViewController.swift @@ -0,0 +1,226 @@ +// +// NotificationsViewController.swift +// Adamant +// +// Created by Anokhov Pavel on 06.05.2018. +// Copyright © 2018 Adamant. All rights reserved. +// + +import UIKit +import SafariServices +import Eureka +import Haring + +extension String.adamantLocalized { + struct notificationsScene { + static let title = NSLocalizedString("Notifications.Title", comment: "Notifications: scene title") + static let modesDescription = NSLocalizedString("Notifications.ModesDescription", comment: "Notifications: Modes description. Markdown supported.") + } +} + +enum NotificationMode: CustomStringConvertible { + case disabled + case backgroundFetch + case push + + var localized: String { + switch self { + case .disabled: + return NSLocalizedString("Notifications.Mode.NotificationsDisabled", comment: "Notifications: Disable notifications") + + case .backgroundFetch: + return NSLocalizedString("Notifications.Mode.BackgroundFetch", comment: "Notifications: Use Background fetch notifications") + + case .push: + return NSLocalizedString("Notifications.Mode.ApplePush", comment: "Notifications: Use Apple Push notifications") + } + } + + var description: String { + return localized + } +} + +class NotificationsViewController: FormViewController { + private static let githubUrl = URL.init(string: "https://github.com/Adamant-im/AdamantNotificationService/blob/master/README.md") + + // MARK: Sections & Rows + enum Sections { + case settings + case types + case ans + + var localized: String { + switch self { + case .settings: + return NSLocalizedString("Notifications.Section.Settings", comment: "Notifications: Notifications settings") + + case .types: + return NSLocalizedString("Notifications.Section.NotificationsType", comment: "Notifications: Selected notifications types") + + case .ans: + return NSLocalizedString("Notifications.Section.AboutANS", comment: "Notifications: About ANS") + } + } + + var tag: String { + switch self { + case .settings: return "sttngs" + case .types: return "tps" + case .ans: return "ans" + } + } + } + + enum Rows { + case notificationsMode + case messages + case transfers + case description + case github + + var localized: String { + switch self { + case .notificationsMode: + return NSLocalizedString("Notifications.Row.Mode", comment: "Notifications: Mode") + + case .messages: + return NSLocalizedString("Notifications.Row.Messages", comment: "Notifications: Send new messages notifications") + + case .transfers: + return NSLocalizedString("Notifications.Row.Transfers", comment: "Notifications: Send new transfers notifications") + + case .description: + return String.adamantLocalized.notificationsScene.modesDescription + + case .github: + return NSLocalizedString("Notifications.Row.VisitGithub", comment: "Notifications: Visit Github") + } + } + + var tag: String { + switch self { + case .notificationsMode: return "md" + case .messages: return "msgs" + case .transfers: return "trsfsrs" + case .description: return "dscrptn" + case .github: return "gthb" + } + } + } + + // MARK: Properties + + private(set) var notificationMode: NotificationMode = .disabled + + private var notificationTypesHidden: Bool = true + + // MARK: Lifecycle + + override func viewDidLoad() { + super.viewDidLoad() + self.navigationItem.title = String.adamantLocalized.notificationsScene.title + navigationOptions = .Disabled + + // MARK: Modes + form +++ Section(Sections.settings.localized) { + $0.tag = Sections.settings.tag + } + + <<< ActionSheetRow() { + $0.tag = Rows.notificationsMode.tag + $0.title = Rows.notificationsMode.localized + $0.selectorTitle = Rows.notificationsMode.localized + $0.options = [.disabled, .backgroundFetch, .push] + $0.value = NotificationMode.disabled + }.onChange({ [weak self] row in + guard let mode = row.value else { + return + } + + self?.setNotificationMode(mode) + }).cellUpdate({ (cell, _) in + cell.accessoryType = .disclosureIndicator + }) + + + // MARK: Types + +++ Section(Sections.types.localized) { + $0.tag = Sections.types.tag + $0.hidden = Condition.function([Rows.notificationsMode.tag], { [weak self] _ -> Bool in + guard let notificationTypesHidden = self?.notificationTypesHidden else { + return true + } + return notificationTypesHidden + }) + } + + <<< SwitchRow() { + $0.tag = Rows.messages.tag + $0.title = Rows.messages.localized + } + + <<< SwitchRow() { + $0.tag = Rows.transfers.tag + $0.title = Rows.transfers.localized + } + + + // 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 + }).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) + }).cellUpdate({ (cell, _) in + cell.accessoryType = .disclosureIndicator + }) + } + + // MARK: Logic + + func setNotificationMode(_ mode: NotificationMode) { + guard mode != notificationMode else { + return + } + + print(mode.localized) + + notificationMode = mode; + notificationTypesHidden = mode == .disabled + +// switch mode { +// case .disabled: +// return +// +// case .backgroundFetch: +// return +// +// case .push: +// return +// } + } +} diff --git a/Adamant/Stories/Settings/SettingsRoutes.swift b/Adamant/Stories/Settings/SettingsRoutes.swift index 7cc5772b4..b15f9fc6c 100644 --- a/Adamant/Stories/Settings/SettingsRoutes.swift +++ b/Adamant/Stories/Settings/SettingsRoutes.swift @@ -26,6 +26,11 @@ extension AdamantScene { return c }) + static let notifications = AdamantScene(identifier: "NotificationsViewController") { r -> UIViewController in + let c = NotificationsViewController() + return c + } + private init() {} } } diff --git a/Adamant/Stories/Settings/SettingsViewController.swift b/Adamant/Stories/Settings/SettingsViewController.swift index 845324fc1..8bb09a03f 100644 --- a/Adamant/Stories/Settings/SettingsViewController.swift +++ b/Adamant/Stories/Settings/SettingsViewController.swift @@ -17,6 +17,8 @@ extension String.adamantLocalized { static let stayInTurnOff = NSLocalizedString("SettingsPage.DoNotStayLoggedIn", comment: "Config: turn off 'Stay Logged In' confirmation") static let biometryOnReason = NSLocalizedString("SettingsPage.UseBiometry", comment: "Config: Authorization reason for turning biometry on") static let biometryOffReason = NSLocalizedString("SettingsPage.DoNotUseBiometry", comment: "Config: Authorization reason for turning biometry off") + + private init() {} } } @@ -145,26 +147,44 @@ class SettingsViewController: FormViewController { }) // Notifications - <<< SwitchRow() { - $0.tag = Rows.notifications.tag +// <<< SwitchRow() { +// $0.tag = Rows.notifications.tag +// $0.title = Rows.notifications.localized +// $0.value = notificationsService.notificationsEnabled +// +// $0.hidden = Condition.function([Rows.stayLoggedIn.tag], { form -> Bool in +// guard let row: SwitchRow = form.rowBy(tag: Rows.stayLoggedIn.tag), let value = row.value else { +// return true +// } +// +// return !value +// }) +// }.onChange({ [weak self] row in +// guard let enabled = row.value else { return } +// self?.setNotifications(enabled: enabled) +// }).cellUpdate({ (cell, _) in +// if let label = cell.textLabel { +// label.font = UIFont.adamantPrimary(size: 17) +// label.textColor = UIColor.adamantPrimary +// } +// }) + <<< LabelRow() { $0.title = Rows.notifications.localized - $0.value = notificationsService.notificationsEnabled - - $0.hidden = Condition.function([Rows.stayLoggedIn.tag], { form -> Bool in - guard let row: SwitchRow = form.rowBy(tag: Rows.stayLoggedIn.tag), let value = row.value else { - return true - } - - return !value - }) - }.onChange({ [weak self] row in - guard let enabled = row.value else { return } - self?.setNotifications(enabled: enabled) + $0.tag = Rows.notifications.tag + }.cellSetup({ (cell, _) in + cell.selectionStyle = .gray + }).onCellSelection({ [weak self] (cell, _) in + guard let nav = self?.navigationController, let vc = self?.router.get(scene: AdamantScene.Settings.notifications) else { + return + } + nav.pushViewController(vc, animated: true) }).cellUpdate({ (cell, _) in if let label = cell.textLabel { label.font = UIFont.adamantPrimary(size: 17) label.textColor = UIColor.adamantPrimary } + + cell.accessoryType = .disclosureIndicator }) // MARK: Utilities From e20e10da32fa2cf58b24f8fde8c69f74e371d852 Mon Sep 17 00:00:00 2001 From: Pavel Anokhov Date: Tue, 8 May 2018 17:14:47 +0300 Subject: [PATCH 08/34] AdamantNotificationService refactored: modes. AnsApiService draft --- Adamant.xcodeproj/project.pbxproj | 4 + Adamant/AppDelegate.swift | 70 ++++---- Adamant/Info.plist | 1 + .../NotificationsService.swift | 16 +- .../Services/AdamantNotificationService.swift | 156 ++++++++++-------- .../Services/ApiService/AnsApiService.swift | 27 +++ .../NotificationsViewController.swift | 110 ++++++------ Adamant/Stories/Settings/SettingsRoutes.swift | 2 +- .../Settings/SettingsViewController.swift | 79 --------- Adamant/SwinjectDependencies.swift | 9 - 10 files changed, 232 insertions(+), 242 deletions(-) create mode 100644 Adamant/Services/ApiService/AnsApiService.swift diff --git a/Adamant.xcodeproj/project.pbxproj b/Adamant.xcodeproj/project.pbxproj index c721325b3..de217ca00 100644 --- a/Adamant.xcodeproj/project.pbxproj +++ b/Adamant.xcodeproj/project.pbxproj @@ -99,6 +99,7 @@ E95F85C4200A540B0070534A /* Chat.json in Resources */ = {isa = PBXBuildFile; fileRef = E95F85C3200A540B0070534A /* Chat.json */; }; E95F85C7200A9B070070534A /* ChatTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = E95F85C5200A9B070070534A /* ChatTableViewCell.swift */; }; E95F85C8200A9B070070534A /* ChatTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = E95F85C6200A9B070070534A /* ChatTableViewCell.xib */; }; + E961E94D20A1E203005C8872 /* AnsApiService.swift in Sources */ = {isa = PBXBuildFile; fileRef = E961E94C20A1E203005C8872 /* AnsApiService.swift */; }; E9722066201F42BB004F2AAD /* CoreDataStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9722065201F42BB004F2AAD /* CoreDataStack.swift */; }; E9722068201F42CC004F2AAD /* InMemoryCoreDataStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9722067201F42CC004F2AAD /* InMemoryCoreDataStack.swift */; }; E972206B201F44CA004F2AAD /* TransfersProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = E972206A201F44CA004F2AAD /* TransfersProvider.swift */; }; @@ -285,6 +286,7 @@ E95F85C3200A540B0070534A /* Chat.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = Chat.json; sourceTree = ""; }; E95F85C5200A9B070070534A /* ChatTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatTableViewCell.swift; sourceTree = ""; }; E95F85C6200A9B070070534A /* ChatTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ChatTableViewCell.xib; sourceTree = ""; }; + E961E94C20A1E203005C8872 /* AnsApiService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnsApiService.swift; sourceTree = ""; }; E9722065201F42BB004F2AAD /* CoreDataStack.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreDataStack.swift; sourceTree = ""; }; E9722067201F42CC004F2AAD /* InMemoryCoreDataStack.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InMemoryCoreDataStack.swift; sourceTree = ""; }; E972206A201F44CA004F2AAD /* TransfersProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransfersProvider.swift; sourceTree = ""; }; @@ -667,6 +669,7 @@ E9CAE8D32018AC1800345E76 /* AdamantApi+Keys.swift */, E9CAE8D52018AC5300345E76 /* AdamantApi+Transactions.swift */, E9CAE8D72018ACA700345E76 /* AdamantApi+Transfers.swift */, + E961E94C20A1E203005C8872 /* AnsApiService.swift */, ); path = ApiService; sourceTree = ""; @@ -1003,6 +1006,7 @@ E9942B87203D9E5100C163AF /* EurekaQRRow.swift in Sources */, E93EFE13200D1156000BB482 /* ChatViewController.swift in Sources */, E9B3D39E201F99F40019EB36 /* DataProvider.swift in Sources */, + E961E94D20A1E203005C8872 /* AnsApiService.swift in Sources */, E9E7CDC02003AF6D00DFC4DB /* AdamantCellFactory.swift in Sources */, E91A063A209F05AA0018A102 /* NotificationsViewController.swift in Sources */, E93B0D742028B21400126346 /* ChatsProvider.swift in Sources */, diff --git a/Adamant/AppDelegate.swift b/Adamant/AppDelegate.swift index 74c45af8a..0be06a269 100644 --- a/Adamant/AppDelegate.swift +++ b/Adamant/AppDelegate.swift @@ -17,14 +17,29 @@ extension String.adamantLocalized { } } +// MARK: Resources +struct AdamantResources { + // Storyboard + static let jsCore = Bundle.main.url(forResource: "adamant-core", withExtension: "js")! + static let api = URL(string: "https://endless.adamant.im")! + static let ans = URL(string: "")! + static let coreDataModel = Bundle.main.url(forResource: "ChatModels", withExtension: "momd")! + + private init() {} +} + + @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? var repeater: RepeaterService! var container: Container! + // MARK: Dependencies + weak var ansApiService: AnsApiService? weak var accountService: AccountService? weak var notificationService: NotificationsService? + weak var dialogService: DialogService? // MARK: - Lifecycle @@ -120,25 +135,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { } - // MARK: 6. Notifications - if let service = container.resolve(NotificationsService.self) { - if service.notificationsEnabled { - UIApplication.shared.setMinimumBackgroundFetchInterval(UIApplicationBackgroundFetchIntervalMinimum) - } else { - UIApplication.shared.setMinimumBackgroundFetchInterval(UIApplicationBackgroundFetchIntervalNever) - } - - NotificationCenter.default.addObserver(forName: Notification.Name.AdamantNotificationService.showNotificationsChanged, object: service, queue: OperationQueue.main) { _ in - if service.notificationsEnabled { - UIApplication.shared.setMinimumBackgroundFetchInterval(UIApplicationBackgroundFetchIntervalMinimum) - } else { - UIApplication.shared.setMinimumBackgroundFetchInterval(UIApplicationBackgroundFetchIntervalNever) - } - } - } - - - // MARK: 7. Logout reset + // MARK: 6. Logout reset NotificationCenter.default.addObserver(forName: Notification.Name.AdamantAccountService.userLoggedOut, object: nil, queue: OperationQueue.main) { [weak self] _ in // On logout, pop all navigators to root. guard let tbc = self?.window?.rootViewController as? UITabBarController, let vcs = tbc.viewControllers else { @@ -150,9 +147,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate { } } -// UIApplication.shared.registerForRemoteNotifications() -// UIApplication.shared.unregisterForRemoteNotifications() - return true } @@ -166,6 +160,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate { repeater.pauseAll() } + // MARK: Notifications + func applicationDidBecomeActive(_ application: UIApplication) { if accountService?.account != nil { notificationService?.removeAllDeliveredNotifications() @@ -187,27 +183,33 @@ class AppDelegate: UIResponder, UIApplicationDelegate { // MARK: - Remote notifications extension AppDelegate { - func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { - print(userInfo) - } +// func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { +// print(userInfo) +// } func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { - let tokenParts = deviceToken.map { data -> String in - return String(format: "%02.2hhx", data) - } + let token = deviceToken.map { String(format: "%02.2hhx", $0) }.joined() - let token = tokenParts.joined() - print("Device Token: \(token)") + ansApiService?.register(token: token) { [weak self] result in + switch result { + case .success: + return + + case .failure(let error): + self?.notificationService?.setNotificationsMode(.disabled, completion: nil) + self?.dialogService?.showError(withMessage: String.localizedStringWithFormat(String.adamantLocalized.notifications.registerRemotesError, error.localizedDescription)) + } + } } - func application(_ application: UIApplication, - didFailToRegisterForRemoteNotificationsWithError error: Error) { - print("Failed to register: \(error)") + func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) { + notificationService?.setNotificationsMode(.disabled, completion: nil) + dialogService?.showError(withMessage: String.localizedStringWithFormat(String.adamantLocalized.notifications.registerRemotesError, error.localizedDescription)) } } -// MARK: - BackgroundFetch +// MARK: - Background Fetch extension AppDelegate { func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { let container = Container() diff --git a/Adamant/Info.plist b/Adamant/Info.plist index 93677bded..cc3c97d3a 100644 --- a/Adamant/Info.plist +++ b/Adamant/Info.plist @@ -47,6 +47,7 @@ UIBackgroundModes fetch + remote-notification UILaunchStoryboardName LaunchScreen diff --git a/Adamant/ServiceProtocols/NotificationsService.swift b/Adamant/ServiceProtocols/NotificationsService.swift index af88356a0..70cb8feb4 100644 --- a/Adamant/ServiceProtocols/NotificationsService.swift +++ b/Adamant/ServiceProtocols/NotificationsService.swift @@ -18,19 +18,27 @@ extension String.adamantLocalized { static let newTransferTitle = NSLocalizedString("NotificationsService.NewTransfer.Title", comment: "Notifications: New transfer transaction title") static let newTransferBody = NSLocalizedString("NotificationsService.NewTransfer.BodyFormat", comment: "Notifications: New transfer notification body. Using %d for amount") + static let registerRemotesError = NSLocalizedString("NotificationsService.Error.RegistrationRemotesFormat", comment: "Notifications: Something went wrong with registration remote notifications. %@ for description") + private init() {} } } extension StoreKey { struct notificationsService { - static let notificationsEnabled = "notifications.show" + static let notificationsMode = "notifications.mode" static let customBadgeNumber = "notifications.number" private init() {} } } +enum NotificationsMode: Int { + case disabled + case backgroundFetch + case push +} + /// Supported notification types /// /// - message: text message @@ -72,7 +80,7 @@ enum AdamantNotificationType { extension Notification.Name { struct AdamantNotificationService { /// Raised when user has logged out. - static let showNotificationsChanged = Notification.Name("adamant.notificationService.showNotifications") + static let notificationsModeChanged = Notification.Name("adamant.notificationService.notificationsMode") private init() {} } @@ -86,9 +94,9 @@ enum NotificationsServiceResult { } protocol NotificationsService: class { - var notificationsEnabled: Bool { get } + var notificationsMode: NotificationsMode { get } - func setNotificationsEnabled(_ enabled: Bool, completion: @escaping (NotificationsServiceResult) -> Void) + func setNotificationsMode(_ mode: NotificationsMode, completion: ((NotificationsServiceResult) -> Void)?) func showNotification(title: String, body: String, type: AdamantNotificationType) diff --git a/Adamant/Services/AdamantNotificationService.swift b/Adamant/Services/AdamantNotificationService.swift index bf05b0161..0833ed866 100644 --- a/Adamant/Services/AdamantNotificationService.swift +++ b/Adamant/Services/AdamantNotificationService.swift @@ -10,36 +10,27 @@ import Foundation import UIKit import UserNotifications -class AdamantNotificationsService: NotificationsService { - // MARK: Dependencies - var securedStore: SecuredStore! { - didSet { - if let raw = securedStore.get(StoreKey.notificationsService.notificationsEnabled), let show = Bool(raw) { - if show { - UNUserNotificationCenter.current().getNotificationSettings { [weak self] settings in - switch settings.authorizationStatus { - case .authorized: - self?.notificationsEnabled = true - - case .denied: - self?.notificationsEnabled = false - - case .notDetermined: - self?.notificationsEnabled = false - } - } - } else { - notificationsEnabled = false - } - } else { - notificationsEnabled = false - } +extension NotificationsMode { + func toRaw() -> String { + return String(self.rawValue) + } + + init?(string: String) { + guard let int = Int(string: string), let mode = NotificationsMode(rawValue: int) else { + return nil } + + self = mode } +} + +class AdamantNotificationsService: NotificationsService { + // MARK: Dependencies + var securedStore: SecuredStore! // MARK: Properties - private(set) var notificationsEnabled = false + private(set) var notificationsMode: NotificationsMode = .disabled private(set) var customBadgeNumber = 0 private var isBackgroundSession = false @@ -47,62 +38,94 @@ class AdamantNotificationsService: NotificationsService { // MARK: Lifecycle init() { - NotificationCenter.default.addObserver(forName: Notification.Name.AdamantAccountService.userLoggedIn, object: nil, queue: OperationQueue.main) { _ in + NotificationCenter.default.addObserver(forName: Notification.Name.AdamantAccountService.userLoggedIn, object: nil, queue: OperationQueue.main) { [weak self] _ in UNUserNotificationCenter.current().removeAllDeliveredNotifications() UIApplication.shared.applicationIconBadgeNumber = 0 + + if let securedStore = self?.securedStore, let raw = securedStore.get(StoreKey.notificationsService.notificationsMode), let mode = NotificationsMode(string: raw) { + self?.setNotificationsMode(mode, completion: nil) + } else { + self?.setNotificationsMode(.disabled, completion: nil) + } } NotificationCenter.default.addObserver(forName: Notification.Name.AdamantAccountService.userLoggedOut, object: nil, queue: nil) { [weak self] _ in - self?.notificationsEnabled = false - self?.securedStore.remove(StoreKey.notificationsService.notificationsEnabled) - NotificationCenter.default.post(name: Notification.Name.AdamantNotificationService.showNotificationsChanged, object: self) + self?.setNotificationsMode(.disabled, completion: nil) } } deinit { NotificationCenter.default.removeObserver(self) } - - - // MARK: Notifications authorization - func setNotificationsEnabled(_ enabled: Bool, completion: @escaping (NotificationsServiceResult) -> Void) { - guard notificationsEnabled != enabled else { +} + + +// MARK: - Notifications mode { +extension AdamantNotificationsService { + func setNotificationsMode(_ mode: NotificationsMode, completion: ((NotificationsServiceResult) -> Void)?) { + switch mode { + case .disabled: + UIApplication.shared.unregisterForRemoteNotifications() + UIApplication.shared.setMinimumBackgroundFetchInterval(UIApplicationBackgroundFetchIntervalNever) + securedStore.remove(StoreKey.notificationsService.notificationsMode) + notificationsMode = mode + NotificationCenter.default.post(name: Notification.Name.AdamantNotificationService.notificationsModeChanged, object: self) + completion?(.success) return - } - - if enabled { // MARK: Turn on - UNUserNotificationCenter.current().getNotificationSettings(completionHandler: { [weak self] settings in - switch settings.authorizationStatus { - case .authorized: - self?.notificationsEnabled = true - self?.securedStore.set(String(true), for: StoreKey.notificationsService.notificationsEnabled) - NotificationCenter.default.post(name: Notification.Name.AdamantNotificationService.showNotificationsChanged, object: self) - completion(.success) - - case .denied: - self?.notificationsEnabled = false - completion(.denied(error: nil)) - - case .notDetermined: - UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound], completionHandler: { (granted, error) in - self?.notificationsEnabled = false - if granted { - completion(.success) - } else { - completion(.denied(error: error)) - } - }) + + case .backgroundFetch: + authorizeNotifications { [weak self] (success, error) in + guard success else { + completion?(.denied(error: error)) + return } - }) - } else { // MARK: Turn off - notificationsEnabled = false - securedStore.remove(StoreKey.notificationsService.notificationsEnabled) - NotificationCenter.default.post(name: Notification.Name.AdamantNotificationService.showNotificationsChanged, object: self) + + UIApplication.shared.unregisterForRemoteNotifications() + UIApplication.shared.setMinimumBackgroundFetchInterval(UIApplicationBackgroundFetchIntervalMinimum) + 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: + authorizeNotifications { [weak self] (success, error) in + guard success else { + completion?(.denied(error: error)) + return + } + + UIApplication.shared.registerForRemoteNotifications() + UIApplication.shared.setMinimumBackgroundFetchInterval(UIApplicationBackgroundFetchIntervalNever) + self?.securedStore.set(mode.toRaw(), for: StoreKey.notificationsService.notificationsMode) + self?.notificationsMode = mode + NotificationCenter.default.post(name: Notification.Name.AdamantNotificationService.notificationsModeChanged, object: self) + completion?(.success) + } } } - - // MARK: Posting & removing Notifications + private func authorizeNotifications(completion: @escaping (Bool, Error?) -> Void) { + UNUserNotificationCenter.current().getNotificationSettings { settings in + switch settings.authorizationStatus { + case .authorized: + completion(true, nil) + + case .denied: + completion(false, nil) + + case .notDetermined: + UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound], completionHandler: { (granted, error) in + completion(granted, error) + }) + } + } + } +} + + +// MARK: - Posting & removing Notifications +extension AdamantNotificationsService { func showNotification(title: String, body: String, type: AdamantNotificationType) { let content = UNMutableNotificationContent() content.title = title @@ -156,7 +179,8 @@ class AdamantNotificationsService: NotificationsService { } } -// MARK: Background batch notifications + +// MARK: - Background batch notifications extension AdamantNotificationsService { func startBackgroundBatchNotifications() { isBackgroundSession = true diff --git a/Adamant/Services/ApiService/AnsApiService.swift b/Adamant/Services/ApiService/AnsApiService.swift new file mode 100644 index 000000000..4e3b6903b --- /dev/null +++ b/Adamant/Services/ApiService/AnsApiService.swift @@ -0,0 +1,27 @@ +// +// AnsApiService.swift +// Adamant +// +// Created by Anokhov Pavel on 08.05.2018. +// Copyright © 2018 Adamant. All rights reserved. +// + +import Foundation +import Alamofire + +enum AnsApiServiceResult { + case success + case failure(ApiServiceError) +} + +class AnsApiService { + struct ApiCommants { + static let register = "/api/reg" + + private init() {} + } + + func register(token: String, completion: @escaping (AnsApiServiceResult) -> Void) { + + } +} diff --git a/Adamant/Stories/Settings/NotificationsViewController.swift b/Adamant/Stories/Settings/NotificationsViewController.swift index 576358e2d..ed007e9cf 100644 --- a/Adamant/Stories/Settings/NotificationsViewController.swift +++ b/Adamant/Stories/Settings/NotificationsViewController.swift @@ -18,11 +18,7 @@ extension String.adamantLocalized { } } -enum NotificationMode: CustomStringConvertible { - case disabled - case backgroundFetch - case push - +extension NotificationsMode: CustomStringConvertible { var localized: String { switch self { case .disabled: @@ -42,8 +38,6 @@ enum NotificationMode: CustomStringConvertible { } class NotificationsViewController: FormViewController { - private static let githubUrl = URL.init(string: "https://github.com/Adamant-im/AdamantNotificationService/blob/master/README.md") - // MARK: Sections & Rows enum Sections { case settings @@ -109,12 +103,17 @@ class NotificationsViewController: FormViewController { } } - // MARK: Properties + // MARK: Dependencies + + var notificationsService: NotificationsService! - private(set) var notificationMode: NotificationMode = .disabled + // 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 override func viewDidLoad() { @@ -127,12 +126,12 @@ class NotificationsViewController: FormViewController { $0.tag = Sections.settings.tag } - <<< ActionSheetRow() { + <<< ActionSheetRow() { $0.tag = Rows.notificationsMode.tag $0.title = Rows.notificationsMode.localized $0.selectorTitle = Rows.notificationsMode.localized $0.options = [.disabled, .backgroundFetch, .push] - $0.value = NotificationMode.disabled + $0.value = notificationsService.notificationsMode }.onChange({ [weak self] row in guard let mode = row.value else { return @@ -144,30 +143,7 @@ class NotificationsViewController: FormViewController { }) - // MARK: Types - +++ Section(Sections.types.localized) { - $0.tag = Sections.types.tag - $0.hidden = Condition.function([Rows.notificationsMode.tag], { [weak self] _ -> Bool in - guard let notificationTypesHidden = self?.notificationTypesHidden else { - return true - } - return notificationTypesHidden - }) - } - - <<< SwitchRow() { - $0.tag = Rows.messages.tag - $0.title = Rows.messages.localized - } - - <<< SwitchRow() { - $0.tag = Rows.transfers.tag - $0.title = Rows.transfers.localized - } - - // MARK: ANS - +++ Section(Sections.ans.localized) { $0.tag = Sections.ans.tag } @@ -202,25 +178,61 @@ class NotificationsViewController: FormViewController { // MARK: Logic - func setNotificationMode(_ mode: NotificationMode) { - guard mode != notificationMode else { + func setNotificationMode(_ mode: NotificationsMode) { + guard mode != notificationsService.notificationsMode else { return } - print(mode.localized) + 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: + return + + case .denied(error: _): + if let row: SwitchRow = self?.form.rowBy(tag: Rows.notificationsMode.tag) { + row.value = false + row.updateCell() + } + + self?.notificationTypesHidden = true + + if let section = self?.form.sectionBy(tag: Sections.types.tag) { + section.evaluateHidden() + } + + DispatchQueue.main.async { + self?.presentNotificationsDeniedError() + } + } + } + } + + private func presentNotificationsDeniedError() { + let alert = UIAlertController(title: nil, message: String.adamantLocalized.notifications.notificationsDisabled, preferredStyle: .alert) + + alert.addAction(UIAlertAction(title: String.adamantLocalized.alert.settings, style: .default) { _ in + DispatchQueue.main.async { + if let settingsURL = URL(string: UIApplicationOpenSettingsURLString) { + UIApplication.shared.open(settingsURL, options: [:], completionHandler: nil) + } + } + }) - notificationMode = mode; - notificationTypesHidden = mode == .disabled + alert.addAction(UIAlertAction(title: String.adamantLocalized.alert.cancel, style: .cancel, handler: nil)) -// switch mode { -// case .disabled: -// return -// -// case .backgroundFetch: -// return -// -// case .push: -// return -// } + present(alert, animated: true, completion: nil) } } diff --git a/Adamant/Stories/Settings/SettingsRoutes.swift b/Adamant/Stories/Settings/SettingsRoutes.swift index b15f9fc6c..c093d9b78 100644 --- a/Adamant/Stories/Settings/SettingsRoutes.swift +++ b/Adamant/Stories/Settings/SettingsRoutes.swift @@ -15,7 +15,6 @@ extension AdamantScene { c.dialogService = r.resolve(DialogService.self) c.accountService = r.resolve(AccountService.self) c.localAuth = r.resolve(LocalAuthentication.self) - c.notificationsService = r.resolve(NotificationsService.self) c.router = r.resolve(Router.self) return c }) @@ -28,6 +27,7 @@ extension AdamantScene { static let notifications = AdamantScene(identifier: "NotificationsViewController") { r -> UIViewController in let c = NotificationsViewController() + c.notificationsService = r.resolve(NotificationsService.self) return c } diff --git a/Adamant/Stories/Settings/SettingsViewController.swift b/Adamant/Stories/Settings/SettingsViewController.swift index 8bb09a03f..f1b90458e 100644 --- a/Adamant/Stories/Settings/SettingsViewController.swift +++ b/Adamant/Stories/Settings/SettingsViewController.swift @@ -85,7 +85,6 @@ class SettingsViewController: FormViewController { var accountService: AccountService! var dialogService: DialogService! var localAuth: LocalAuthentication! - var notificationsService: NotificationsService! var router: Router! @@ -147,27 +146,6 @@ class SettingsViewController: FormViewController { }) // Notifications -// <<< SwitchRow() { -// $0.tag = Rows.notifications.tag -// $0.title = Rows.notifications.localized -// $0.value = notificationsService.notificationsEnabled -// -// $0.hidden = Condition.function([Rows.stayLoggedIn.tag], { form -> Bool in -// guard let row: SwitchRow = form.rowBy(tag: Rows.stayLoggedIn.tag), let value = row.value else { -// return true -// } -// -// return !value -// }) -// }.onChange({ [weak self] row in -// guard let enabled = row.value else { return } -// self?.setNotifications(enabled: enabled) -// }).cellUpdate({ (cell, _) in -// if let label = cell.textLabel { -// label.font = UIFont.adamantPrimary(size: 17) -// label.textColor = UIColor.adamantPrimary -// } -// }) <<< LabelRow() { $0.title = Rows.notifications.localized $0.tag = Rows.notifications.tag @@ -258,16 +236,6 @@ class SettingsViewController: FormViewController { row.evaluateHidden() } } - - // MARK: Notifications - NotificationCenter.default.addObserver(forName: Notification.Name.AdamantNotificationService.showNotificationsChanged, object: nil, queue: OperationQueue.main) { [weak self] _ in - guard let row: SwitchRow = self?.form.rowBy(tag: Rows.notifications.tag), let value = self?.notificationsService.notificationsEnabled else { - return - } - - row.value = value - row.updateCell() - } } override func viewWillAppear(_ animated: Bool) { @@ -281,50 +249,3 @@ class SettingsViewController: FormViewController { NotificationCenter.default.removeObserver(self) } } - - -// MARK: - Properties -extension SettingsViewController { - func setNotifications(enabled: Bool) { - guard enabled != notificationsService.notificationsEnabled else { - return - } - - notificationsService.setNotificationsEnabled(enabled) { [weak self] result in - switch result { - case .success: - break - - case .denied(error: _): - DispatchQueue.main.async { - let alert = UIAlertController(title: nil, message: String.adamantLocalized.notifications.notificationsDisabled, preferredStyle: .alert) - - alert.addAction(UIAlertAction(title: String.adamantLocalized.alert.settings, style: .default) { _ in - DispatchQueue.main.async { - if let row: SwitchRow = self?.form.rowBy(tag: Rows.notifications.tag) { - row.value = false - row.updateCell() - } - - if let settingsURL = URL(string: UIApplicationOpenSettingsURLString) { - UIApplication.shared.open(settingsURL, options: [:], completionHandler: nil) - } - } - }) - - alert.addAction(UIAlertAction(title: String.adamantLocalized.alert.cancel, style: .cancel, handler: { _ in - if let row: SwitchRow = self?.form.rowBy(tag: Rows.notifications.tag) { - row.value = false - row.updateCell() - } - })) - - self?.present(alert, animated: true, completion: nil) - } - } - } - } -} - - - diff --git a/Adamant/SwinjectDependencies.swift b/Adamant/SwinjectDependencies.swift index 3ce08909b..ba69519b4 100644 --- a/Adamant/SwinjectDependencies.swift +++ b/Adamant/SwinjectDependencies.swift @@ -8,15 +8,6 @@ import Swinject -// MARK: - Resources -private struct AdamantResources { - // Storyboard - static let jsCore = Bundle.main.url(forResource: "adamant-core", withExtension: "js")! - static let api = URL(string: "https://endless.adamant.im")! - static let coreDataModel = Bundle.main.url(forResource: "ChatModels", withExtension: "momd")! - - private init() {} -} // MARK: - Services extension Container { From 390a489c8fa3647a03b9ea1a7af2de5faff84fb2 Mon Sep 17 00:00:00 2001 From: Pavel Anokhov Date: Wed, 9 May 2018 21:03:09 +0300 Subject: [PATCH 09/34] Fixes. --- Adamant/AppDelegate.swift | 31 +++++++++----- .../Assets/l18n/en.lproj/Localizable.strings | 3 ++ .../Assets/l18n/ru.lproj/Localizable.strings | Bin 43642 -> 44106 bytes .../BackgroundFetchService.swift | 2 +- .../NotificationsService.swift | 2 +- .../Services/AdamantNotificationService.swift | 35 +++++++++++++--- .../Services/ApiService/AnsApiService.swift | 39 ++++++++++++++++-- ...AdamantChatsProvider+backgroundFetch.swift | 4 +- ...antTransfersProvider+backgroundFetch.swift | 4 +- 9 files changed, 95 insertions(+), 25 deletions(-) diff --git a/Adamant/AppDelegate.swift b/Adamant/AppDelegate.swift index 0be06a269..6c5190ad2 100644 --- a/Adamant/AppDelegate.swift +++ b/Adamant/AppDelegate.swift @@ -36,10 +36,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate { var container: Container! // MARK: Dependencies - weak var ansApiService: AnsApiService? - weak var accountService: AccountService? - weak var notificationService: NotificationsService? - weak var dialogService: DialogService? + var ansApiService: AnsApiService! + var accountService: AccountService! + var notificationService: NotificationsService! // MARK: - Lifecycle @@ -50,6 +49,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { accountService = container.resolve(AccountService.self) notificationService = container.resolve(NotificationsService.self) + ansApiService = AnsApiService(ansApi: AdamantResources.ans) // MARK: 2. Init UI window = UIWindow(frame: UIScreen.main.bounds) @@ -163,8 +163,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate { // MARK: Notifications func applicationDidBecomeActive(_ application: UIApplication) { - if accountService?.account != nil { - notificationService?.removeAllDeliveredNotifications() + if accountService.account != nil { + notificationService.removeAllDeliveredNotifications() } if let connection = container.resolve(ReachabilityMonitor.self)?.connection { @@ -188,23 +188,34 @@ extension AppDelegate { // } func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { + guard let address = accountService.account?.address else { + print("Trying to register with no user logged") + UIApplication.shared.unregisterForRemoteNotifications() + return + } + let token = deviceToken.map { String(format: "%02.2hhx", $0) }.joined() - ansApiService?.register(token: token) { [weak self] result in + ansApiService?.register(token: token, address: address) { [weak self] result in switch result { case .success: return case .failure(let error): self?.notificationService?.setNotificationsMode(.disabled, completion: nil) - self?.dialogService?.showError(withMessage: String.localizedStringWithFormat(String.adamantLocalized.notifications.registerRemotesError, error.localizedDescription)) + if let service = self?.container.resolve(DialogService.self) { + service.showError(withMessage: String.localizedStringWithFormat(String.adamantLocalized.notifications.registerRemotesError, error.localizedDescription)) + } } } } func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) { notificationService?.setNotificationsMode(.disabled, completion: nil) - dialogService?.showError(withMessage: String.localizedStringWithFormat(String.adamantLocalized.notifications.registerRemotesError, error.localizedDescription)) + + if let service = container.resolve(DialogService.self) { + service.showError(withMessage: String.localizedStringWithFormat(String.adamantLocalized.notifications.registerRemotesError, error.localizedDescription)) + } } } @@ -234,7 +245,7 @@ extension AppDelegate { for service in services { group.enter() - service.fetchBackgroundData(notificationService: notificationsService) { result in + service.fetchBackgroundData(notificationsService: notificationsService) { result in defer { group.leave() } diff --git a/Adamant/Assets/l18n/en.lproj/Localizable.strings b/Adamant/Assets/l18n/en.lproj/Localizable.strings index 57020292b..40b09acb0 100644 --- a/Adamant/Assets/l18n/en.lproj/Localizable.strings +++ b/Adamant/Assets/l18n/en.lproj/Localizable.strings @@ -242,6 +242,9 @@ /* Notifications: Send new transfers notifications */ "Notifications.Row.Transfers" = "Transfers"; +/* Notifications: Something went wrong while registering remote notifications. %@ for description */ +"NotificationsService.Error.RegistrationRemotesFormat" = "Registration in ANS failed. Please, try again later. Reason %@"; + /* Notifications: New message notification title */ "NotificationsService.NewMessage.Title" = "New Message"; diff --git a/Adamant/Assets/l18n/ru.lproj/Localizable.strings b/Adamant/Assets/l18n/ru.lproj/Localizable.strings index aa2d5b53852c9a12de33e7b45ca54185b299a5c9..0e3f9da069be95ce9b714a3dc724069dd6de9fde 100644 GIT binary patch delta 314 zcmex$h3V7{rVanH!-E;}8FCp?8A=#37&0017}6OO7|MbCJfH}ORRolWiGoz*0Ob^b zqNzZYnGD51RjEK8OeaV@7pS9TvS5zv$}>Y6vy} diff --git a/Adamant/ServiceProtocols/BackgroundFetchService.swift b/Adamant/ServiceProtocols/BackgroundFetchService.swift index 9c82c048d..002d10a66 100644 --- a/Adamant/ServiceProtocols/BackgroundFetchService.swift +++ b/Adamant/ServiceProtocols/BackgroundFetchService.swift @@ -15,6 +15,6 @@ enum FetchResult { } protocol BackgroundFetchService { - func fetchBackgroundData(notificationService: NotificationsService, completion: @escaping (FetchResult) -> Void) + func fetchBackgroundData(notificationsService: NotificationsService, completion: @escaping (FetchResult) -> Void) func dropStateData() } diff --git a/Adamant/ServiceProtocols/NotificationsService.swift b/Adamant/ServiceProtocols/NotificationsService.swift index 70cb8feb4..5f4812511 100644 --- a/Adamant/ServiceProtocols/NotificationsService.swift +++ b/Adamant/ServiceProtocols/NotificationsService.swift @@ -18,7 +18,7 @@ extension String.adamantLocalized { static let newTransferTitle = NSLocalizedString("NotificationsService.NewTransfer.Title", comment: "Notifications: New transfer transaction title") static let newTransferBody = NSLocalizedString("NotificationsService.NewTransfer.BodyFormat", comment: "Notifications: New transfer notification body. Using %d for amount") - static let registerRemotesError = NSLocalizedString("NotificationsService.Error.RegistrationRemotesFormat", comment: "Notifications: Something went wrong with registration remote notifications. %@ for description") + static let registerRemotesError = NSLocalizedString("NotificationsService.Error.RegistrationRemotesFormat", comment: "Notifications: while registering remote notifications. %@ for description") private init() {} } diff --git a/Adamant/Services/AdamantNotificationService.swift b/Adamant/Services/AdamantNotificationService.swift index 0833ed866..234a0b747 100644 --- a/Adamant/Services/AdamantNotificationService.swift +++ b/Adamant/Services/AdamantNotificationService.swift @@ -65,8 +65,7 @@ extension AdamantNotificationsService { func setNotificationsMode(_ mode: NotificationsMode, completion: ((NotificationsServiceResult) -> Void)?) { switch mode { case .disabled: - UIApplication.shared.unregisterForRemoteNotifications() - UIApplication.shared.setMinimumBackgroundFetchInterval(UIApplicationBackgroundFetchIntervalNever) + AdamantNotificationsService.configureUIApplicationFor(mode: mode) securedStore.remove(StoreKey.notificationsService.notificationsMode) notificationsMode = mode NotificationCenter.default.post(name: Notification.Name.AdamantNotificationService.notificationsModeChanged, object: self) @@ -80,8 +79,7 @@ extension AdamantNotificationsService { return } - UIApplication.shared.unregisterForRemoteNotifications() - UIApplication.shared.setMinimumBackgroundFetchInterval(UIApplicationBackgroundFetchIntervalMinimum) + 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) @@ -95,8 +93,7 @@ extension AdamantNotificationsService { return } - UIApplication.shared.registerForRemoteNotifications() - UIApplication.shared.setMinimumBackgroundFetchInterval(UIApplicationBackgroundFetchIntervalNever) + 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) @@ -121,6 +118,32 @@ extension AdamantNotificationsService { } } } + + private static func configureUIApplicationFor(mode: NotificationsMode) { + let callback = { + switch mode { + case .disabled: + UIApplication.shared.unregisterForRemoteNotifications() + UIApplication.shared.setMinimumBackgroundFetchInterval(UIApplicationBackgroundFetchIntervalNever) + + case .backgroundFetch: + UIApplication.shared.unregisterForRemoteNotifications() + UIApplication.shared.setMinimumBackgroundFetchInterval(UIApplicationBackgroundFetchIntervalMinimum) + + case .push: + UIApplication.shared.registerForRemoteNotifications() + UIApplication.shared.setMinimumBackgroundFetchInterval(UIApplicationBackgroundFetchIntervalNever) + } + } + + if Thread.isMainThread { + callback() + } else { + DispatchQueue.main.sync { + callback() + } + } + } } diff --git a/Adamant/Services/ApiService/AnsApiService.swift b/Adamant/Services/ApiService/AnsApiService.swift index 4e3b6903b..5b717bd32 100644 --- a/Adamant/Services/ApiService/AnsApiService.swift +++ b/Adamant/Services/ApiService/AnsApiService.swift @@ -15,13 +15,46 @@ enum AnsApiServiceResult { } class AnsApiService { - struct ApiCommants { - static let register = "/api/reg" + struct ApiCommands { + static let register = "/api/devices/register" private init() {} } - func register(token: String, completion: @escaping (AnsApiServiceResult) -> Void) { + // MARK: Properties + + let ansApi: URL + var defaultResponseDispatchQueue = DispatchQueue(label: "com.adamant.ans-queue", qos: .utility, attributes: [.concurrent]) + + init(ansApi: URL) { + self.ansApi = ansApi + } + + func register(token: String, address: String, completion: @escaping (AnsApiServiceResult) -> Void) { + let parameters: [String: Any] = [ + "token": token, + "address": address + ] + + let headers = [ + "Content-Type": "application/json" + ] + + guard var components = URLComponents(url: ansApi, resolvingAgainstBaseURL: false) else { + fatalError("Parsing API URL failed: \(ansApi)") + } + + components.path = ApiCommands.register + let url = try! components.asURL() + Alamofire.request(url, method: .post, parameters: parameters, encoding: JSONEncoding.default, headers: headers).validate().responseData(queue: defaultResponseDispatchQueue) { response in + switch response.result { + case .success: + completion(.success) + + case .failure(let error): + completion(.failure(.networkError(error: error))) + } + } } } diff --git a/Adamant/Services/DataProviders/AdamantChatsProvider+backgroundFetch.swift b/Adamant/Services/DataProviders/AdamantChatsProvider+backgroundFetch.swift index 5ce97ac27..8a3a6bf3a 100644 --- a/Adamant/Services/DataProviders/AdamantChatsProvider+backgroundFetch.swift +++ b/Adamant/Services/DataProviders/AdamantChatsProvider+backgroundFetch.swift @@ -9,7 +9,7 @@ import Foundation extension AdamantChatsProvider: BackgroundFetchService { - func fetchBackgroundData(notificationService: NotificationsService, completion: @escaping (FetchResult) -> Void) { + func fetchBackgroundData(notificationsService: NotificationsService, completion: @escaping (FetchResult) -> Void) { guard let securedStore = self.securedStore, let address = securedStore.get(StoreKey.chatProvider.address) else { completion(.failed) @@ -45,7 +45,7 @@ extension AdamantChatsProvider: BackgroundFetchService { securedStore.set(String(newLastHeight), for: StoreKey.chatProvider.notifiedLastHeight) } - notificationService.showNotification(title: String.adamantLocalized.notifications.newMessageTitle, body: String.localizedStringWithFormat(String.adamantLocalized.notifications.newMessageBody, total + notifiedCount), type: .newMessages(count: total)) + notificationsService.showNotification(title: String.adamantLocalized.notifications.newMessageTitle, body: String.localizedStringWithFormat(String.adamantLocalized.notifications.newMessageBody, total + notifiedCount), type: .newMessages(count: total)) completion(.newData) } else { diff --git a/Adamant/Services/DataProviders/AdamantTransfersProvider+backgroundFetch.swift b/Adamant/Services/DataProviders/AdamantTransfersProvider+backgroundFetch.swift index a6742ff28..0c303d42f 100644 --- a/Adamant/Services/DataProviders/AdamantTransfersProvider+backgroundFetch.swift +++ b/Adamant/Services/DataProviders/AdamantTransfersProvider+backgroundFetch.swift @@ -9,7 +9,7 @@ import Foundation extension AdamantTransfersProvider: BackgroundFetchService { - func fetchBackgroundData(notificationService: NotificationsService, completion: @escaping (FetchResult) -> Void) { + func fetchBackgroundData(notificationsService: NotificationsService, completion: @escaping (FetchResult) -> Void) { guard let securedStore = self.securedStore, let address = securedStore.get(StoreKey.transfersProvider.address) else { completion(.failed) @@ -47,7 +47,7 @@ extension AdamantTransfersProvider: BackgroundFetchService { securedStore.set(String(newLastHeight), for: StoreKey.transfersProvider.notifiedLastHeight) } - notificationService.showNotification(title: String.adamantLocalized.notifications.newTransferTitle, body: String.localizedStringWithFormat(String.adamantLocalized.notifications.newTransferBody, total + notifiedCount), type: .newTransactions(count: total)) + notificationsService.showNotification(title: String.adamantLocalized.notifications.newTransferTitle, body: String.localizedStringWithFormat(String.adamantLocalized.notifications.newTransferBody, total + notifiedCount), type: .newTransactions(count: total)) completion(.newData) } else { From 3940f2cc38bc99f03247773dafcf80fa833371e9 Mon Sep 17 00:00:00 2001 From: Pavel Anokhov Date: Thu, 17 May 2018 17:25:09 +0300 Subject: [PATCH 10/34] Some updates. --- Adamant.xcodeproj/project.pbxproj | 10 +++++++++- .../AnsApiService.swift | 7 +++++++ Adamant/Stories/Chats/ChatTableViewCell.xib | 4 ++-- 3 files changed, 18 insertions(+), 3 deletions(-) rename Adamant/Services/{ApiService => AdamantServices}/AnsApiService.swift (87%) diff --git a/Adamant.xcodeproj/project.pbxproj b/Adamant.xcodeproj/project.pbxproj index de217ca00..7f50eb5d2 100644 --- a/Adamant.xcodeproj/project.pbxproj +++ b/Adamant.xcodeproj/project.pbxproj @@ -441,6 +441,7 @@ E913C9061FFFA92E001A83F7 /* Services */ = { isa = PBXGroup; children = ( + E965A52E20ADC6080041A3EA /* AdamantServices */, E9CAE8D02018AA5000345E76 /* ApiService */, E9B3D39F201FA2090019EB36 /* DataProviders */, E9E7CD922002740500DFC4DB /* AdamantAccountService.swift */, @@ -623,6 +624,14 @@ path = Parsing; sourceTree = ""; }; + E965A52E20ADC6080041A3EA /* AdamantServices */ = { + isa = PBXGroup; + children = ( + E961E94C20A1E203005C8872 /* AnsApiService.swift */, + ); + path = AdamantServices; + sourceTree = ""; + }; E982F69820235AF000566AC7 /* Settings */ = { isa = PBXGroup; children = ( @@ -669,7 +678,6 @@ E9CAE8D32018AC1800345E76 /* AdamantApi+Keys.swift */, E9CAE8D52018AC5300345E76 /* AdamantApi+Transactions.swift */, E9CAE8D72018ACA700345E76 /* AdamantApi+Transfers.swift */, - E961E94C20A1E203005C8872 /* AnsApiService.swift */, ); path = ApiService; sourceTree = ""; diff --git a/Adamant/Services/ApiService/AnsApiService.swift b/Adamant/Services/AdamantServices/AnsApiService.swift similarity index 87% rename from Adamant/Services/ApiService/AnsApiService.swift rename to Adamant/Services/AdamantServices/AnsApiService.swift index 5b717bd32..1998cee95 100644 --- a/Adamant/Services/ApiService/AnsApiService.swift +++ b/Adamant/Services/AdamantServices/AnsApiService.swift @@ -15,6 +15,13 @@ enum AnsApiServiceResult { } class AnsApiService { + + /// Fallback registration ANS address + static private let ansAccount = ( + address: "U10629337621822775991", + publicKey: "188b24bd116a556ac8ba905bbbdaa16e237dfb14269f5a4f9a26be77537d977c" + ) + struct ApiCommands { static let register = "/api/devices/register" diff --git a/Adamant/Stories/Chats/ChatTableViewCell.xib b/Adamant/Stories/Chats/ChatTableViewCell.xib index 13a453117..7f0c5f443 100644 --- a/Adamant/Stories/Chats/ChatTableViewCell.xib +++ b/Adamant/Stories/Chats/ChatTableViewCell.xib @@ -1,11 +1,11 @@ - + - + From a7bc310500b0e387a9567ce14fc749cad280c340 Mon Sep 17 00:00:00 2001 From: Anton B Date: Thu, 24 May 2018 12:59:10 +0300 Subject: [PATCH 11/34] Servers list Add servers list Add random server selecting Add server status check --- .../ApiService/AdamantApiService.swift | 46 ++++++++++++++++++- Adamant/SwinjectDependencies.swift | 19 ++++++-- 2 files changed, 58 insertions(+), 7 deletions(-) diff --git a/Adamant/Services/ApiService/AdamantApiService.swift b/Adamant/Services/ApiService/AdamantApiService.swift index f1d30783f..3f889070c 100644 --- a/Adamant/Services/ApiService/AdamantApiService.swift +++ b/Adamant/Services/ApiService/AdamantApiService.swift @@ -54,7 +54,8 @@ class AdamantApiService: ApiService { // MARK: - Properties - let apiUrl: URL + var apiUrl: URL + let apiUrls: [String] var defaultResponseDispatchQueue = DispatchQueue(label: "com.adamant.response-queue", qos: .utility, attributes: [.concurrent]) @@ -62,8 +63,25 @@ class AdamantApiService: ApiService { init(apiUrl: URL) { self.apiUrl = apiUrl + self.apiUrls = [apiUrl.absoluteString] // Temp } - + + init(apiUrls: [String]) { + self.apiUrls = apiUrls + self.apiUrl = URL(string: apiUrls[0])! // Temp + + self.newServerAddress() + } + + func newServerAddress() { + let randomIndex = Int(arc4random_uniform(UInt32(self.apiUrls.count))) + let url = self.apiUrls[randomIndex] + self.apiUrl = URL(string: url)! + + self.testServer { (isAlive) in + if isAlive == false { self.newServerAddress() } + } + } // MARK: - Tools @@ -122,4 +140,28 @@ class AdamantApiService: ApiService { return .serverError(error: error) } } + + // Test current server is it alive or not + func testServer(completion: @escaping (Bool) -> Void) { + // MARK: 1. Build endpoint + let endpoint: URL + do { + endpoint = try buildUrl(path: ApiCommands.Accounts.newAccount) + } catch { + completion(false) + return + } + + let headers = [ + "Content-Type": "application/json" + ] + + // MARK: 2. Send + sendRequest(url: endpoint, method: .post, encoding: .json, headers: headers) { (serverResponse: ApiServiceResult>) in + switch serverResponse { + case .success: completion(true) + case .failure: completion(false) + } + } + } } diff --git a/Adamant/SwinjectDependencies.swift b/Adamant/SwinjectDependencies.swift index 3ce08909b..8df772678 100644 --- a/Adamant/SwinjectDependencies.swift +++ b/Adamant/SwinjectDependencies.swift @@ -14,12 +14,21 @@ private struct AdamantResources { static let jsCore = Bundle.main.url(forResource: "adamant-core", withExtension: "js")! static let api = URL(string: "https://endless.adamant.im")! static let coreDataModel = Bundle.main.url(forResource: "ChatModels", withExtension: "momd")! - + + + static let servers = [ + "https://endless.adamant.im", +// "https://clown.adamant.im", +// "https://lake.adamant.im", + "https://fake.adamant.im" + ] + private init() {} } // MARK: - Services extension Container { + func registerAdamantServices() { // MARK: - Standalone services // MARK: AdamantCore @@ -68,9 +77,9 @@ extension Container { // MARK: ApiService self.register(ApiService.self) { r in - let service = AdamantApiService(apiUrl: AdamantResources.api) - service.adamantCore = r.resolve(AdamantCore.self)! - return service + let service = AdamantApiService(apiUrls: AdamantResources.servers) + service.adamantCore = r.resolve(AdamantCore.self)! + return service }.inObjectScope(.container) // MARK: AccountService @@ -127,7 +136,7 @@ extension Container { // MARK: ApiService // No need to init AdamantCore - self.register(ApiService.self) { r in AdamantApiService(apiUrl: AdamantResources.api)}.inObjectScope(.container) + self.register(ApiService.self) { r in AdamantApiService(apiUrls: AdamantResources.servers)}.inObjectScope(.container) // MARK: Notifications self.register(NotificationsService.self) { r in From d8144e7f26ce01f14ee7b119f44b4f1b8f8e9371 Mon Sep 17 00:00:00 2001 From: RealBonus Date: Thu, 24 May 2018 19:15:51 +0300 Subject: [PATCH 12/34] Update README.md Changes from pull request #6 (https://github.com/Adamant-im/Adamant-iOS/pull/6), but merged into develop branch, and fixed markup. --- README.md | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 751490da6..0e76f5dd7 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,18 @@ -# Adamant-iOS -Adamant Messenger - iOS native client +# iOS version of ADAMANT Messenger -https://adamant.im +It's a native iOS version of ADAMANT Messenger. Available at App Store *(now it's in TestFlight)*. You can use this repository to build your own ADAMANT iOS application. + +ADAMANT is the most secure and anonymous messenger, encrypted with Blockchain. + +Highlights: + +- The most secure and anonymous messenger (see comparison table) +- PWA version is also available https://msg.adamant.im/ +- Trusted. Open-source project. +- The only one which is Blockchain-powered +- Integrated token transfers + +Read more about ADAMANT at https://adamant.im ## Build Setup From ebe93db5aba00475a27c26f478ecb46c51ff6076 Mon Sep 17 00:00:00 2001 From: Pavel Anokhov Date: Fri, 25 May 2018 17:33:33 +0300 Subject: [PATCH 13/34] StateAsset JSCore updated --- Adamant.xcodeproj/project.pbxproj | 24 +++++++++++------ Adamant/Helpers/JSModels.swift | 27 ++++++++++++++++--- .../Models/{Chat.swift => ChatAsset.swift} | 4 +-- Adamant/Models/NormalizedTransaction.swift | 19 ++++++++++--- Adamant/Models/StateAsset.swift | 23 ++++++++++++++++ Adamant/Models/StateType.swift | 13 +++++++++ Adamant/Models/TransactionAsset.swift | 8 +++--- Adamant/Models/TransactionType.swift | 1 + Adamant/ServiceProtocols/AdamantCore.swift | 13 ++++++++- Adamant/Services/JSAdamantCore.swift | 11 ++++---- AdamantTests/Parsing/ParsingModelsTests.swift | 2 +- 11 files changed, 116 insertions(+), 29 deletions(-) rename Adamant/Models/{Chat.swift => ChatAsset.swift} (91%) create mode 100644 Adamant/Models/StateAsset.swift create mode 100644 Adamant/Models/StateType.swift diff --git a/Adamant.xcodeproj/project.pbxproj b/Adamant.xcodeproj/project.pbxproj index 7f50eb5d2..4f082758b 100644 --- a/Adamant.xcodeproj/project.pbxproj +++ b/Adamant.xcodeproj/project.pbxproj @@ -86,9 +86,8 @@ E95F857A2007F0260070534A /* ServerResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = E95F85792007F0260070534A /* ServerResponse.swift */; }; E95F85802008C8D70070534A /* ChatsRoutes.swift in Sources */ = {isa = PBXBuildFile; fileRef = E95F857E2008C8D60070534A /* ChatsRoutes.swift */; }; E95F85852008CB3A0070534A /* ChatListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E95F85842008CB3A0070534A /* ChatListViewController.swift */; }; - E95F85872008FDBF0070534A /* Chat.swift in Sources */ = {isa = PBXBuildFile; fileRef = E95F85862008FDBF0070534A /* Chat.swift */; }; + E95F85872008FDBF0070534A /* ChatAsset.swift in Sources */ = {isa = PBXBuildFile; fileRef = E95F85862008FDBF0070534A /* ChatAsset.swift */; }; E95F8589200900B10070534A /* ChatType.swift in Sources */ = {isa = PBXBuildFile; fileRef = E95F8588200900B10070534A /* ChatType.swift */; }; - E95F858B200931410070534A /* TransactionAsset.swift in Sources */ = {isa = PBXBuildFile; fileRef = E95F858A200931410070534A /* TransactionAsset.swift */; }; E95F85B3200954D00070534A /* ChatModels.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = E95F85B1200954D00070534A /* ChatModels.xcdatamodeld */; }; E95F85B7200A4D8F0070534A /* TestTools.swift in Sources */ = {isa = PBXBuildFile; fileRef = E95F85B6200A4D8F0070534A /* TestTools.swift */; }; E95F85BA200A4DC90070534A /* TransactionSend.json in Resources */ = {isa = PBXBuildFile; fileRef = E95F85B9200A4DC90070534A /* TransactionSend.json */; }; @@ -100,6 +99,9 @@ E95F85C7200A9B070070534A /* ChatTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = E95F85C5200A9B070070534A /* ChatTableViewCell.swift */; }; E95F85C8200A9B070070534A /* ChatTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = E95F85C6200A9B070070534A /* ChatTableViewCell.xib */; }; E961E94D20A1E203005C8872 /* AnsApiService.swift in Sources */ = {isa = PBXBuildFile; fileRef = E961E94C20A1E203005C8872 /* AnsApiService.swift */; }; + E965A53220B82C850041A3EA /* StateType.swift in Sources */ = {isa = PBXBuildFile; fileRef = E965A53120B82C850041A3EA /* StateType.swift */; }; + E965A53420B833A00041A3EA /* StateAsset.swift in Sources */ = {isa = PBXBuildFile; fileRef = E965A53320B833A00041A3EA /* StateAsset.swift */; }; + E965A53620B8370C0041A3EA /* TransactionAsset.swift in Sources */ = {isa = PBXBuildFile; fileRef = E965A53520B8370C0041A3EA /* TransactionAsset.swift */; }; E9722066201F42BB004F2AAD /* CoreDataStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9722065201F42BB004F2AAD /* CoreDataStack.swift */; }; E9722068201F42CC004F2AAD /* InMemoryCoreDataStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9722067201F42CC004F2AAD /* InMemoryCoreDataStack.swift */; }; E972206B201F44CA004F2AAD /* TransfersProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = E972206A201F44CA004F2AAD /* TransfersProvider.swift */; }; @@ -273,9 +275,8 @@ E95F85792007F0260070534A /* ServerResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerResponse.swift; sourceTree = ""; }; E95F857E2008C8D60070534A /* ChatsRoutes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatsRoutes.swift; sourceTree = ""; }; E95F85842008CB3A0070534A /* ChatListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatListViewController.swift; sourceTree = ""; }; - E95F85862008FDBF0070534A /* Chat.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Chat.swift; sourceTree = ""; }; + E95F85862008FDBF0070534A /* ChatAsset.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatAsset.swift; sourceTree = ""; }; E95F8588200900B10070534A /* ChatType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatType.swift; sourceTree = ""; }; - E95F858A200931410070534A /* TransactionAsset.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionAsset.swift; sourceTree = ""; }; E95F85B2200954D00070534A /* ChatModels.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = ChatModels.xcdatamodel; sourceTree = ""; }; E95F85B6200A4D8F0070534A /* TestTools.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestTools.swift; sourceTree = ""; }; E95F85B9200A4DC90070534A /* TransactionSend.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = TransactionSend.json; sourceTree = ""; }; @@ -287,6 +288,9 @@ E95F85C5200A9B070070534A /* ChatTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatTableViewCell.swift; sourceTree = ""; }; E95F85C6200A9B070070534A /* ChatTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ChatTableViewCell.xib; sourceTree = ""; }; E961E94C20A1E203005C8872 /* AnsApiService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnsApiService.swift; sourceTree = ""; }; + E965A53120B82C850041A3EA /* StateType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StateType.swift; sourceTree = ""; }; + E965A53320B833A00041A3EA /* StateAsset.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StateAsset.swift; sourceTree = ""; }; + E965A53520B8370C0041A3EA /* TransactionAsset.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionAsset.swift; sourceTree = ""; }; E9722065201F42BB004F2AAD /* CoreDataStack.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreDataStack.swift; sourceTree = ""; }; E9722067201F42CC004F2AAD /* InMemoryCoreDataStack.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InMemoryCoreDataStack.swift; sourceTree = ""; }; E972206A201F44CA004F2AAD /* TransfersProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransfersProvider.swift; sourceTree = ""; }; @@ -462,13 +466,15 @@ isa = PBXGroup; children = ( E91947B320002809001362F8 /* Account.swift */, - E95F85862008FDBF0070534A /* Chat.swift */, + E95F85862008FDBF0070534A /* ChatAsset.swift */, E95F8588200900B10070534A /* ChatType.swift */, + E965A53320B833A00041A3EA /* StateAsset.swift */, + E965A53120B82C850041A3EA /* StateType.swift */, E95F85682006AB9D0070534A /* NormalizedTransaction.swift */, E9E7CDC920040CC200DFC4DB /* Transaction.swift */, - E95F858A200931410070534A /* TransactionAsset.swift */, E9E7CDCB20040FDC00DFC4DB /* TransactionType.swift */, E9393FA92055D03300EE6F30 /* AdamantMessage.swift */, + E965A53520B8370C0041A3EA /* TransactionAsset.swift */, ); path = Models; sourceTree = ""; @@ -990,7 +996,6 @@ E9FAE5E2203ED1AE008D3A6B /* ShareQrViewController.swift in Sources */, E94E7B06205D48B20042B639 /* TransactionsRoutes.swift in Sources */, E95F85B3200954D00070534A /* ChatModels.xcdatamodeld in Sources */, - E95F858B200931410070534A /* TransactionAsset.swift in Sources */, E90A494B204D9EB8009F6A65 /* AdamantAuthentication.swift in Sources */, E9215973206119FB0000CA5C /* ReachabilityMonitor.swift in Sources */, E9E7CDAC2002AFA500DFC4DB /* AccountViewController.swift in Sources */, @@ -999,7 +1004,7 @@ E905D39F204C281400DDB504 /* LoginViewController.swift in Sources */, E9150B9C2066DA210065A985 /* BaseTransaction+CoreDataProperties.swift in Sources */, E9E7CDB52002BA6900DFC4DB /* SwinjectedRouter.swift in Sources */, - E95F85872008FDBF0070534A /* Chat.swift in Sources */, + E95F85872008FDBF0070534A /* ChatAsset.swift in Sources */, E9393FAA2055D03300EE6F30 /* AdamantMessage.swift in Sources */, E90A494D204DA932009F6A65 /* LocalAuthentication.swift in Sources */, E95F856F2007B61D0070534A /* GetPublicKeyResponse.swift in Sources */, @@ -1044,6 +1049,8 @@ E95F857A2007F0260070534A /* ServerResponse.swift in Sources */, E9A174B32057EC47003667CD /* BackgroundFetchService.swift in Sources */, E9E7CDBE2003AEFB00DFC4DB /* CellFactory.swift in Sources */, + E965A53220B82C850041A3EA /* StateType.swift in Sources */, + E965A53620B8370C0041A3EA /* TransactionAsset.swift in Sources */, E9061B97207501E40011F104 /* AdamantUserInfoKey.swift in Sources */, E9CAE8D62018AC5300345E76 /* AdamantApi+Transactions.swift in Sources */, E93D7ABE2052CEE1005D19DC /* NotificationsService.swift in Sources */, @@ -1056,6 +1063,7 @@ E93D7AC02052CF63005D19DC /* AdamantNotificationService.swift in Sources */, E93B0D762028B28E00126346 /* AdamantChatsProvider.swift in Sources */, E90A4943204C5ED6009F6A65 /* EurekaPassphraseRow.swift in Sources */, + E965A53420B833A00041A3EA /* StateAsset.swift in Sources */, E9393FA82055C92700EE6F30 /* Decimal+adamant.swift in Sources */, E95F8589200900B10070534A /* ChatType.swift in Sources */, E913C9081FFFA943001A83F7 /* AdamantCore.swift in Sources */, diff --git a/Adamant/Helpers/JSModels.swift b/Adamant/Helpers/JSModels.swift index 02a1dc302..034dee6e4 100644 --- a/Adamant/Helpers/JSModels.swift +++ b/Adamant/Helpers/JSModels.swift @@ -37,14 +37,12 @@ import JavaScriptCore @objc protocol JSAssetProtocol: JSExport { var chat: JSChat? { get set } + var store: JSStore? { get set } } @objc class JSAsset: NSObject, JSAssetProtocol { dynamic var chat: JSChat? - - init(chat: JSChat?) { - self.chat = chat - } + dynamic var store: JSStore? } @@ -69,6 +67,27 @@ import JavaScriptCore } +// MARK: - Store + +@objc protocol JSStoreProtocol: JSExport { + var key: String { get set } + var value: String { get set } + var type: Int { get set } +} + +@objc class JSStore: NSObject, JSStoreProtocol { + dynamic var key: String + dynamic var value: String + dynamic var type: Int + + init(key: String, value: String, type: Int) { + self.key = key + self.value = value + self.type = type + } +} + + // MARK: - Transaction @objc protocol JSTransactionProtocol: JSExport { diff --git a/Adamant/Models/Chat.swift b/Adamant/Models/ChatAsset.swift similarity index 91% rename from Adamant/Models/Chat.swift rename to Adamant/Models/ChatAsset.swift index 42681c26a..260d8f3af 100644 --- a/Adamant/Models/Chat.swift +++ b/Adamant/Models/ChatAsset.swift @@ -1,5 +1,5 @@ // -// Chat.swift +// ChatAsset.swift // Adamant // // Created by Anokhov Pavel on 12.01.2018. @@ -8,7 +8,7 @@ import Foundation -struct Chat: Codable { +struct ChatAsset: Codable { enum CodingKeys: String, CodingKey { case message, ownMessage = "own_message", type } diff --git a/Adamant/Models/NormalizedTransaction.swift b/Adamant/Models/NormalizedTransaction.swift index 777c86465..388a5856c 100644 --- a/Adamant/Models/NormalizedTransaction.swift +++ b/Adamant/Models/NormalizedTransaction.swift @@ -8,17 +8,30 @@ import Foundation -struct NormalizedTransaction { +struct NormalizedTransaction: SignableTransaction { let type: TransactionType let amount: Decimal let senderPublicKey: String let requesterPublicKey: String? let timestamp: UInt64 - let recipientId: String + let recipientId: String? let asset: TransactionAsset var date: Date { - return AdamantUtilities.decodeAdamantDate(timestamp: TimeInterval(timestamp)) + return AdamantUtilities.decodeAdamant(timestamp: TimeInterval(timestamp)) + } +} + +// Convinient init +extension NormalizedTransaction { + init(type: TransactionType, amount: Decimal, senderPublicKey: String, requesterPublicKey: String?, date: Date, recipientId: String?, asset: TransactionAsset) { + self.type = type + self.amount = amount + self.senderPublicKey = senderPublicKey + self.requesterPublicKey = requesterPublicKey + self.timestamp = UInt64(AdamantUtilities.encodeAdamant(date: date)) + self.recipientId = recipientId + self.asset = asset } } diff --git a/Adamant/Models/StateAsset.swift b/Adamant/Models/StateAsset.swift new file mode 100644 index 000000000..aa4eb199d --- /dev/null +++ b/Adamant/Models/StateAsset.swift @@ -0,0 +1,23 @@ +// +// StateAsset.swift +// Adamant +// +// Created by Anokhov Pavel on 25.05.2018. +// Copyright © 2018 Adamant. All rights reserved. +// + +import Foundation + +struct StateAsset: Codable { + let key: String + let value: String + let type: StateType +} + +/* JSON +"state": { + "value": "myValue", + "key": "myKey", + "type": 0 +} +*/ diff --git a/Adamant/Models/StateType.swift b/Adamant/Models/StateType.swift new file mode 100644 index 000000000..4b8f6402c --- /dev/null +++ b/Adamant/Models/StateType.swift @@ -0,0 +1,13 @@ +// +// StateType.swift +// Adamant +// +// Created by Anokhov Pavel on 25.05.2018. +// Copyright © 2018 Adamant. All rights reserved. +// + +import Foundation + +enum StateType: Int16, Codable { + case keyValue = 0 +} diff --git a/Adamant/Models/TransactionAsset.swift b/Adamant/Models/TransactionAsset.swift index 5d6f50b82..b3b00fdeb 100644 --- a/Adamant/Models/TransactionAsset.swift +++ b/Adamant/Models/TransactionAsset.swift @@ -2,15 +2,13 @@ // TransactionAsset.swift // Adamant // -// Created by Anokhov Pavel on 12.01.2018. +// Created by Anokhov Pavel on 25.05.2018. // Copyright © 2018 Adamant. All rights reserved. // import Foundation -/* - Позже сделаю это дело расширяемым с помощью extension -*/ struct TransactionAsset: Codable { - let chat: Chat? + let chat: ChatAsset? + let state: StateAsset? } diff --git a/Adamant/Models/TransactionType.swift b/Adamant/Models/TransactionType.swift index e7efbd5be..84e4dddd6 100644 --- a/Adamant/Models/TransactionType.swift +++ b/Adamant/Models/TransactionType.swift @@ -18,4 +18,5 @@ enum TransactionType: Int, Codable { case inTransfer case outTransfer case chatMessage + case state } diff --git a/Adamant/ServiceProtocols/AdamantCore.swift b/Adamant/ServiceProtocols/AdamantCore.swift index 07fd4340e..0d700f0d0 100644 --- a/Adamant/ServiceProtocols/AdamantCore.swift +++ b/Adamant/ServiceProtocols/AdamantCore.swift @@ -15,9 +15,20 @@ protocol AdamantCore: class { func generateNewPassphrase() -> String // MARK: - Signing transactions - func sign(transaction: NormalizedTransaction, senderId: String, keypair: Keypair) -> String? + func sign(transaction: SignableTransaction, senderId: String, keypair: Keypair) -> String? // MARK: - Encoding messages func encodeMessage(_ message: String, recipientPublicKey: String, privateKey: String) -> (message: String, nonce: String)? func decodeMessage(rawMessage: String, rawNonce: String, senderPublicKey: String, privateKey: String) -> String? } + +protocol SignableTransaction { + var type: TransactionType { get } + var amount: Decimal { get } + var senderPublicKey: String { get } + var requesterPublicKey: String? { get } + var timestamp: UInt64 { get } + var recipientId: String? { get } + + var asset: TransactionAsset { get } +} diff --git a/Adamant/Services/JSAdamantCore.swift b/Adamant/Services/JSAdamantCore.swift index 067f626c9..66b248401 100644 --- a/Adamant/Services/JSAdamantCore.swift +++ b/Adamant/Services/JSAdamantCore.swift @@ -256,12 +256,13 @@ extension JSAdamantCore { // MARK: - Transactions extension JSAdamantCore { - func sign(transaction t: NormalizedTransaction, senderId: String, keypair: Keypair) -> String? { - let asset: JSAsset + func sign(transaction t: SignableTransaction, senderId: String, keypair: Keypair) -> String? { + let asset = JSAsset() if let chat = t.asset.chat { - asset = JSAsset(chat: JSChat(type: Int(chat.type.rawValue), message: chat.message, own_message: chat.ownMessage)) - } else { - asset = JSAsset(chat: nil) + asset.chat = JSChat(type: Int(chat.type.rawValue), message: chat.message, own_message: chat.ownMessage) + } + if let store = t.asset.state { + asset.store = JSStore(key: store.key, value: store.value, type: Int(store.type.rawValue)) } let jsTransaction = JSTransaction(id: 0, diff --git a/AdamantTests/Parsing/ParsingModelsTests.swift b/AdamantTests/Parsing/ParsingModelsTests.swift index 29864f460..ce8064877 100644 --- a/AdamantTests/Parsing/ParsingModelsTests.swift +++ b/AdamantTests/Parsing/ParsingModelsTests.swift @@ -71,7 +71,7 @@ class ParsingModelsTests: XCTestCase { } func testChat() { - let c: Chat = TestTools.LoadJsonAndDecode(filename: "Chat") + let c: AssetChat = TestTools.LoadJsonAndDecode(filename: "Chat") XCTAssertEqual(c.message, "7b7b3802f1d081e10624a373628fd0ba57e9348a7bca196c7511b05403a10611e3b4cf8b37cb9858f7f52cd5") XCTAssertEqual(c.ownMessage, "f4f7972f735997b4c2014d87cb491bb156f9cc4d0404cb9c") From f86db5899d4cf6a6459a6d5cd279cccb7f2eb5b2 Mon Sep 17 00:00:00 2001 From: Pavel Anokhov Date: Fri, 25 May 2018 17:36:18 +0300 Subject: [PATCH 14/34] AdamantUtilities.decodeAdamant(timestamp:) and encodeAdamant(date:) --- Adamant/Models/Transaction.swift | 2 +- Adamant/Utilities/AdamantUtilities.swift | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Adamant/Models/Transaction.swift b/Adamant/Models/Transaction.swift index ce9bcd386..0c639a37e 100644 --- a/Adamant/Models/Transaction.swift +++ b/Adamant/Models/Transaction.swift @@ -76,7 +76,7 @@ extension Transaction: Decodable { let fee = try container.decode(Decimal.self, forKey: .fee) self.fee = fee.shiftedFromAdamant() - self.date = AdamantUtilities.decodeAdamantDate(timestamp: TimeInterval(self.timestamp)) + self.date = AdamantUtilities.decodeAdamant(timestamp: TimeInterval(self.timestamp)) } } diff --git a/Adamant/Utilities/AdamantUtilities.swift b/Adamant/Utilities/AdamantUtilities.swift index 580229e6f..f6b081682 100644 --- a/Adamant/Utilities/AdamantUtilities.swift +++ b/Adamant/Utilities/AdamantUtilities.swift @@ -94,7 +94,11 @@ extension AdamantUtilities { // MARK: - Dates extension AdamantUtilities { - static func decodeAdamantDate(timestamp: TimeInterval) -> Date { + static func encodeAdamant(date: Date) -> TimeInterval { + return date.timeIntervalSince1970 - magicAdamantTimeInterval + } + + static func decodeAdamant(timestamp: TimeInterval) -> Date { return Date(timeIntervalSince1970: timestamp + magicAdamantTimeInterval) } From 8b4e31a62ebba25be02467e18acd07493e4544e7 Mon Sep 17 00:00:00 2001 From: Pavel Anokhov Date: Fri, 25 May 2018 17:37:30 +0300 Subject: [PATCH 15/34] ProcessTransactionResponse -> TransactionIdResponse --- Adamant.xcodeproj/project.pbxproj | 8 ++++---- ...nsactionResponse.swift => TransactionIdResponse.swift} | 2 +- Adamant/Services/ApiService/AdamantApi+Chats.swift | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) rename Adamant/ServerResponses/{ProcessTransactionResponse.swift => TransactionIdResponse.swift} (93%) diff --git a/Adamant.xcodeproj/project.pbxproj b/Adamant.xcodeproj/project.pbxproj index 4f082758b..0d39ea5bb 100644 --- a/Adamant.xcodeproj/project.pbxproj +++ b/Adamant.xcodeproj/project.pbxproj @@ -120,7 +120,7 @@ E9B3D3A9202082450019EB36 /* AdamantTransfersProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9B3D3A8202082450019EB36 /* AdamantTransfersProvider.swift */; }; E9C51ECF200E2D1100385EB7 /* FeeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9C51ECE200E2D1100385EB7 /* FeeTests.swift */; }; E9C51EED2011416E00385EB7 /* adamant-core.js in Resources */ = {isa = PBXBuildFile; fileRef = E9C51EEC2011416E00385EB7 /* adamant-core.js */; }; - E9C51EEF20139DC600385EB7 /* ProcessTransactionResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9C51EEE20139DC600385EB7 /* ProcessTransactionResponse.swift */; }; + E9C51EEF20139DC600385EB7 /* TransactionIdResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9C51EEE20139DC600385EB7 /* TransactionIdResponse.swift */; }; E9C51EF12013F18000385EB7 /* NewChatViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9C51EF02013F18000385EB7 /* NewChatViewController.swift */; }; E9CAE8D22018AA7700345E76 /* AdamantApi+Accounts.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9CAE8D12018AA7700345E76 /* AdamantApi+Accounts.swift */; }; E9CAE8D42018AC1800345E76 /* AdamantApi+Keys.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9CAE8D32018AC1800345E76 /* AdamantApi+Keys.swift */; }; @@ -309,7 +309,7 @@ E9B3D3A8202082450019EB36 /* AdamantTransfersProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdamantTransfersProvider.swift; sourceTree = ""; }; E9C51ECE200E2D1100385EB7 /* FeeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeeTests.swift; sourceTree = ""; }; E9C51EEC2011416E00385EB7 /* adamant-core.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = "adamant-core.js"; sourceTree = ""; }; - E9C51EEE20139DC600385EB7 /* ProcessTransactionResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProcessTransactionResponse.swift; sourceTree = ""; }; + E9C51EEE20139DC600385EB7 /* TransactionIdResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionIdResponse.swift; sourceTree = ""; }; E9C51EF02013F18000385EB7 /* NewChatViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewChatViewController.swift; sourceTree = ""; }; E9CAE8D12018AA7700345E76 /* AdamantApi+Accounts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AdamantApi+Accounts.swift"; sourceTree = ""; }; E9CAE8D32018AC1800345E76 /* AdamantApi+Keys.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AdamantApi+Keys.swift"; sourceTree = ""; }; @@ -541,7 +541,7 @@ isa = PBXGroup; children = ( E95F85792007F0260070534A /* ServerResponse.swift */, - E9C51EEE20139DC600385EB7 /* ProcessTransactionResponse.swift */, + E9C51EEE20139DC600385EB7 /* TransactionIdResponse.swift */, E95F856E2007B61D0070534A /* GetPublicKeyResponse.swift */, ); path = ServerResponses; @@ -1025,7 +1025,7 @@ E93B0D742028B21400126346 /* ChatsProvider.swift in Sources */, E9150BA12066DA210065A985 /* ChatTransaction+CoreDataClass.swift in Sources */, E9CAE8D42018AC1800345E76 /* AdamantApi+Keys.swift in Sources */, - E9C51EEF20139DC600385EB7 /* ProcessTransactionResponse.swift in Sources */, + E9C51EEF20139DC600385EB7 /* TransactionIdResponse.swift in Sources */, E9722066201F42BB004F2AAD /* CoreDataStack.swift in Sources */, E913C8F21FFFA51D001A83F7 /* AppDelegate.swift in Sources */, E905D39B2048A9BD00DDB504 /* KeychainStore.swift in Sources */, diff --git a/Adamant/ServerResponses/ProcessTransactionResponse.swift b/Adamant/ServerResponses/TransactionIdResponse.swift similarity index 93% rename from Adamant/ServerResponses/ProcessTransactionResponse.swift rename to Adamant/ServerResponses/TransactionIdResponse.swift index 9cf072c21..58ed08c33 100644 --- a/Adamant/ServerResponses/ProcessTransactionResponse.swift +++ b/Adamant/ServerResponses/TransactionIdResponse.swift @@ -8,7 +8,7 @@ import Foundation -class ProcessTransactionResponse: ServerResponse { +class TransactionIdResponse: ServerResponse { let transactionId: UInt64? required init(from decoder: Decoder) throws { diff --git a/Adamant/Services/ApiService/AdamantApi+Chats.swift b/Adamant/Services/ApiService/AdamantApi+Chats.swift index a9bb85c4b..a761782d9 100644 --- a/Adamant/Services/ApiService/AdamantApi+Chats.swift +++ b/Adamant/Services/ApiService/AdamantApi+Chats.swift @@ -105,7 +105,7 @@ extension AdamantApiService { "senderPublicKey": normalizedTransaction.senderPublicKey, "requesterPublicKey": normalizedTransaction.requesterPublicKey ?? NSNull(), "timestamp": normalizedTransaction.timestamp, - "recipientId": normalizedTransaction.recipientId, + "recipientId": normalizedTransaction.recipientId ?? NSNull(), "senderId": senderId, "signature": signature, "asset": [ @@ -122,7 +122,7 @@ extension AdamantApiService { ] // MARK: 5. Send - self.sendRequest(url: processEndpoin, method: .post, parameters: params, encoding: .json, headers: headers) { (serverResponse: ApiServiceResult) in + self.sendRequest(url: processEndpoin, method: .post, parameters: params, encoding: .json, headers: headers) { (serverResponse: ApiServiceResult) in switch serverResponse { case .success(let response): if let id = response.transactionId { From 9e69d6d08a01a327d27dbe3b1e2c44bc834f3cee Mon Sep 17 00:00:00 2001 From: Pavel Anokhov Date: Fri, 25 May 2018 17:37:49 +0300 Subject: [PATCH 16/34] AdamantApi+States States.store method. --- Adamant.xcodeproj/project.pbxproj | 4 + Adamant/ServiceProtocols/ApiService.swift | 6 ++ .../ApiService/AdamantApi+States.swift | 76 +++++++++++++++++++ 3 files changed, 86 insertions(+) create mode 100644 Adamant/Services/ApiService/AdamantApi+States.swift diff --git a/Adamant.xcodeproj/project.pbxproj b/Adamant.xcodeproj/project.pbxproj index 0d39ea5bb..bb8d63f01 100644 --- a/Adamant.xcodeproj/project.pbxproj +++ b/Adamant.xcodeproj/project.pbxproj @@ -99,6 +99,7 @@ E95F85C7200A9B070070534A /* ChatTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = E95F85C5200A9B070070534A /* ChatTableViewCell.swift */; }; E95F85C8200A9B070070534A /* ChatTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = E95F85C6200A9B070070534A /* ChatTableViewCell.xib */; }; E961E94D20A1E203005C8872 /* AnsApiService.swift in Sources */ = {isa = PBXBuildFile; fileRef = E961E94C20A1E203005C8872 /* AnsApiService.swift */; }; + E965A53020B594120041A3EA /* AdamantApi+States.swift in Sources */ = {isa = PBXBuildFile; fileRef = E965A52F20B594120041A3EA /* AdamantApi+States.swift */; }; E965A53220B82C850041A3EA /* StateType.swift in Sources */ = {isa = PBXBuildFile; fileRef = E965A53120B82C850041A3EA /* StateType.swift */; }; E965A53420B833A00041A3EA /* StateAsset.swift in Sources */ = {isa = PBXBuildFile; fileRef = E965A53320B833A00041A3EA /* StateAsset.swift */; }; E965A53620B8370C0041A3EA /* TransactionAsset.swift in Sources */ = {isa = PBXBuildFile; fileRef = E965A53520B8370C0041A3EA /* TransactionAsset.swift */; }; @@ -288,6 +289,7 @@ E95F85C5200A9B070070534A /* ChatTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatTableViewCell.swift; sourceTree = ""; }; E95F85C6200A9B070070534A /* ChatTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ChatTableViewCell.xib; sourceTree = ""; }; E961E94C20A1E203005C8872 /* AnsApiService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnsApiService.swift; sourceTree = ""; }; + E965A52F20B594120041A3EA /* AdamantApi+States.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AdamantApi+States.swift"; sourceTree = ""; }; E965A53120B82C850041A3EA /* StateType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StateType.swift; sourceTree = ""; }; E965A53320B833A00041A3EA /* StateAsset.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StateAsset.swift; sourceTree = ""; }; E965A53520B8370C0041A3EA /* TransactionAsset.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionAsset.swift; sourceTree = ""; }; @@ -684,6 +686,7 @@ E9CAE8D32018AC1800345E76 /* AdamantApi+Keys.swift */, E9CAE8D52018AC5300345E76 /* AdamantApi+Transactions.swift */, E9CAE8D72018ACA700345E76 /* AdamantApi+Transfers.swift */, + E965A52F20B594120041A3EA /* AdamantApi+States.swift */, ); path = ApiService; sourceTree = ""; @@ -1042,6 +1045,7 @@ E95F85852008CB3A0070534A /* ChatListViewController.swift in Sources */, E91947AC20001A9A001362F8 /* ApiService.swift in Sources */, E9150B9F2066DA210065A985 /* MessageTransaction+CoreDataClass.swift in Sources */, + E965A53020B594120041A3EA /* AdamantApi+States.swift in Sources */, E9150B982066DA210065A985 /* CoreDataAccount+CoreDataProperties.swift in Sources */, E9B3D3A1201FA26B0019EB36 /* AdamantAccountsProvider.swift in Sources */, E9FAE5DA203DBFEF008D3A6B /* Comparable+Clamped.swift in Sources */, diff --git a/Adamant/ServiceProtocols/ApiService.swift b/Adamant/ServiceProtocols/ApiService.swift index 98b9a56ce..a2b6376a0 100644 --- a/Adamant/ServiceProtocols/ApiService.swift +++ b/Adamant/ServiceProtocols/ApiService.swift @@ -78,6 +78,12 @@ protocol ApiService: class { func transferFunds(sender: String, recipient: String, amount: Decimal, keypair: Keypair, completion: @escaping (ApiServiceResult) -> Void) + // MARK: - States + + /// - Returns: Transaction ID + func store(key: String, value: String, type: StateType, sender: String, keypair: Keypair, completion: @escaping (ApiServiceResult) -> Void) + + // MARK: - Chats /// Get chat transactions (type 8) diff --git a/Adamant/Services/ApiService/AdamantApi+States.swift b/Adamant/Services/ApiService/AdamantApi+States.swift new file mode 100644 index 000000000..591ea7cd2 --- /dev/null +++ b/Adamant/Services/ApiService/AdamantApi+States.swift @@ -0,0 +1,76 @@ +// +// AdamantApi+States.swift +// Adamant +// +// Created by Anokhov Pavel on 23.05.2018. +// Copyright © 2018 Adamant. All rights reserved. +// + +import Foundation + +extension AdamantApiService.ApiCommands { + static let States = ( + root: "/api/states", + getTransactions: "/api/states/get", + store: "/api/states/store" + ) +} + +extension AdamantApiService { + func store(key: String, value: String, type: StateType, sender: String, keypair: Keypair, completion: @escaping (ApiServiceResult) -> Void) { + + // MARK: 1. Create and sign transaction + let asset = TransactionAsset(chat: nil, state: StateAsset(key: key, value: value, type: .keyValue)) + let transaction = NormalizedTransaction(type: .state, + amount: 0, + senderPublicKey: keypair.publicKey, + requesterPublicKey: nil, + date: Date(), + recipientId: nil, + asset: asset) + guard let signature = adamantCore.sign(transaction: transaction, senderId: sender, keypair: keypair) else { + completion(.failure(.internalError(message: "Failed to sign transaction", error: nil))) + return + } + + let params: [String: Any] = [ + "type": transaction.type.rawValue, + "amount": transaction.amount, + "senderPublicKey": transaction.senderPublicKey, + "senderId": sender, + "timestamp": transaction.timestamp, + "signature": signature, + "asset": transaction.asset + ] + + let headers = [ + "Content-Type": "application/json" + ] + + // MARK: 2. Build endpoints + let endpoint: URL + + do { + endpoint = try buildUrl(path: ApiCommands.States.store) + } catch { + let err = InternalError.endpointBuildFailed.apiServiceErrorWith(error: error) + completion(.failure(err)) + return + } + + // MARK: 3. Send + sendRequest(url: endpoint, method: .post, parameters: params, encoding: .json, headers: headers) { (serverResponse: ApiServiceResult) in + switch serverResponse { + case .success(let response): + if let id = response.transactionId { + completion(.success(id)) + } else { + completion(ApiServiceResult.success(0)) + } + + case .failure(let error): + completion(.failure(.networkError(error: error))) + } + } + } +} From 175eaf6ca5f7bffb0985c29912a16ef70914675e Mon Sep 17 00:00:00 2001 From: Pavel Anokhov Date: Sat, 26 May 2018 17:35:20 +0300 Subject: [PATCH 17/34] adamant-core.js fixed. store -> state --- Adamant/Assets/adamant-core.js | 42 +++++++++++++++++-- Adamant/Helpers/JSModels.swift | 8 ++-- .../TransactionIdResponse.swift | 2 +- .../ApiService/AdamantApi+States.swift | 23 ++++++---- Adamant/Services/JSAdamantCore.swift | 4 +- 5 files changed, 62 insertions(+), 17 deletions(-) diff --git a/Adamant/Assets/adamant-core.js b/Adamant/Assets/adamant-core.js index 187e68009..0f6cf48e0 100644 --- a/Adamant/Assets/adamant-core.js +++ b/Adamant/Assets/adamant-core.js @@ -42501,12 +42501,19 @@ class Adamant { var assetBytes = null switch (transaction.type) { - case 0: + case 0: // Send break - case 8: + + case 8: // Chat message assetBytes = this.chatGetBytes(transaction) assetSize = assetBytes.length break + + case 9: // State + assetBytes = this.statesGetBytes(transaction) + assetSize = assetBytes.length + break + default: alert('Not supported yet') } @@ -42578,7 +42585,7 @@ class Adamant { static transactionSign (trs, keypair) { var hash = Adamant.getHash(trs) - return Adamant.sign(hash, keypair).toString('hex') + return Adamant.sign(hash, keypair).toString('hex'); } static chatGetBytes (trs) { @@ -42596,6 +42603,7 @@ class Adamant { var bb = new ByteBuffer(4 + 4, true) bb.writeInt(trs.asset.chat.type) bb.flip() + buf = Buffer.concat([buf, Buffer.from(bb.toBuffer())]) } catch (e) { throw e @@ -42604,6 +42612,34 @@ class Adamant { return buf } + static statesGetBytes (trs) { + if (!trs.asset.state.value) { + return null; + } + var buf; + + try { + buf = Buffer.from([]); + var stateBuf = Buffer.from(trs.asset.state.value); + buf = Buffer.concat([buf, stateBuf]); + + if (trs.asset.state.key) { + var keyBuf = Buffer.from(trs.asset.state.key); + buf = Buffer.concat([buf, keyBuf]); + } + + var bb = new ByteBuffer(4 + 4, true); + bb.writeInt(trs.asset.state.type); + bb.flip(); + + buf = Buffer.concat([buf, Buffer.from(bb.toBuffer())]); + } catch (e) { + throw e; + } + + return buf; + } + /** * Creates a signature based on a hash and a keypair. * @implements {sodium} diff --git a/Adamant/Helpers/JSModels.swift b/Adamant/Helpers/JSModels.swift index 034dee6e4..73b34ce11 100644 --- a/Adamant/Helpers/JSModels.swift +++ b/Adamant/Helpers/JSModels.swift @@ -37,12 +37,12 @@ import JavaScriptCore @objc protocol JSAssetProtocol: JSExport { var chat: JSChat? { get set } - var store: JSStore? { get set } + var state: JSState? { get set } } @objc class JSAsset: NSObject, JSAssetProtocol { dynamic var chat: JSChat? - dynamic var store: JSStore? + dynamic var state: JSState? } @@ -69,13 +69,13 @@ import JavaScriptCore // MARK: - Store -@objc protocol JSStoreProtocol: JSExport { +@objc protocol JSStateProtocol: JSExport { var key: String { get set } var value: String { get set } var type: Int { get set } } -@objc class JSStore: NSObject, JSStoreProtocol { +@objc class JSState: NSObject, JSStateProtocol { dynamic var key: String dynamic var value: String dynamic var type: Int diff --git a/Adamant/ServerResponses/TransactionIdResponse.swift b/Adamant/ServerResponses/TransactionIdResponse.swift index 58ed08c33..5091ad889 100644 --- a/Adamant/ServerResponses/TransactionIdResponse.swift +++ b/Adamant/ServerResponses/TransactionIdResponse.swift @@ -16,7 +16,7 @@ class TransactionIdResponse: ServerResponse { let success = try container.decode(Bool.self, forKey: .success) let error = try? container.decode(String.self, forKey: .error) - if let idRaw = try? container.decode(String.self, forKey: CodingKeys.init(stringValue: "transactionId")!) { + if let idRaw = try? container.decode(String.self, forKey: CodingKeys(stringValue: "transactionId")!) { transactionId = UInt64(idRaw) } else { transactionId = nil diff --git a/Adamant/Services/ApiService/AdamantApi+States.swift b/Adamant/Services/ApiService/AdamantApi+States.swift index 591ea7cd2..5c3ce2bfe 100644 --- a/Adamant/Services/ApiService/AdamantApi+States.swift +++ b/Adamant/Services/ApiService/AdamantApi+States.swift @@ -34,13 +34,22 @@ extension AdamantApiService { } let params: [String: Any] = [ - "type": transaction.type.rawValue, - "amount": transaction.amount, - "senderPublicKey": transaction.senderPublicKey, - "senderId": sender, - "timestamp": transaction.timestamp, - "signature": signature, - "asset": transaction.asset + "transaction": [ + "type": transaction.type.rawValue, + "amount": transaction.amount, + "senderPublicKey": transaction.senderPublicKey, + "senderId": sender, + "timestamp": transaction.timestamp, + "signature": signature, + "recipientId": NSNull(), + "asset": [ + "state": [ + "key": key, + "value": value, + "type": type.rawValue + ] + ] + ] ] let headers = [ diff --git a/Adamant/Services/JSAdamantCore.swift b/Adamant/Services/JSAdamantCore.swift index 66b248401..7b361c51c 100644 --- a/Adamant/Services/JSAdamantCore.swift +++ b/Adamant/Services/JSAdamantCore.swift @@ -261,8 +261,8 @@ extension JSAdamantCore { if let chat = t.asset.chat { asset.chat = JSChat(type: Int(chat.type.rawValue), message: chat.message, own_message: chat.ownMessage) } - if let store = t.asset.state { - asset.store = JSStore(key: store.key, value: store.value, type: Int(store.type.rawValue)) + if let state = t.asset.state { + asset.state = JSState(key: state.key, value: state.value, type: Int(state.type.rawValue)) } let jsTransaction = JSTransaction(id: 0, From b406f6507ae7500a77cd2ee26ca7f44e0fd01156 Mon Sep 17 00:00:00 2001 From: Pavel Anokhov Date: Sat, 26 May 2018 19:02:57 +0300 Subject: [PATCH 18/34] AppDelegate: storing device token's hash, checking if it has changed and needed to be stored in blockchain. --- Adamant.xcodeproj/project.pbxproj | 2 + Adamant/AppDelegate.swift | 46 +++++++++++++------ .../Services/AdamantNotificationService.swift | 1 + Podfile | 1 + Podfile.lock | 6 ++- 5 files changed, 40 insertions(+), 16 deletions(-) diff --git a/Adamant.xcodeproj/project.pbxproj b/Adamant.xcodeproj/project.pbxproj index bb8d63f01..3b4acc332 100644 --- a/Adamant.xcodeproj/project.pbxproj +++ b/Adamant.xcodeproj/project.pbxproj @@ -910,6 +910,7 @@ inputPaths = ( "${SRCROOT}/Pods/Target Support Files/Pods-Adamant/Pods-Adamant-frameworks.sh", "${BUILT_PRODUCTS_DIR}/Alamofire/Alamofire.framework", + "${BUILT_PRODUCTS_DIR}/CryptoSwift/CryptoSwift.framework", "${BUILT_PRODUCTS_DIR}/EFQRCode/EFQRCode.framework", "${BUILT_PRODUCTS_DIR}/Eureka/Eureka.framework", "${BUILT_PRODUCTS_DIR}/FTIndicator/FTIndicator.framework", @@ -926,6 +927,7 @@ name = "[CP] Embed Pods Frameworks"; outputPaths = ( "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Alamofire.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/CryptoSwift.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/EFQRCode.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Eureka.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FTIndicator.framework", diff --git a/Adamant/AppDelegate.swift b/Adamant/AppDelegate.swift index 6c5190ad2..37a45c6a6 100644 --- a/Adamant/AppDelegate.swift +++ b/Adamant/AppDelegate.swift @@ -8,6 +8,7 @@ import UIKit import Swinject +import CryptoSwift extension String.adamantLocalized { struct tabItems { @@ -17,12 +18,20 @@ extension String.adamantLocalized { } } +extension StoreKey { + struct application { + static let deviceTokenHash = "app.deviceTokenHash" + + private init() {} + } +} + // MARK: Resources struct AdamantResources { // Storyboard static let jsCore = Bundle.main.url(forResource: "adamant-core", withExtension: "js")! static let api = URL(string: "https://endless.adamant.im")! - static let ans = URL(string: "")! + static let ans = URL(string: "https://192.168.1.69:44340")! static let coreDataModel = Bundle.main.url(forResource: "ChatModels", withExtension: "momd")! private init() {} @@ -36,7 +45,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate { var container: Container! // MARK: Dependencies - var ansApiService: AnsApiService! var accountService: AccountService! var notificationService: NotificationsService! @@ -49,8 +57,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate { accountService = container.resolve(AccountService.self) notificationService = container.resolve(NotificationsService.self) - ansApiService = AnsApiService(ansApi: AdamantResources.ans) - // MARK: 2. Init UI window = UIWindow(frame: UIScreen.main.bounds) window!.rootViewController = UITabBarController() @@ -183,12 +189,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate { // MARK: - Remote notifications extension AppDelegate { -// func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { -// print(userInfo) -// } - func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { - guard let address = accountService.account?.address else { + guard let address = accountService.account?.address, let keypair = accountService.keypair else { print("Trying to register with no user logged") UIApplication.shared.unregisterForRemoteNotifications() return @@ -196,23 +198,37 @@ extension AppDelegate { let token = deviceToken.map { String(format: "%02.2hhx", $0) }.joined() - ansApiService?.register(token: token, address: address) { [weak self] result in + // 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") + } + + let tokenHash = token.md5() + + if let savedHash = securedStore.get(StoreKey.application.deviceTokenHash), tokenHash == savedHash { + return + } else { + securedStore.set(tokenHash, for: StoreKey.application.deviceTokenHash) + } + + // Storing new token in blockchain + 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 switch result { case .success: return case .failure(let error): + print("Failed to store device token: \(error)") self?.notificationService?.setNotificationsMode(.disabled, completion: nil) - if let service = self?.container.resolve(DialogService.self) { - service.showError(withMessage: String.localizedStringWithFormat(String.adamantLocalized.notifications.registerRemotesError, error.localizedDescription)) - } } } } func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) { - notificationService?.setNotificationsMode(.disabled, completion: nil) - if let service = container.resolve(DialogService.self) { service.showError(withMessage: String.localizedStringWithFormat(String.adamantLocalized.notifications.registerRemotesError, error.localizedDescription)) } diff --git a/Adamant/Services/AdamantNotificationService.swift b/Adamant/Services/AdamantNotificationService.swift index 234a0b747..0e3df7406 100644 --- a/Adamant/Services/AdamantNotificationService.swift +++ b/Adamant/Services/AdamantNotificationService.swift @@ -51,6 +51,7 @@ class AdamantNotificationsService: NotificationsService { NotificationCenter.default.addObserver(forName: Notification.Name.AdamantAccountService.userLoggedOut, object: nil, queue: nil) { [weak self] _ in self?.setNotificationsMode(.disabled, completion: nil) + self?.securedStore.remove(StoreKey.notificationsService.notificationsMode) } } diff --git a/Podfile b/Podfile index ed9f1912a..afb89baf9 100644 --- a/Podfile +++ b/Podfile @@ -7,6 +7,7 @@ target 'Adamant' do pod 'Alamofire' # Network pod 'KeychainAccess' # Keychain pod 'RNCryptor' # Cryptor + pod 'CryptoSwift' # MD5 hash pod 'Swinject' # Dependency Injection pod 'ReachabilitySwift' # Network status pod 'Haring' # Markdown parser diff --git a/Podfile.lock b/Podfile.lock index 6945a7a13..85de429a3 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -1,5 +1,6 @@ PODS: - Alamofire (4.7.2) + - CryptoSwift (0.9.0) - EFQRCode (4.2.1) - Eureka (4.1.1) - FreakingSimpleRoundImageView (1.1) @@ -21,6 +22,7 @@ PODS: DEPENDENCIES: - Alamofire + - CryptoSwift - EFQRCode - Eureka - FreakingSimpleRoundImageView @@ -37,6 +39,7 @@ DEPENDENCIES: SPEC REPOS: https://github.com/CocoaPods/Specs.git: - Alamofire + - CryptoSwift - EFQRCode - Eureka - FreakingSimpleRoundImageView @@ -52,6 +55,7 @@ SPEC REPOS: SPEC CHECKSUMS: Alamofire: e4fa87002c137ba2d8d634d2c51fabcda0d5c223 + CryptoSwift: bca8c5b653dcc2d9734409242a070ff53bafac86 EFQRCode: f3fd67049faa07adb575495c05d72a34e407a940 Eureka: b88fb930e42c79f8c03c373d0fcdc28c1d6c50ed FreakingSimpleRoundImageView: 4a3a1cb1347beb247f8c63b5b5cae9d770b431ee @@ -65,6 +69,6 @@ SPEC CHECKSUMS: RNCryptor: c93d19029dcf7ff160aca0f24d6c9e7b0d82f664 Swinject: a1364b0f66c2736bb03c1c7cab54809e16df25da -PODFILE CHECKSUM: 2afa7e8b57ec3b78d5cea2c6ce51fd5f71e3c1f6 +PODFILE CHECKSUM: 12f69d43b1b41b997e7c698aa56457f71d9ade99 COCOAPODS: 1.5.0 From a77a8b43f1c04e6ff96b50c93219654c86bea437 Mon Sep 17 00:00:00 2001 From: Pavel Anokhov Date: Sat, 26 May 2018 19:34:08 +0300 Subject: [PATCH 19/34] AnsApiService deprecated and removed. --- Adamant.xcodeproj/project.pbxproj | 12 ---- Adamant/AppDelegate.swift | 2 - .../AdamantServices/AnsApiService.swift | 67 ------------------- 3 files changed, 81 deletions(-) delete mode 100644 Adamant/Services/AdamantServices/AnsApiService.swift diff --git a/Adamant.xcodeproj/project.pbxproj b/Adamant.xcodeproj/project.pbxproj index 3b4acc332..7b52b988c 100644 --- a/Adamant.xcodeproj/project.pbxproj +++ b/Adamant.xcodeproj/project.pbxproj @@ -98,7 +98,6 @@ E95F85C4200A540B0070534A /* Chat.json in Resources */ = {isa = PBXBuildFile; fileRef = E95F85C3200A540B0070534A /* Chat.json */; }; E95F85C7200A9B070070534A /* ChatTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = E95F85C5200A9B070070534A /* ChatTableViewCell.swift */; }; E95F85C8200A9B070070534A /* ChatTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = E95F85C6200A9B070070534A /* ChatTableViewCell.xib */; }; - E961E94D20A1E203005C8872 /* AnsApiService.swift in Sources */ = {isa = PBXBuildFile; fileRef = E961E94C20A1E203005C8872 /* AnsApiService.swift */; }; E965A53020B594120041A3EA /* AdamantApi+States.swift in Sources */ = {isa = PBXBuildFile; fileRef = E965A52F20B594120041A3EA /* AdamantApi+States.swift */; }; E965A53220B82C850041A3EA /* StateType.swift in Sources */ = {isa = PBXBuildFile; fileRef = E965A53120B82C850041A3EA /* StateType.swift */; }; E965A53420B833A00041A3EA /* StateAsset.swift in Sources */ = {isa = PBXBuildFile; fileRef = E965A53320B833A00041A3EA /* StateAsset.swift */; }; @@ -288,7 +287,6 @@ E95F85C3200A540B0070534A /* Chat.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = Chat.json; sourceTree = ""; }; E95F85C5200A9B070070534A /* ChatTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatTableViewCell.swift; sourceTree = ""; }; E95F85C6200A9B070070534A /* ChatTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ChatTableViewCell.xib; sourceTree = ""; }; - E961E94C20A1E203005C8872 /* AnsApiService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnsApiService.swift; sourceTree = ""; }; E965A52F20B594120041A3EA /* AdamantApi+States.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AdamantApi+States.swift"; sourceTree = ""; }; E965A53120B82C850041A3EA /* StateType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StateType.swift; sourceTree = ""; }; E965A53320B833A00041A3EA /* StateAsset.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StateAsset.swift; sourceTree = ""; }; @@ -447,7 +445,6 @@ E913C9061FFFA92E001A83F7 /* Services */ = { isa = PBXGroup; children = ( - E965A52E20ADC6080041A3EA /* AdamantServices */, E9CAE8D02018AA5000345E76 /* ApiService */, E9B3D39F201FA2090019EB36 /* DataProviders */, E9E7CD922002740500DFC4DB /* AdamantAccountService.swift */, @@ -632,14 +629,6 @@ path = Parsing; sourceTree = ""; }; - E965A52E20ADC6080041A3EA /* AdamantServices */ = { - isa = PBXGroup; - children = ( - E961E94C20A1E203005C8872 /* AnsApiService.swift */, - ); - path = AdamantServices; - sourceTree = ""; - }; E982F69820235AF000566AC7 /* Settings */ = { isa = PBXGroup; children = ( @@ -1024,7 +1013,6 @@ E9942B87203D9E5100C163AF /* EurekaQRRow.swift in Sources */, E93EFE13200D1156000BB482 /* ChatViewController.swift in Sources */, E9B3D39E201F99F40019EB36 /* DataProvider.swift in Sources */, - E961E94D20A1E203005C8872 /* AnsApiService.swift in Sources */, E9E7CDC02003AF6D00DFC4DB /* AdamantCellFactory.swift in Sources */, E91A063A209F05AA0018A102 /* NotificationsViewController.swift in Sources */, E93B0D742028B21400126346 /* ChatsProvider.swift in Sources */, diff --git a/Adamant/AppDelegate.swift b/Adamant/AppDelegate.swift index 37a45c6a6..0bdeba30f 100644 --- a/Adamant/AppDelegate.swift +++ b/Adamant/AppDelegate.swift @@ -28,10 +28,8 @@ extension StoreKey { // MARK: Resources struct AdamantResources { - // Storyboard static let jsCore = Bundle.main.url(forResource: "adamant-core", withExtension: "js")! static let api = URL(string: "https://endless.adamant.im")! - static let ans = URL(string: "https://192.168.1.69:44340")! static let coreDataModel = Bundle.main.url(forResource: "ChatModels", withExtension: "momd")! private init() {} diff --git a/Adamant/Services/AdamantServices/AnsApiService.swift b/Adamant/Services/AdamantServices/AnsApiService.swift deleted file mode 100644 index 1998cee95..000000000 --- a/Adamant/Services/AdamantServices/AnsApiService.swift +++ /dev/null @@ -1,67 +0,0 @@ -// -// AnsApiService.swift -// Adamant -// -// Created by Anokhov Pavel on 08.05.2018. -// Copyright © 2018 Adamant. All rights reserved. -// - -import Foundation -import Alamofire - -enum AnsApiServiceResult { - case success - case failure(ApiServiceError) -} - -class AnsApiService { - - /// Fallback registration ANS address - static private let ansAccount = ( - address: "U10629337621822775991", - publicKey: "188b24bd116a556ac8ba905bbbdaa16e237dfb14269f5a4f9a26be77537d977c" - ) - - struct ApiCommands { - static let register = "/api/devices/register" - - private init() {} - } - - // MARK: Properties - - let ansApi: URL - var defaultResponseDispatchQueue = DispatchQueue(label: "com.adamant.ans-queue", qos: .utility, attributes: [.concurrent]) - - init(ansApi: URL) { - self.ansApi = ansApi - } - - func register(token: String, address: String, completion: @escaping (AnsApiServiceResult) -> Void) { - let parameters: [String: Any] = [ - "token": token, - "address": address - ] - - let headers = [ - "Content-Type": "application/json" - ] - - guard var components = URLComponents(url: ansApi, resolvingAgainstBaseURL: false) else { - fatalError("Parsing API URL failed: \(ansApi)") - } - - components.path = ApiCommands.register - let url = try! components.asURL() - - Alamofire.request(url, method: .post, parameters: parameters, encoding: JSONEncoding.default, headers: headers).validate().responseData(queue: defaultResponseDispatchQueue) { response in - switch response.result { - case .success: - completion(.success) - - case .failure(let error): - completion(.failure(.networkError(error: error))) - } - } - } -} From 398d18be5a7502aefa4d622dfb488a6f7af9c3a1 Mon Sep 17 00:00:00 2001 From: Pavel Anokhov Date: Sat, 26 May 2018 19:35:03 +0300 Subject: [PATCH 20/34] AccountService: disable notifications on dropping saved user. SettingsViewController: hiding notification options. --- Adamant/Services/AdamantAccountService.swift | 2 + .../SettingsViewController+StayIn.swift | 40 +++++++++++++------ .../Settings/SettingsViewController.swift | 19 +++++++-- Adamant/SwinjectDependencies.swift | 1 + 4 files changed, 45 insertions(+), 17 deletions(-) diff --git a/Adamant/Services/AdamantAccountService.swift b/Adamant/Services/AdamantAccountService.swift index 931a3be61..a6b17e915 100644 --- a/Adamant/Services/AdamantAccountService.swift +++ b/Adamant/Services/AdamantAccountService.swift @@ -14,6 +14,7 @@ class AdamantAccountService { var apiService: ApiService! var adamantCore: AdamantCore! + var notificationsService: NotificationsService! var securedStore: SecuredStore! { didSet { securedStoreSemaphore.wait() @@ -134,6 +135,7 @@ extension AdamantAccountService { securedStore.remove(.privateKey) securedStore.remove(.useBiometry) hasStayInAccount = false + notificationsService.setNotificationsMode(.disabled, completion: nil) } } diff --git a/Adamant/Stories/Settings/SettingsViewController+StayIn.swift b/Adamant/Stories/Settings/SettingsViewController+StayIn.swift index d8dc9b7d6..51c5f30dc 100644 --- a/Adamant/Stories/Settings/SettingsViewController+StayIn.swift +++ b/Adamant/Stories/Settings/SettingsViewController+StayIn.swift @@ -47,7 +47,7 @@ extension SettingsViewController { // MARK: Use biometry func setBiometry(enabled: Bool) { - guard showBiometryRow, accountService.hasStayInAccount, accountService.useBiometry != enabled else { + guard showLoggedInOptions, accountService.hasStayInAccount, accountService.useBiometry != enabled else { return } @@ -86,18 +86,20 @@ extension SettingsViewController { case .failed: DispatchQueue.main.async { - guard let row: SwitchRow = self?.form.rowBy(tag: Rows.biometry.tag) else { - return + if let row: SwitchRow = self?.form.rowBy(tag: Rows.biometry.tag) { + if let value = self?.accountService.useBiometry { + row.value = value + } else { + row.value = false + } + + row.updateCell() + row.evaluateHidden() } - if let value = self?.accountService.useBiometry { - row.value = value - } else { - row.value = false + if let row: LabelRow = self?.form.rowBy(tag: Rows.notifications.tag) { + row.evaluateHidden() } - - row.updateCell() - row.evaluateHidden() } } } @@ -134,12 +136,16 @@ extension SettingsViewController: PinpadViewControllerDelegate { if let biometryType = self?.localAuth.biometryType, biometryType == .touchID || biometryType == .faceID, let row: SwitchRow = self?.form.rowBy(tag: Rows.biometry.tag) { - self?.showBiometryRow = true + self?.showLoggedInOptions = true row.value = false row.updateCell() row.evaluateHidden() } + if let row: LabelRow = self?.form.rowBy(tag: Rows.notifications.tag) { + row.evaluateHidden() + } + pinpad.dismiss(animated: true, completion: nil) } @@ -159,12 +165,16 @@ extension SettingsViewController: PinpadViewControllerDelegate { accountService.dropSavedAccount() if let row: SwitchRow = form.rowBy(tag: Rows.biometry.tag) { - showBiometryRow = false + showLoggedInOptions = false row.value = false row.updateCell() row.evaluateHidden() } + if let row: LabelRow = form.rowBy(tag: Rows.notifications.tag) { + row.evaluateHidden() + } + pinpad.dismiss(animated: true, completion: nil) @@ -208,12 +218,16 @@ extension SettingsViewController: PinpadViewControllerDelegate { DispatchQueue.main.async { if let row: SwitchRow = self?.form.rowBy(tag: Rows.biometry.tag) { - self?.showBiometryRow = false + self?.showLoggedInOptions = false row.value = false row.updateCell() row.evaluateHidden() } + if let row: LabelRow = self?.form.rowBy(tag: Rows.notifications.tag) { + row.evaluateHidden() + } + pinpad.dismiss(animated: true, completion: nil) } diff --git a/Adamant/Stories/Settings/SettingsViewController.swift b/Adamant/Stories/Settings/SettingsViewController.swift index f1b90458e..b87bf20d1 100644 --- a/Adamant/Stories/Settings/SettingsViewController.swift +++ b/Adamant/Stories/Settings/SettingsViewController.swift @@ -89,7 +89,7 @@ class SettingsViewController: FormViewController { // MARK: Properties - var showBiometryRow = false + var showLoggedInOptions = false var pinpadRequest: SettingsViewController.PinpadRequest? @@ -98,7 +98,7 @@ class SettingsViewController: FormViewController { super.viewDidLoad() self.navigationItem.title = String.adamantLocalized.settings.title navigationOptions = .Disabled - showBiometryRow = accountService.hasStayInAccount + showLoggedInOptions = accountService.hasStayInAccount // MARK: Settings form +++ Section(Sections.settings.localized) @@ -123,7 +123,7 @@ class SettingsViewController: FormViewController { $0.tag = Rows.biometry.tag $0.value = accountService.useBiometry $0.hidden = Condition.function([], { [weak self] _ -> Bool in - guard let showBiometry = self?.showBiometryRow else { + guard let showBiometry = self?.showLoggedInOptions else { return true } @@ -149,6 +149,13 @@ class SettingsViewController: FormViewController { <<< LabelRow() { $0.title = Rows.notifications.localized $0.tag = Rows.notifications.tag + $0.hidden = Condition.function([], { [weak self] _ -> Bool in + guard let showNotifications = self?.showLoggedInOptions else { + return true + } + + return !showNotifications + }) }.cellSetup({ (cell, _) in cell.selectionStyle = .gray }).onCellSelection({ [weak self] (cell, _) in @@ -225,7 +232,7 @@ class SettingsViewController: FormViewController { return } - self?.showBiometryRow = accountService.hasStayInAccount + self?.showLoggedInOptions = accountService.hasStayInAccount if let row: SwitchRow = form.rowBy(tag: Rows.stayLoggedIn.tag) { row.value = accountService.hasStayInAccount @@ -235,6 +242,10 @@ class SettingsViewController: FormViewController { row.value = accountService.hasStayInAccount && accountService.useBiometry row.evaluateHidden() } + + if let row: LabelRow = form.rowBy(tag: Rows.notifications.tag) { + row.evaluateHidden() + } } } diff --git a/Adamant/SwinjectDependencies.swift b/Adamant/SwinjectDependencies.swift index ba69519b4..2f95da748 100644 --- a/Adamant/SwinjectDependencies.swift +++ b/Adamant/SwinjectDependencies.swift @@ -70,6 +70,7 @@ extension Container { service.apiService = r.resolve(ApiService.self)! service.adamantCore = r.resolve(AdamantCore.self)! service.securedStore = r.resolve(SecuredStore.self)! + service.notificationsService = r.resolve(NotificationsService.self)! return service }.inObjectScope(.container) From 0bbcbdbad7dcaa57a865249bfe1b9066a95780e8 Mon Sep 17 00:00:00 2001 From: Pavel Anokhov Date: Sat, 26 May 2018 20:07:10 +0300 Subject: [PATCH 21/34] Pods updated --- Podfile.lock | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/Podfile.lock b/Podfile.lock index 85de429a3..2ae0fffe7 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -4,18 +4,18 @@ PODS: - EFQRCode (4.2.1) - Eureka (4.1.1) - FreakingSimpleRoundImageView (1.1) - - FTIndicator (1.2.8): - - FTIndicator/FTNotificationIndicator (= 1.2.8) - - FTIndicator/FTProgressIndicator (= 1.2.8) - - FTIndicator/FTToastIndicator (= 1.2.8) - - FTIndicator/FTNotificationIndicator (1.2.8) - - FTIndicator/FTProgressIndicator (1.2.8) - - FTIndicator/FTToastIndicator (1.2.8) - - Haring (2.0.7) + - FTIndicator (1.2.9): + - FTIndicator/FTNotificationIndicator (= 1.2.9) + - FTIndicator/FTProgressIndicator (= 1.2.9) + - FTIndicator/FTToastIndicator (= 1.2.9) + - FTIndicator/FTNotificationIndicator (1.2.9) + - FTIndicator/FTProgressIndicator (1.2.9) + - FTIndicator/FTToastIndicator (1.2.9) + - Haring (2.0.8) - KeychainAccess (3.1.1) - - MessageKit (0.13.4) + - MessageKit (0.13.5) - MyLittlePinpad (0.2.4) - - QRCodeReader.swift (8.1.1) + - QRCodeReader.swift (8.2.0) - ReachabilitySwift (4.1.0) - RNCryptor (5.0.3) - Swinject (2.4.0) @@ -59,12 +59,12 @@ SPEC CHECKSUMS: EFQRCode: f3fd67049faa07adb575495c05d72a34e407a940 Eureka: b88fb930e42c79f8c03c373d0fcdc28c1d6c50ed FreakingSimpleRoundImageView: 4a3a1cb1347beb247f8c63b5b5cae9d770b431ee - FTIndicator: 5218a42e6a3bb6623f58e4931fc36bb09b7e1b78 - Haring: e9fa27a6ca648045fbacccc59547abed44b26694 + FTIndicator: f7f071fd159e5befa1d040a9ef2e3ab53fa9322c + Haring: d2a4cfc00dfb63836dffc93e45919369f850e134 KeychainAccess: 7bd430028059754a3debab3cfc0bd1fc7fb85df3 - MessageKit: b600e3f466632f93c0333c78fd9006c960c8cbe2 + MessageKit: c4bd50e285a0f0761bc23a2e982aa7840873c4f5 MyLittlePinpad: dc5f8a7fc13a4ad6fc9dc8d3359d91f1b5b1c7e8 - QRCodeReader.swift: b164a681887de276d405ff02bce854d82cd6360b + QRCodeReader.swift: 003eb32f18a5a675b936ec82ba0ff368cddbff45 ReachabilitySwift: 6849231cd4e06559f3b9ef4a97a0a0f96d41e09f RNCryptor: c93d19029dcf7ff160aca0f24d6c9e7b0d82f664 Swinject: a1364b0f66c2736bb03c1c7cab54809e16df25da From 5b78cdf2e7fce5b5a91456c3067f128a9e9eac0b Mon Sep 17 00:00:00 2001 From: Pavel Anokhov Date: Tue, 29 May 2018 16:58:19 +0300 Subject: [PATCH 22/34] Readme.md updated. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0e76f5dd7..ba0d03393 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# iOS version of ADAMANT Messenger +# Adamant-iOS -It's a native iOS version of ADAMANT Messenger. Available at App Store *(now it's in TestFlight)*. You can use this repository to build your own ADAMANT iOS application. +iOS native client for ADAMANT Messenger. Available at App Store *(now it's in TestFlight)*. You can use this repository to build your own ADAMANT iOS application. ADAMANT is the most secure and anonymous messenger, encrypted with Blockchain. From 7ddc41aacc8af6b5c7dfdfe1ed1b0ae1c5d319b5 Mon Sep 17 00:00:00 2001 From: Anton B Date: Tue, 29 May 2018 19:42:46 +0300 Subject: [PATCH 23/34] add offset and limit Add limit and offset parameters to transaction request --- Adamant/ServiceProtocols/ApiService.swift | 2 +- .../ApiService/AdamantApi+Transactions.swift | 71 ++++++++++--------- 2 files changed, 40 insertions(+), 33 deletions(-) diff --git a/Adamant/ServiceProtocols/ApiService.swift b/Adamant/ServiceProtocols/ApiService.swift index a2b6376a0..962c42516 100644 --- a/Adamant/ServiceProtocols/ApiService.swift +++ b/Adamant/ServiceProtocols/ApiService.swift @@ -70,7 +70,7 @@ protocol ApiService: class { // MARK: - Transactions func getTransaction(id: UInt64, completion: @escaping (ApiServiceResult) -> Void) - func getTransactions(forAccount: String, type: TransactionType, fromHeight: Int64?, completion: @escaping (ApiServiceResult<[Transaction]>) -> Void) + func getTransactions(forAccount: String, type: TransactionType, fromHeight: Int64?, offset: Int?, limit: Int?, completion: @escaping (ApiServiceResult<[Transaction]>) -> Void) // MARK: - Funds diff --git a/Adamant/Services/ApiService/AdamantApi+Transactions.swift b/Adamant/Services/ApiService/AdamantApi+Transactions.swift index 5b4818b2f..107ae7c35 100644 --- a/Adamant/Services/ApiService/AdamantApi+Transactions.swift +++ b/Adamant/Services/ApiService/AdamantApi+Transactions.swift @@ -44,36 +44,43 @@ extension AdamantApiService { } } - func getTransactions(forAccount account: String, type: TransactionType, fromHeight: Int64?, completion: @escaping (ApiServiceResult<[Transaction]>) -> Void) { - var queryItems = [URLQueryItem(name: "inId", value: account), - URLQueryItem(name: "and:type", value: String(type.rawValue))] - - if let fromHeight = fromHeight, fromHeight > 0 { - queryItems.append(URLQueryItem(name: "and:fromHeight", value: String(fromHeight))) - } - - let endpoint: URL - do { - endpoint = try buildUrl(path: ApiCommands.Transactions.root, queryItems: queryItems) - } catch { - let err = InternalError.endpointBuildFailed.apiServiceErrorWith(error: error) - completion(.failure(err)) - return - } - - sendRequest(url: endpoint) { (serverResponse: ApiServiceResult>) in - switch serverResponse { - case .success(let response): - if let collection = response.collection { - completion(.success(collection)) - } else { - let error = AdamantApiService.translateServerError(response.error) - completion(.failure(error)) - } - - case .failure(let error): - completion(.failure(.networkError(error: error))) - } - } - } + func getTransactions(forAccount account: String, type: TransactionType, fromHeight: Int64?, offset: Int?, limit: Int?, completion: @escaping (ApiServiceResult<[Transaction]>) -> Void) { + + var queryItems = [URLQueryItem(name: "inId", value: account), + URLQueryItem(name: "and:type", value: String(type.rawValue)) + ] + + if let limit = limit { queryItems.append(URLQueryItem(name: "limit", value: String(limit))) } + + if let offset = offset { queryItems.append(URLQueryItem(name: "offset", value: String(offset))) } + + if let fromHeight = fromHeight, fromHeight > 0 { + queryItems.append(URLQueryItem(name: "and:fromHeight", value: String(fromHeight))) + } + + let endpoint: URL + do { + endpoint = try buildUrl(path: ApiCommands.Transactions.root, queryItems: queryItems) + } catch { + let err = InternalError.endpointBuildFailed.apiServiceErrorWith(error: error) + completion(.failure(err)) + return + } + + sendRequest(url: endpoint) { (serverResponse: ApiServiceResult>) in + switch serverResponse { + case .success(let response): + if let collection = response.collection { + print("Recive \(collection.count) trantaction(s)") + completion(.success(collection)) + } else { + let error = AdamantApiService.translateServerError(response.error) + completion(.failure(error)) + } + + case .failure(let error): + completion(.failure(.networkError(error: error))) + } + } + } } From 88e6de09d099df6d519da870313ece6d5cdb1602 Mon Sep 17 00:00:00 2001 From: Anton B Date: Tue, 29 May 2018 20:23:45 +0300 Subject: [PATCH 24/34] Load transactions recursivly --- .../DataProviders/TransfersProvider.swift | 3 + ...antTransfersProvider+backgroundFetch.swift | 2 +- .../AdamantTransfersProvider.swift | 467 ++++++++++-------- 3 files changed, 275 insertions(+), 197 deletions(-) diff --git a/Adamant/ServiceProtocols/DataProviders/TransfersProvider.swift b/Adamant/ServiceProtocols/DataProviders/TransfersProvider.swift index b77eba025..9f5c54f58 100644 --- a/Adamant/ServiceProtocols/DataProviders/TransfersProvider.swift +++ b/Adamant/ServiceProtocols/DataProviders/TransfersProvider.swift @@ -45,6 +45,9 @@ extension AdamantUserInfoKey { // New received transactions static let newTransactions = "transfersNewTransactions" + + /// lastMessageHeight: new lastMessageHeight + static let lastTransactionHeight = "adamant.transfersProvider.newTransactions.lastHeight" private init() {} } diff --git a/Adamant/Services/DataProviders/AdamantTransfersProvider+backgroundFetch.swift b/Adamant/Services/DataProviders/AdamantTransfersProvider+backgroundFetch.swift index 0c303d42f..06149276b 100644 --- a/Adamant/Services/DataProviders/AdamantTransfersProvider+backgroundFetch.swift +++ b/Adamant/Services/DataProviders/AdamantTransfersProvider+backgroundFetch.swift @@ -34,7 +34,7 @@ extension AdamantTransfersProvider: BackgroundFetchService { } } - apiService.getTransactions(forAccount: address, type: .send, fromHeight: lastHeight) { result in + apiService.getTransactions(forAccount: address, type: .send, fromHeight: lastHeight, offset: 0, limit: 100) { result in switch result { case .success(let transactions): let total = transactions.filter({$0.recipientId == address}).count diff --git a/Adamant/Services/DataProviders/AdamantTransfersProvider.swift b/Adamant/Services/DataProviders/AdamantTransfersProvider.swift index 940b11919..a8b6e721d 100644 --- a/Adamant/Services/DataProviders/AdamantTransfersProvider.swift +++ b/Adamant/Services/DataProviders/AdamantTransfersProvider.swift @@ -24,6 +24,7 @@ class AdamantTransfersProvider: TransfersProvider { private(set) var isInitiallySynced: Bool = false private(set) var receivedLastHeight: Int64? private(set) var readedLastHeight: Int64? + private let apiTransactions = 100 private let processingQueue = DispatchQueue(label: "im.Adamant.processing.transfers", qos: .utility, attributes: [.concurrent]) private let stateSemaphore = DispatchSemaphore(value: 1) @@ -129,50 +130,57 @@ extension AdamantTransfersProvider { self.setState(.failedToUpdate(TransfersProviderError.notLogged), previous: prevState) return } + + // MARK: 3. Get transactions + let privateContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType) + privateContext.parent = self.stack.container.viewContext + let processingGroup = DispatchGroup() + let cms = DispatchSemaphore(value: 1) + let prevHeight = receivedLastHeight - apiService.getTransactions(forAccount: address, type: .send, fromHeight: receivedLastHeight) { result in - switch result { - case .success(let transactions): - guard transactions.count > 0 else { - self.setState(.upToDate, previous: prevState) - return - } - - self.processingQueue.async { - self.processRawTransactions(transactions, currentAddress: address) { [weak self] result in - switch result { - case .success(let total): - self?.setState(.upToDate, previous: prevState) - if total > 0 { - NotificationCenter.default.post(name: Notification.Name.AdamantTransfersProvider.newTransactions, - object: self, - userInfo: [AdamantUserInfoKey.TransfersProvider.newTransactions: total]) - } - - if let h = self?.receivedLastHeight { - self?.readedLastHeight = h - } else { - self?.readedLastHeight = 0 - } - - if let synced = self?.isInitiallySynced, !synced { - self?.isInitiallySynced = true - NotificationCenter.default.post(name: Notification.Name.AdamantTransfersProvider.initialSyncFinished, object: self) - } - - case .error(let error): - self?.setState(.failedToUpdate(error), previous: prevState) - - case .accountNotFound(let key): - self?.setState(.failedToUpdate(TransfersProviderError.accountNotFound(key)), previous: prevState) - } - } - } - - case .failure(let error): - self.setState(.failedToUpdate(error), previous: prevState) - } - } + getTransactions(forAccount: address, type: .send, fromHeight: prevHeight, offset: nil, dispatchGroup: processingGroup, context: privateContext, contextMutatingSemaphore: cms) + + // MARK: 4. Check + processingGroup.notify(queue: DispatchQueue.global(qos: .utility)) { [weak self] in + guard let state = self?.state else { + return + } + + switch state { + case .failedToUpdate(_): // Processing failed + break + + default: + self?.setState(.upToDate, previous: prevState) + + if prevHeight != self?.receivedLastHeight, let h = self?.receivedLastHeight { + NotificationCenter.default.post(name: Notification.Name.AdamantChatsProvider.newUnreadMessages, + object: self, + userInfo: [AdamantUserInfoKey.TransfersProvider.lastTransactionHeight:h]) + } + + if let h = self?.receivedLastHeight { + self?.readedLastHeight = h + } else { + self?.readedLastHeight = 0 + } + + if let store = self?.securedStore { + if let h = self?.receivedLastHeight { + store.set(String(h), for: StoreKey.chatProvider.receivedLastHeight) + } + + if let h = self?.readedLastHeight, h > 0 { + store.set(String(h), for: StoreKey.chatProvider.readedLastHeight) + } + } + + if let synced = self?.isInitiallySynced, !synced { + self?.isInitiallySynced = true + NotificationCenter.default.post(name: Notification.Name.AdamantTransfersProvider.initialSyncFinished, object: self) + } + } + } } func reset() { @@ -265,158 +273,225 @@ extension AdamantTransfersProvider { case accountNotFound(address: String) case error(Error) } + + /// Get transactions + /// + /// - Parameters: + /// - account: for account + /// - height: last transaction height. + /// - offset: offset, if greater than 100 + /// - Returns: ammount of new messages was added + private func getTransactions(forAccount account: String, + type: TransactionType, + fromHeight: Int64?, + offset: Int?, + dispatchGroup: DispatchGroup, + context: NSManagedObjectContext, + contextMutatingSemaphore cms: DispatchSemaphore) { + // Enter 1 + dispatchGroup.enter() + + // MARK: 1. Get new transactions + apiService.getTransactions(forAccount: account, type: type, fromHeight: fromHeight, offset: offset, limit: self.apiTransactions) { result in + + defer { + // Leave 1 + dispatchGroup.leave() + } + + switch result { + case .success(let transactions): + guard transactions.count > 0 else { + return + } + + // MARK: 2. Process transactions in background + // Enter 2 + dispatchGroup.enter() + self.processingQueue.async { + defer { + // Leave 2 + dispatchGroup.leave() + } + + self.processRawTransactions(transactions, + currentAddress: account, + context: context, + contextMutatingSemaphore: cms) + } + + // MARK: 3. Get more transactions + if transactions.count == self.apiTransactions { + let newOffset: Int + if let offset = offset { + newOffset = offset + self.apiTransactions + } else { + newOffset = self.apiTransactions + } + + self.getTransactions(forAccount: account, type: type, fromHeight: fromHeight, offset: newOffset, dispatchGroup: dispatchGroup, context: context, contextMutatingSemaphore: cms) + } + + case .failure(let error): + self.setState(.failedToUpdate(error), previous: .updating) + } + } + } + + private func processRawTransactions(_ transactions: [Transaction], + currentAddress address: String, + context: NSManagedObjectContext, + contextMutatingSemaphore cms: DispatchSemaphore) { + // MARK: 0. Transactions? + guard transactions.count > 0 else { + return + } + + // MARK: 1. Collect all partners + var partnerIds: Set = [] + + for t in transactions { + if t.senderId == address { + partnerIds.insert(t.recipientId) + } else { + partnerIds.insert(t.senderId) + } + } + + // MARK: 2. Let AccountProvider get all partners from server. + let partnersGroup = DispatchGroup() + var errors: [ProcessingResult] = [] + for id in partnerIds { + partnersGroup.enter() + accountsProvider.getAccount(byAddress: id, completion: { result in + defer { + partnersGroup.leave() + } + + switch result { + case .success(_): + break + + case .notFound: + errors.append(ProcessingResult.accountNotFound(address: id)) + + case .serverError(let error): + errors.append(ProcessingResult.error(error)) + } + }) + } + + partnersGroup.wait() + + // MARK: 2.5. If we have any errors - drop processing. + if let error = errors.first { + print(error) + return + } + + + // MARK: 3. Create private context, and process transactions + let context = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType) + context.parent = self.stack.container.viewContext + + var partners: [String:CoreDataAccount] = [:] + for id in partnerIds { + let request = NSFetchRequest(entityName: CoreDataAccount.entityName) + request.predicate = NSPredicate(format: "address == %@", id) + request.fetchLimit = 1 + if let partner = (try? context.fetch(request))?.first { + partners[id] = partner + } + } + + var transfers = [TransferTransaction]() + var height: Int64 = 0 + for t in transactions { + let transfer = TransferTransaction(entity: TransferTransaction.entity(), insertInto: context) + transfer.amount = t.amount as NSDecimalNumber + transfer.date = t.date as NSDate + transfer.fee = t.fee as NSDecimalNumber + transfer.height = Int64(t.height) + transfer.recipientId = t.recipientId + transfer.senderId = t.senderId + transfer.transactionId = String(t.id) + transfer.type = Int16(t.type.rawValue) + transfer.blockId = t.blockId + transfer.confirmations = t.confirmations + + transfer.isOutgoing = t.senderId == address + let partnerId = transfer.isOutgoing ? t.recipientId : t.senderId + + if let partner = partners[partnerId] { + transfer.partner = partner + transfer.chatroom = partner.chatroom + } + + if t.height > height { + height = t.height + } + + transfers.append(transfer) + } + + + // MARK: 4. Check lastHeight + // API returns transactions from lastHeight INCLUDING transaction with height == lastHeight, so +1 + if height > 0 { + let uH = Int64(height + 1) + + if let lastHeight = receivedLastHeight { + if lastHeight < uH { + self.receivedLastHeight = uH + } + } else { + self.receivedLastHeight = uH + } + } + + // MARK: 5. Unread transactions + if let unreadedHeight = readedLastHeight { + let unreadTransactions = transfers.filter { $0.height > unreadedHeight } + let chatrooms = Dictionary.init(grouping: unreadTransactions, by: ({ (t: TransferTransaction) -> Chatroom in t.chatroom!})) + + for (chatroom, trs) in chatrooms { + chatroom.hasUnreadMessages = true + trs.forEach { $0.isUnread = true } + } + + transfers.filter({$0.height > unreadedHeight}).forEach({$0.isUnread = true}) + + readedLastHeight = self.receivedLastHeight + } + + if let h = self.receivedLastHeight { + securedStore.set(String(h), for: StoreKey.transfersProvider.receivedLastHeight) + } + + if let h = self.readedLastHeight { + securedStore.set(String(h), for: StoreKey.transfersProvider.readedLastHeight) + } + + // MARK: 6. Dump transactions to viewContext + if context.hasChanges { + do { + try context.save() + + // MARK: 7. Update lastTransactions + let viewContextChatrooms = Set(transfers.compactMap { $0.chatroom }).compactMap { self.stack.container.viewContext.object(with: $0.objectID) as? Chatroom } + DispatchQueue.main.async { + viewContextChatrooms.forEach { $0.updateLastTransaction() } + } + print(".success \(transfers.count)") + // completion(.success(new: transfers.count)) + } catch { + // completion(.error(error)) + print(error) + } + } else { + // completion(.success(new: 0)) + print(".success 0") + } + } - private func processRawTransactions(_ transactions: [Transaction], - currentAddress address: String, - completion: @escaping (ProcessingResult) -> Void) { - // MARK: 0. Transactions? - guard transactions.count > 0 else { - completion(.success(new: 0)) - return - } - - // MARK: 1. Collect all partners - var partnerIds: Set = [] - - for t in transactions { - if t.senderId == address { - partnerIds.insert(t.recipientId) - } else { - partnerIds.insert(t.senderId) - } - } - - // MARK: 2. Let AccountProvider get all partners from server. - let partnersGroup = DispatchGroup() - var errors: [ProcessingResult] = [] - for id in partnerIds { - partnersGroup.enter() - accountsProvider.getAccount(byAddress: id, completion: { result in - defer { - partnersGroup.leave() - } - - switch result { - case .success(_): - break - - case .notFound: - errors.append(ProcessingResult.accountNotFound(address: id)) - - case .serverError(let error): - errors.append(ProcessingResult.error(error)) - } - }) - } - - partnersGroup.wait() - - // MARK: 2.5. If we have any errors - drop processing. - if let err = errors.first { - completion(err) - return - } - - - // MARK: 3. Create private context, and process transactions - let context = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType) - context.parent = self.stack.container.viewContext - - var partners: [String:CoreDataAccount] = [:] - for id in partnerIds { - let request = NSFetchRequest(entityName: CoreDataAccount.entityName) - request.predicate = NSPredicate(format: "address == %@", id) - request.fetchLimit = 1 - if let partner = (try? context.fetch(request))?.first { - partners[id] = partner - } - } - - var transfers = [TransferTransaction]() - var height: Int64 = 0 - for t in transactions { - let transfer = TransferTransaction(entity: TransferTransaction.entity(), insertInto: context) - transfer.amount = t.amount as NSDecimalNumber - transfer.date = t.date as NSDate - transfer.fee = t.fee as NSDecimalNumber - transfer.height = Int64(t.height) - transfer.recipientId = t.recipientId - transfer.senderId = t.senderId - transfer.transactionId = String(t.id) - transfer.type = Int16(t.type.rawValue) - transfer.blockId = t.blockId - transfer.confirmations = t.confirmations - - transfer.isOutgoing = t.senderId == address - let partnerId = transfer.isOutgoing ? t.recipientId : t.senderId - - if let partner = partners[partnerId] { - transfer.partner = partner - transfer.chatroom = partner.chatroom - } - - if t.height > height { - height = t.height - } - - transfers.append(transfer) - } - - - // MARK: 4. Check lastHeight - // API returns transactions from lastHeight INCLUDING transaction with height == lastHeight, so +1 - if height > 0 { - let uH = Int64(height + 1) - - if let lastHeight = receivedLastHeight { - if lastHeight < uH { - self.receivedLastHeight = uH - } - } else { - self.receivedLastHeight = uH - } - } - - // MARK: 5. Unread transactions - if let unreadedHeight = readedLastHeight { - let unreadTransactions = transfers.filter { $0.height > unreadedHeight } - let chatrooms = Dictionary.init(grouping: unreadTransactions, by: ({ (t: TransferTransaction) -> Chatroom in t.chatroom!})) - - for (chatroom, trs) in chatrooms { - chatroom.hasUnreadMessages = true - trs.forEach { $0.isUnread = true } - } - - transfers.filter({$0.height > unreadedHeight}).forEach({$0.isUnread = true}) - - readedLastHeight = self.receivedLastHeight - } - - if let h = self.receivedLastHeight { - securedStore.set(String(h), for: StoreKey.transfersProvider.receivedLastHeight) - } - - if let h = self.readedLastHeight { - securedStore.set(String(h), for: StoreKey.transfersProvider.readedLastHeight) - } - - // MARK: 6. Dump transactions to viewContext - if context.hasChanges { - do { - try context.save() - - // MARK: 7. Update lastTransactions - let viewContextChatrooms = Set(transfers.compactMap { $0.chatroom }).compactMap { self.stack.container.viewContext.object(with: $0.objectID) as? Chatroom } - DispatchQueue.main.async { - viewContextChatrooms.forEach { $0.updateLastTransaction() } - } - - completion(.success(new: transfers.count)) - } catch { - completion(.error(error)) - } - } else { - completion(.success(new: 0)) - } - } } From ec97d965b44f4d51a75c71e7061af9fdac7eaf42 Mon Sep 17 00:00:00 2001 From: Anton B Date: Tue, 29 May 2018 20:25:22 +0300 Subject: [PATCH 25/34] Update list of servers --- Adamant/AppDelegate.swift | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Adamant/AppDelegate.swift b/Adamant/AppDelegate.swift index da2db9ad1..5c1ce8c64 100644 --- a/Adamant/AppDelegate.swift +++ b/Adamant/AppDelegate.swift @@ -37,9 +37,8 @@ struct AdamantResources { static let servers = [ "https://endless.adamant.im", -// "https://clown.adamant.im", -// "https://lake.adamant.im", - "https://fake.adamant.im" + "https://clown.adamant.im", + "https://lake.adamant.im" ] private init() {} From 49767541bd019980d76ddce1c9fd314509224f1b Mon Sep 17 00:00:00 2001 From: Anton B Date: Thu, 31 May 2018 15:15:29 +0300 Subject: [PATCH 26/34] Add new error alert Add New error alert Add No internet connection notification --- Adamant/AppDelegate.swift | 11 ++- .../Icons/error.imageset/Contents.json | 23 +++++ .../Icons/error.imageset/error.png | Bin 0 -> 1949 bytes .../Icons/error.imageset/error@2x.png | Bin 0 -> 4057 bytes .../Icons/error.imageset/error@3x.png | Bin 0 -> 6365 bytes .../Assets/l18n/en.lproj/Localizable.strings | 12 +++ .../Assets/l18n/ru.lproj/Localizable.strings | Bin 44106 -> 45170 bytes Adamant/Helpers/GlobalConstants.swift | 3 + Adamant/Helpers/String+localized.swift | 7 ++ Adamant/Helpers/UIColor+adamant.swift | 30 +++++++ Adamant/ServiceProtocols/DialogService.swift | 2 + Adamant/Services/AdamantDialogService.swift | 79 +++++++++++++++++- Adamant/Utilities/AdamantUtilities.swift | 13 +++ Podfile | 1 + 14 files changed, 177 insertions(+), 4 deletions(-) create mode 100644 Adamant/Assets/Assets.xcassets/Icons/error.imageset/Contents.json create mode 100644 Adamant/Assets/Assets.xcassets/Icons/error.imageset/error.png create mode 100644 Adamant/Assets/Assets.xcassets/Icons/error.imageset/error@2x.png create mode 100644 Adamant/Assets/Assets.xcassets/Icons/error.imageset/error@3x.png create mode 100644 Adamant/Helpers/UIColor+adamant.swift diff --git a/Adamant/AppDelegate.swift b/Adamant/AppDelegate.swift index da2db9ad1..14b0541a7 100644 --- a/Adamant/AppDelegate.swift +++ b/Adamant/AppDelegate.swift @@ -37,9 +37,8 @@ struct AdamantResources { static let servers = [ "https://endless.adamant.im", -// "https://clown.adamant.im", -// "https://lake.adamant.im", - "https://fake.adamant.im" + "https://clown.adamant.im", + "https://lake.adamant.im" ] private init() {} @@ -56,6 +55,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { // MARK: Dependencies var accountService: AccountService! var notificationService: NotificationsService! + var dialogService: DialogService! // MARK: - Lifecycle @@ -65,6 +65,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { container.registerAdamantServices() accountService = container.resolve(AccountService.self) notificationService = container.resolve(NotificationsService.self) + dialogService = container.resolve(DialogService.self) // MARK: 2. Init UI window = UIWindow(frame: UIScreen.main.bounds) @@ -116,9 +117,11 @@ class AppDelegate: UIResponder, UIApplicationDelegate { switch reachability.connection { case .cellular, .wifi: + dialogService.dissmisNoConnectionNotification() break case .none: + dialogService.showNoConnectionNotification() repeater.pauseAll() } @@ -130,9 +133,11 @@ class AppDelegate: UIResponder, UIApplicationDelegate { switch connection { case .cellular, .wifi: + self?.dialogService.dissmisNoConnectionNotification() repeater.resumeAll() case .none: + self?.dialogService.showNoConnectionNotification() repeater.pauseAll() } } diff --git a/Adamant/Assets/Assets.xcassets/Icons/error.imageset/Contents.json b/Adamant/Assets/Assets.xcassets/Icons/error.imageset/Contents.json new file mode 100644 index 000000000..3e5932ea6 --- /dev/null +++ b/Adamant/Assets/Assets.xcassets/Icons/error.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "error.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "error@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "error@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Adamant/Assets/Assets.xcassets/Icons/error.imageset/error.png b/Adamant/Assets/Assets.xcassets/Icons/error.imageset/error.png new file mode 100644 index 0000000000000000000000000000000000000000..2f1e3d747c3aa00e863199349a90a540cba00635 GIT binary patch literal 1949 zcmV;O2V(e%P)Ik4pMwY{;aaH@>ijyDy0-)b93{8(b3UrjIR;T z=|KQAh&K;bR8;(;=ebq)x+O?2r>UuF#@N`{GXQ(j44l?p_&X0DK0IIF->rJe00Qp8 z$;rt#Fm$@gSit#d^E_S@pau`C`Xxe73BdaL`aF!-OfIrv%maWdZES2@VWVxrO_n#Ytz?R?K&BHp5#+we zmxGUjf`V+$y}cMEM}yNO-W$Dk?b_v*mXndaLNH4jb1N|k* zv^zxocU61n+X?ZCBuKYtV~ZtcmZLO0JUokavmYV3W`Y266VHCMd-v{!DE?`Jfv-6l z4#L|IMGt*Ko5e?EXJ_YCN~?j;cQw=sRriOUU=iv17;bE!tSJk^+cQ@8ICz zG9Fo7|ao;>3v@!m523FK3@slCVn| znP}jZ1E2|>$3RqExpL(mD@7_^!X9L5`Z?6sA+Ss(G)xD8gltR0b$tncE;PyUQYizv zL3GD!sOKjPkn>57gIbKs&Y+CTuk+A%L#HzZ?L_h%Xo!8IcQcOzP<9khLdM+yFkNp;loa9v0n+-Zl~f*xK4UA4Sz> z2AI)kE8BY(hAq+anR2u6t^l{ad5G?=uCAq;T)jxsx@~|w|79>m- zNPv)whfSj&l_*Vj)zPCzZ6E%&03z7SXs;yiQ7N~e&!E3y2ZUyKgNYH%pj2STYP-)H zwg8?vb0!0W4xq{Q$c3%KAbC|=O#hf@L;z|a!%c0K^vNdIT)%$Z&gK;V9F&pj?(Qx@ z=!>>2Fg6MRa?$bQ$0G+iyQ+^UW-fFIsuNDw@*G|)L5}iCGAuN%2iw}(=CC1rK!><( z!$NgsItJyklSW3ozrQ~dnoQATo;F0h*it(%Ft7kQF*8G$(aF+i?Y1Dd^i|QF68RPb zNHfWiqr&g^%h4bJlfLgEcLiR2EqZ2{*`)+1hTXb#YZ*fJn{`Z(7C_Fta378hSx1sB zM?c)&qTC~wZJF|_HcEh}PMyl&BKsa4?SxV)`5`(${CF_>NRagyS(BEXEtts7W%;}# zmu^)v(-D*r;U4xB0#4J>_Cc&t0(}&KZOCh<^iXC~+)Jr6%tDr)HO{@VEI&U#SW;3l zs%M%$S>X0;8p_7v1~}0vv7}tDmQhMwZtS7bg+J+dv6cwuBC^`_@(fcH@A+~04h)bR zgKG(o^^w2TatEbr2r;sVHNIVJ*BfjA$kr=|vaCI~(lB6x$lSDR*RGA^4QXj*F0FaTWp|M*(f{X*=JLK)-RqbcKC&^Rg<>h~5W@a9OUPm4xJ5CJX zLb07VHRkAv0Z2eyNW*f++B5-x=_0rAs&-Q^v#hMF2VLi&U)J-It&8n|ToZk4L;%9{ zvRFSwxSPN3^rNsHvoO&!$?*vFQ`Adzt zj5GYRLP%lP$D#3~Ia;=e!z}~u z1h8mZS9_T@!_kzx?0dwE2hRa=;w}Rw|L`|K{>Dp`A^!;X#oY!F$JOQK<^3d6bgHnh zu$uCHKz9D$fr3NA!%uQ@a%xITONU}2Z~3o3Ub~7x{|G=IrJODU@LvXvjhN^eA^(V- j-;*!$>#-b5A-n$uQ{Yg=qU`;!00000NkvXXu0mjf^0T^m literal 0 HcmV?d00001 diff --git a/Adamant/Assets/Assets.xcassets/Icons/error.imageset/error@2x.png b/Adamant/Assets/Assets.xcassets/Icons/error.imageset/error@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..f5c301a37ed512f31e52180a433e299a3bb77b56 GIT binary patch literal 4057 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 literal 0 HcmV?d00001 diff --git a/Adamant/Assets/l18n/en.lproj/Localizable.strings b/Adamant/Assets/l18n/en.lproj/Localizable.strings index 40b09acb0..60e16067e 100644 --- a/Adamant/Assets/l18n/en.lproj/Localizable.strings +++ b/Adamant/Assets/l18n/en.lproj/Localizable.strings @@ -131,6 +131,12 @@ /* Chat: 'Sent funds' buble 'Tap for details' tip */ "ChatScene.tapForDetails" = "Tap for details"; +/* Error messge title for support email */ +"Error.Mail.Title" = "I have error in my iOS app"; + +/* Error messge body for support email */ +"Error.Mail.Body" = "Hello,\nI have this error:\n\n%@\n\nMy device info:\n%2"; + /* 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"; @@ -332,6 +338,12 @@ /* 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 alert notification: title for no internet connection message. */ +"Shared.NoInternet.Title" = "No internet connection"; + +/* Shared alert notification: body message for no internet connection. */ +"Shared.NoInternet.Body" = "Please check your internet connection and try again"; + /* 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 0e3f9da069be95ce9b714a3dc724069dd6de9fde..f7c7c90e0f2064b81990c03658a446b214484320 100644 GIT binary patch delta 755 zcmbVKyG{a85FN=FBNiBlf{(<_8XHu^4NH*60!u7>AThQyibzOsHG%~d9U-y2|6xq9 z@)K-~iG`igSy}i6o_i%h36;5-o!OalXU?6O$5+?ayX(%%mbd8&=K`Y9IhCo7Q-Ri~ zK~+di*=K!3ov!H;N8%g6lyT=6xEkyrZIGLE8e|Mn{wFF99e-> 8) * 17, (int >> 4 & 0xF) * 17, (int & 0xF) * 17) + case 6: // RGB (24-bit) + (a, r, g, b) = (255, int >> 16, int >> 8 & 0xFF, int & 0xFF) + case 8: // ARGB (32-bit) + (a, r, g, b) = (int >> 24, int >> 16 & 0xFF, int >> 8 & 0xFF, int & 0xFF) + default: + (a, r, g, b) = (255, 0, 0, 0) + } + self.init(red: CGFloat(r) / 255, green: CGFloat(g) / 255, blue: CGFloat(b) / 255, alpha: CGFloat(a) / 255) + } +} diff --git a/Adamant/ServiceProtocols/DialogService.swift b/Adamant/ServiceProtocols/DialogService.swift index e915d3896..169308fe4 100644 --- a/Adamant/ServiceProtocols/DialogService.swift +++ b/Adamant/ServiceProtocols/DialogService.swift @@ -88,6 +88,8 @@ protocol DialogService: class { func dismissProgress() func showSuccess(withMessage: String) func showError(withMessage: String) + func showNoConnectionNotification() + func dissmisNoConnectionNotification() // MARK: - Notifications func showNotification(title: String?, message: String?, image: UIImage?, tapHandler: (() -> Void)?) diff --git a/Adamant/Services/AdamantDialogService.swift b/Adamant/Services/AdamantDialogService.swift index 4dbf20e25..5e80d5598 100644 --- a/Adamant/Services/AdamantDialogService.swift +++ b/Adamant/Services/AdamantDialogService.swift @@ -8,10 +8,14 @@ import UIKit import FTIndicator +import PMAlertController +import MessageUI class AdamantDialogService: DialogService { // MARK: Dependencies var router: Router! + + var mailDelegate = MailDelegate() // Configure notifications init() { @@ -88,8 +92,73 @@ extension AdamantDialogService { } func showError(withMessage message: String) { - FTIndicator.showError(withMessage: message) +// FTIndicator.showError(withMessage: message) + + let alertVC = PMAlertController(title: String.adamantLocalized.alert.error, description: message, image: #imageLiteral(resourceName: "error"), style: .alert) + + alertVC.gravityDismissAnimation = false + alertVC.alertTitle.textColor = UIColor.adamantPrimary + alertVC.alertDescription.textColor = .adamantSecondary + alertVC.alertTitle.font = UIFont.adamantPrimary(size: 20) + alertVC.alertDescription.font = UIFont.adamantPrimaryLight(size: 14) + alertVC.headerViewHeightConstraint.constant = 50 + + let supportBtn = PMAlertAction(title: String.adamantSupportEmail, style: .default, action: { () -> Void in + print("Support") + + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in + print("Send mail") + + let mailVC = MFMailComposeViewController() + mailVC.mailComposeDelegate = self?.mailDelegate + mailVC.setToRecipients([String.adamantSupportEmail]) + mailVC.setSubject(String.adamantLocalized.alert.emailErrorMessageTitle) + + let systemVersion = UIDevice.current.systemVersion + let model = AdamantUtilities.deviceModelCode + let deviceInfo = "Model: \(model)\n" + "iOS: \(systemVersion)\n" + "App version: \(AdamantUtilities.applicationVersion)" + + let body = String(format: String.adamantLocalized.alert.emailErrorMessageBody, message, deviceInfo) + + mailVC.setMessageBody(body, isHTML: false) + + self?.present(mailVC, animated: true, completion: nil) + } + }) + + supportBtn.titleLabel?.font = UIFont.adamantPrimary(size: 16) + supportBtn.setTitleColor(UIColor(hex: "#00B6FF"), for: .normal) + supportBtn.separator.isHidden = true + + alertVC.addAction(supportBtn) + + let okBtn = PMAlertAction(title: String.adamantLocalized.alert.ok, style: .default, action: { () in + print("Capture action OK") + }) + + okBtn.titleLabel?.font = UIFont.adamantPrimary(size: 16) + okBtn.setTitleColor(UIColor.white, for: .normal) + okBtn.backgroundColor = UIColor.adamantSecondary + alertVC.addAction(okBtn) + + alertVC.alertActionStackView.axis = .vertical + alertVC.alertActionStackView.spacing = 0 + alertVC.alertActionStackViewHeightConstraint.constant = 100 + + self.present(alertVC, animated: true, completion: nil) } + + func showNoConnectionNotification() { + FTIndicator.showNotification(with: #imageLiteral(resourceName: "error"), title: String.adamantLocalized.alert.noInternetNotificationTitle, message: String.adamantLocalized.alert.noInternetNotificationBoby, autoDismiss: false, tapHandler: { + // + }) { + // + } + } + + func dissmisNoConnectionNotification() { + FTIndicator.dismissNotification() + } } @@ -191,3 +260,11 @@ extension AdamantDialogService { } } } + +class MailDelegate: NSObject, MFMailComposeViewControllerDelegate { + + func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) { + controller.dismiss(animated: true, completion: nil) + } + +} diff --git a/Adamant/Utilities/AdamantUtilities.swift b/Adamant/Utilities/AdamantUtilities.swift index f6b081682..650ade09d 100644 --- a/Adamant/Utilities/AdamantUtilities.swift +++ b/Adamant/Utilities/AdamantUtilities.swift @@ -19,6 +19,19 @@ class AdamantUtilities { return "" }() + + // MARK: Device model + static var deviceModelCode: String { + var systemInfo = utsname() + uname(&systemInfo) + let modelCode = withUnsafePointer(to: &systemInfo.machine) { + $0.withMemoryRebound(to: CChar.self, capacity: 1) { + ptr in String.init(validatingUTF8: ptr) + + } + } + return modelCode ?? "Unknown" + } private init() { } } diff --git a/Podfile b/Podfile index afb89baf9..8da19a00b 100644 --- a/Podfile +++ b/Podfile @@ -18,6 +18,7 @@ target 'Adamant' do pod 'Eureka' # Forms pod 'MessageKit' # Chat UI pod 'MyLittlePinpad' # Pinpad + pod 'PMAlertController' # Custom alert controller # QR pod 'EFQRCode' # QR generator From 79c976eb6bf0090f5646670b40a404850f864bef Mon Sep 17 00:00:00 2001 From: Pavel Anokhov Date: Thu, 31 May 2018 18:16:20 +0300 Subject: [PATCH 27/34] Project, pod and merge updates and fixes. --- Adamant.xcodeproj/project.pbxproj | 14 ++++++++++---- .../xcschemes/Adamant.Dev.BackgroundFetch.xcscheme | 2 +- .../xcshareddata/xcschemes/Adamant.Dev.xcscheme | 2 +- .../xcschemes/Adamant.Release.xcscheme | 2 +- .../xcshareddata/xcschemes/Adamant.Test.xcscheme | 2 +- Adamant/AppDelegate.swift | 3 ++- ...able+Clamped.swift => Comparable+clamped.swift} | 2 +- Adamant/Helpers/GlobalConstants.swift | 4 ---- .../{UIColor+adamant.swift => UIColor+hex.swift} | 2 +- Adamant/Services/AdamantDialogService.swift | 4 ++-- Podfile.lock | 6 +++++- 11 files changed, 25 insertions(+), 18 deletions(-) rename Adamant/Helpers/{Comparable+Clamped.swift => Comparable+clamped.swift} (91%) rename Adamant/Helpers/{UIColor+adamant.swift => UIColor+hex.swift} (97%) diff --git a/Adamant.xcodeproj/project.pbxproj b/Adamant.xcodeproj/project.pbxproj index 7b52b988c..308aa1b11 100644 --- a/Adamant.xcodeproj/project.pbxproj +++ b/Adamant.xcodeproj/project.pbxproj @@ -50,6 +50,7 @@ E9256F5F2034C21100DE86E9 /* String+localized.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9256F5E2034C21100DE86E9 /* String+localized.swift */; }; E9256F6D20357B1700DE86E9 /* LoginHeader.xib in Resources */ = {isa = PBXBuildFile; fileRef = E9256F6C20357B1700DE86E9 /* LoginHeader.xib */; }; E9256F762039A9A200DE86E9 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = E9256F752039A9A200DE86E9 /* LaunchScreen.storyboard */; }; + E927171E20C04614002BB9A6 /* UIColor+hex.swift in Sources */ = {isa = PBXBuildFile; fileRef = E927171D20C04613002BB9A6 /* UIColor+hex.swift */; }; E9393FA82055C92700EE6F30 /* Decimal+adamant.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9393FA72055C92700EE6F30 /* Decimal+adamant.swift */; }; E9393FAA2055D03300EE6F30 /* AdamantMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9393FA92055D03300EE6F30 /* AdamantMessage.swift */; }; E93B0D742028B21400126346 /* ChatsProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = E93B0D732028B21400126346 /* ChatsProvider.swift */; }; @@ -159,7 +160,7 @@ E9EC341F200524CA00C0E546 /* Roboto_700_normal.ttf in Resources */ = {isa = PBXBuildFile; fileRef = E91947A7200010DF001362F8 /* Roboto_700_normal.ttf */; }; E9EC342120052ABB00C0E546 /* TransferViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9EC342020052ABB00C0E546 /* TransferViewController.swift */; }; E9EC344720066D4A00C0E546 /* AddressValidationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9EC344620066D4A00C0E546 /* AddressValidationTests.swift */; }; - E9FAE5DA203DBFEF008D3A6B /* Comparable+Clamped.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9FAE5D9203DBFEF008D3A6B /* Comparable+Clamped.swift */; }; + E9FAE5DA203DBFEF008D3A6B /* Comparable+clamped.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9FAE5D9203DBFEF008D3A6B /* Comparable+clamped.swift */; }; E9FAE5E2203ED1AE008D3A6B /* ShareQrViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9FAE5E0203ED1AE008D3A6B /* ShareQrViewController.swift */; }; E9FAE5E3203ED1AE008D3A6B /* ShareQrViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = E9FAE5E1203ED1AE008D3A6B /* ShareQrViewController.xib */; }; /* End PBXBuildFile section */ @@ -236,6 +237,7 @@ E9256F5E2034C21100DE86E9 /* String+localized.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+localized.swift"; sourceTree = ""; }; E9256F6C20357B1700DE86E9 /* LoginHeader.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = LoginHeader.xib; sourceTree = ""; }; E9256F752039A9A200DE86E9 /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = ""; }; + E927171D20C04613002BB9A6 /* UIColor+hex.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIColor+hex.swift"; sourceTree = ""; }; E9393FA72055C92700EE6F30 /* Decimal+adamant.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Decimal+adamant.swift"; sourceTree = ""; }; E9393FA92055D03300EE6F30 /* AdamantMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdamantMessage.swift; sourceTree = ""; }; E93B0D732028B21400126346 /* ChatsProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatsProvider.swift; sourceTree = ""; }; @@ -339,7 +341,7 @@ E9EC344420066D4A00C0E546 /* AdamantTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AdamantTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; E9EC344620066D4A00C0E546 /* AddressValidationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddressValidationTests.swift; sourceTree = ""; }; E9EC344820066D4A00C0E546 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - E9FAE5D9203DBFEF008D3A6B /* Comparable+Clamped.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Comparable+Clamped.swift"; sourceTree = ""; }; + E9FAE5D9203DBFEF008D3A6B /* Comparable+clamped.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Comparable+clamped.swift"; sourceTree = ""; }; E9FAE5E0203ED1AE008D3A6B /* ShareQrViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareQrViewController.swift; sourceTree = ""; }; E9FAE5E1203ED1AE008D3A6B /* ShareQrViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ShareQrViewController.xib; sourceTree = ""; }; /* End PBXFileReference section */ @@ -483,7 +485,7 @@ children = ( E91947B12000246A001362F8 /* AdamantError.swift */, E9061B96207501E40011F104 /* AdamantUserInfoKey.swift */, - E9FAE5D9203DBFEF008D3A6B /* Comparable+Clamped.swift */, + E9FAE5D9203DBFEF008D3A6B /* Comparable+clamped.swift */, E9393FA72055C92700EE6F30 /* Decimal+adamant.swift */, E95F856420067A030070534A /* GlobalConstants.swift */, E95F856A200789450070534A /* JSModels.swift */, @@ -491,6 +493,7 @@ E9147B5E20500E9300145913 /* MyLittlePinpad+adamant.swift */, E9147B6220505C7500145913 /* QRCodeReader+adamant.swift */, E9256F5E2034C21100DE86E9 /* String+localized.swift */, + E927171D20C04613002BB9A6 /* UIColor+hex.swift */, ); path = Helpers; sourceTree = ""; @@ -908,6 +911,7 @@ "${BUILT_PRODUCTS_DIR}/KeychainAccess/KeychainAccess.framework", "${BUILT_PRODUCTS_DIR}/MessageKit/MessageKit.framework", "${BUILT_PRODUCTS_DIR}/MyLittlePinpad/MyLittlePinpad.framework", + "${BUILT_PRODUCTS_DIR}/PMAlertController/PMAlertController.framework", "${BUILT_PRODUCTS_DIR}/QRCodeReader.swift/QRCodeReader.framework", "${BUILT_PRODUCTS_DIR}/RNCryptor/RNCryptor.framework", "${BUILT_PRODUCTS_DIR}/ReachabilitySwift/Reachability.framework", @@ -925,6 +929,7 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/KeychainAccess.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MessageKit.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MyLittlePinpad.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/PMAlertController.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/QRCodeReader.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RNCryptor.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Reachability.framework", @@ -1012,6 +1017,7 @@ E95F85802008C8D70070534A /* ChatsRoutes.swift in Sources */, E9942B87203D9E5100C163AF /* EurekaQRRow.swift in Sources */, E93EFE13200D1156000BB482 /* ChatViewController.swift in Sources */, + E927171E20C04614002BB9A6 /* UIColor+hex.swift in Sources */, E9B3D39E201F99F40019EB36 /* DataProvider.swift in Sources */, E9E7CDC02003AF6D00DFC4DB /* AdamantCellFactory.swift in Sources */, E91A063A209F05AA0018A102 /* NotificationsViewController.swift in Sources */, @@ -1038,7 +1044,7 @@ E965A53020B594120041A3EA /* AdamantApi+States.swift in Sources */, E9150B982066DA210065A985 /* CoreDataAccount+CoreDataProperties.swift in Sources */, E9B3D3A1201FA26B0019EB36 /* AdamantAccountsProvider.swift in Sources */, - E9FAE5DA203DBFEF008D3A6B /* Comparable+Clamped.swift in Sources */, + E9FAE5DA203DBFEF008D3A6B /* Comparable+clamped.swift in Sources */, E9150B972066DA210065A985 /* CoreDataAccount+CoreDataClass.swift in Sources */, E95F857A2007F0260070534A /* ServerResponse.swift in Sources */, E9A174B32057EC47003667CD /* BackgroundFetchService.swift in Sources */, diff --git a/Adamant.xcodeproj/xcshareddata/xcschemes/Adamant.Dev.BackgroundFetch.xcscheme b/Adamant.xcodeproj/xcshareddata/xcschemes/Adamant.Dev.BackgroundFetch.xcscheme index 15fd92192..ef6bec5a9 100644 --- a/Adamant.xcodeproj/xcshareddata/xcschemes/Adamant.Dev.BackgroundFetch.xcscheme +++ b/Adamant.xcodeproj/xcshareddata/xcschemes/Adamant.Dev.BackgroundFetch.xcscheme @@ -1,6 +1,6 @@ Void in + let supportBtn = PMAlertAction(title: AdamantResources.iosAppSupportEmail, style: .default, action: { () -> Void in print("Support") DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in @@ -111,7 +111,7 @@ extension AdamantDialogService { let mailVC = MFMailComposeViewController() mailVC.mailComposeDelegate = self?.mailDelegate - mailVC.setToRecipients([String.adamantSupportEmail]) + mailVC.setToRecipients([AdamantResources.iosAppSupportEmail]) mailVC.setSubject(String.adamantLocalized.alert.emailErrorMessageTitle) let systemVersion = UIDevice.current.systemVersion diff --git a/Podfile.lock b/Podfile.lock index 2ae0fffe7..08bec33cd 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -15,6 +15,7 @@ PODS: - KeychainAccess (3.1.1) - MessageKit (0.13.5) - MyLittlePinpad (0.2.4) + - PMAlertController (3.4.0) - QRCodeReader.swift (8.2.0) - ReachabilitySwift (4.1.0) - RNCryptor (5.0.3) @@ -31,6 +32,7 @@ DEPENDENCIES: - KeychainAccess - MessageKit - MyLittlePinpad + - PMAlertController - QRCodeReader.swift - ReachabilitySwift - RNCryptor @@ -48,6 +50,7 @@ SPEC REPOS: - KeychainAccess - MessageKit - MyLittlePinpad + - PMAlertController - QRCodeReader.swift - ReachabilitySwift - RNCryptor @@ -64,11 +67,12 @@ SPEC CHECKSUMS: KeychainAccess: 7bd430028059754a3debab3cfc0bd1fc7fb85df3 MessageKit: c4bd50e285a0f0761bc23a2e982aa7840873c4f5 MyLittlePinpad: dc5f8a7fc13a4ad6fc9dc8d3359d91f1b5b1c7e8 + PMAlertController: efb781925d741d50e0200018a00c53cecb8b4910 QRCodeReader.swift: 003eb32f18a5a675b936ec82ba0ff368cddbff45 ReachabilitySwift: 6849231cd4e06559f3b9ef4a97a0a0f96d41e09f RNCryptor: c93d19029dcf7ff160aca0f24d6c9e7b0d82f664 Swinject: a1364b0f66c2736bb03c1c7cab54809e16df25da -PODFILE CHECKSUM: 12f69d43b1b41b997e7c698aa56457f71d9ade99 +PODFILE CHECKSUM: d5efe36624e504f34934e8b20f4138c7db823908 COCOAPODS: 1.5.0 From 6bc2874054db473dbb397082cd325a5907d64468 Mon Sep 17 00:00:00 2001 From: Pavel Anokhov Date: Fri, 1 Jun 2018 12:21:38 +0300 Subject: [PATCH 28/34] dialogService.showWarning dialogService not dismissing progress fixed --- Adamant/ServiceProtocols/DialogService.swift | 1 + Adamant/Services/AdamantDialogService.swift | 30 ++++++++++--------- .../Account/TransferViewController.swift | 6 ++-- .../Stories/Chats/NewChatViewController.swift | 10 +++---- .../Login/LoginViewController+QR.swift | 6 ++-- .../Stories/Login/LoginViewController.swift | 2 +- 6 files changed, 29 insertions(+), 26 deletions(-) diff --git a/Adamant/ServiceProtocols/DialogService.swift b/Adamant/ServiceProtocols/DialogService.swift index 169308fe4..07c8b8dae 100644 --- a/Adamant/ServiceProtocols/DialogService.swift +++ b/Adamant/ServiceProtocols/DialogService.swift @@ -87,6 +87,7 @@ protocol DialogService: class { func showProgress(withMessage: String?, userInteractionEnable: Bool) func dismissProgress() func showSuccess(withMessage: String) + func showWarning(withMessage: String) func showError(withMessage: String) func showNoConnectionNotification() func dissmisNoConnectionNotification() diff --git a/Adamant/Services/AdamantDialogService.swift b/Adamant/Services/AdamantDialogService.swift index 5aa8261ee..2fa97a3f2 100644 --- a/Adamant/Services/AdamantDialogService.swift +++ b/Adamant/Services/AdamantDialogService.swift @@ -91,10 +91,20 @@ extension AdamantDialogService { FTIndicator.showSuccess(withMessage: message) } + func showWarning(withMessage message: String) { + FTIndicator.showError(withMessage: message) + } + func showError(withMessage message: String) { -// FTIndicator.showError(withMessage: message) - - let alertVC = PMAlertController(title: String.adamantLocalized.alert.error, description: message, image: #imageLiteral(resourceName: "error"), style: .alert) + if Thread.isMainThread { + FTIndicator.dismissProgress() + } else { + DispatchQueue.main.sync { + FTIndicator.dismissProgress() + } + } + + let alertVC = PMAlertController(title: String.adamantLocalized.alert.error, description: message, image: #imageLiteral(resourceName: "error"), style: .alert) alertVC.gravityDismissAnimation = false alertVC.alertTitle.textColor = UIColor.adamantPrimary @@ -103,9 +113,7 @@ extension AdamantDialogService { alertVC.alertDescription.font = UIFont.adamantPrimaryLight(size: 14) alertVC.headerViewHeightConstraint.constant = 50 - let supportBtn = PMAlertAction(title: AdamantResources.iosAppSupportEmail, style: .default, action: { () -> Void in - print("Support") - + let supportBtn = PMAlertAction(title: AdamantResources.iosAppSupportEmail, style: .default) { DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in print("Send mail") @@ -132,9 +140,7 @@ extension AdamantDialogService { alertVC.addAction(supportBtn) - let okBtn = PMAlertAction(title: String.adamantLocalized.alert.ok, style: .default, action: { () in - print("Capture action OK") - }) + let okBtn = PMAlertAction(title: String.adamantLocalized.alert.ok, style: .default) okBtn.titleLabel?.font = UIFont.adamantPrimary(size: 16) okBtn.setTitleColor(UIColor.white, for: .normal) @@ -149,11 +155,7 @@ extension AdamantDialogService { } func showNoConnectionNotification() { - FTIndicator.showNotification(with: #imageLiteral(resourceName: "error"), title: String.adamantLocalized.alert.noInternetNotificationTitle, message: String.adamantLocalized.alert.noInternetNotificationBoby, autoDismiss: false, tapHandler: { - // - }) { - // - } + FTIndicator.showNotification(with: #imageLiteral(resourceName: "error"), title: String.adamantLocalized.alert.noInternetNotificationTitle, message: String.adamantLocalized.alert.noInternetNotificationBoby, autoDismiss: false, tapHandler: nil, completion: nil) } func dissmisNoConnectionNotification() { diff --git a/Adamant/Stories/Account/TransferViewController.swift b/Adamant/Stories/Account/TransferViewController.swift index d10661728..562f915fc 100644 --- a/Adamant/Stories/Account/TransferViewController.swift +++ b/Adamant/Stories/Account/TransferViewController.swift @@ -275,17 +275,17 @@ class TransferViewController: FormViewController { let amount = Decimal(raw) guard AdamantUtilities.validateAmount(amount: amount) else { - dialogService.showError(withMessage: String.adamantLocalized.transfer.amountZeroError) + dialogService.showWarning(withMessage: String.adamantLocalized.transfer.amountZeroError) return } guard AdamantUtilities.validateAdamantAddress(address: recipient) else { - dialogService.showError(withMessage: String.adamantLocalized.transfer.addressValidationError) + dialogService.showWarning(withMessage: String.adamantLocalized.transfer.addressValidationError) return } guard amount <= Decimal(maxToTransfer) else { - dialogService.showError(withMessage: String.adamantLocalized.transfer.amountTooHigh) + dialogService.showWarning(withMessage: String.adamantLocalized.transfer.amountTooHigh) return } diff --git a/Adamant/Stories/Chats/NewChatViewController.swift b/Adamant/Stories/Chats/NewChatViewController.swift index fff27c196..bb4de5340 100644 --- a/Adamant/Stories/Chats/NewChatViewController.swift +++ b/Adamant/Stories/Chats/NewChatViewController.swift @@ -198,7 +198,7 @@ class NewChatViewController: FormViewController { } case .notFound: - self.dialogService.showError(withMessage: String.localizedStringWithFormat(String.adamantLocalized.newChat.addressNotFoundFormat, address)) + self.dialogService.showWarning(withMessage: String.localizedStringWithFormat(String.adamantLocalized.newChat.addressNotFoundFormat, address)) case .serverError(let error): self.dialogService.showError(withMessage: String.localizedStringWithFormat(String.adamantLocalized.newChat.serverErrorFormat, String(describing: error))) @@ -306,7 +306,7 @@ extension NewChatViewController { extension NewChatViewController: QRCodeReaderViewControllerDelegate { func reader(_ reader: QRCodeReaderViewController, didScanResult result: QRCodeReaderResult) { guard let uri = AdamantUriTools.decode(uri: result.value) else { - dialogService.showError(withMessage: String.adamantLocalized.newChat.wrongQrError) + dialogService.showWarning(withMessage: String.adamantLocalized.newChat.wrongQrError) DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { reader.startScanning() } @@ -316,7 +316,7 @@ extension NewChatViewController: QRCodeReaderViewControllerDelegate { if startNewChat(with: uri) { dismiss(animated: true, completion: nil) } else { - dialogService.showError(withMessage: String.adamantLocalized.newChat.wrongQrError) + dialogService.showWarning(withMessage: String.adamantLocalized.newChat.wrongQrError) DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { reader.startScanning() } @@ -347,9 +347,9 @@ extension NewChatViewController: UINavigationControllerDelegate, UIImagePickerCo } } - dialogService.showError(withMessage: String.adamantLocalized.newChat.wrongQrError) + dialogService.showWarning(withMessage: String.adamantLocalized.newChat.wrongQrError) } else { - dialogService.showError(withMessage: String.adamantLocalized.login.noQrError) + dialogService.showWarning(withMessage: String.adamantLocalized.login.noQrError) } } } diff --git a/Adamant/Stories/Login/LoginViewController+QR.swift b/Adamant/Stories/Login/LoginViewController+QR.swift index 3aab38912..3d573c353 100644 --- a/Adamant/Stories/Login/LoginViewController+QR.swift +++ b/Adamant/Stories/Login/LoginViewController+QR.swift @@ -84,7 +84,7 @@ extension LoginViewController { extension LoginViewController: QRCodeReaderViewControllerDelegate { func reader(_ reader: QRCodeReaderViewController, didScanResult result: QRCodeReaderResult) { guard AdamantUtilities.validateAdamantPassphrase(passphrase: result.value) else { - dialogService.showError(withMessage: String.adamantLocalized.login.wrongQrError) + dialogService.showWarning(withMessage: String.adamantLocalized.login.wrongQrError) DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { reader.startScanning() } @@ -117,9 +117,9 @@ extension LoginViewController: UINavigationControllerDelegate, UIImagePickerCont } } - dialogService.showError(withMessage: String.adamantLocalized.login.wrongQrError) + dialogService.showWarning(withMessage: String.adamantLocalized.login.wrongQrError) } else { - dialogService.showError(withMessage: String.adamantLocalized.login.noQrError) + dialogService.showWarning(withMessage: String.adamantLocalized.login.noQrError) } } } diff --git a/Adamant/Stories/Login/LoginViewController.swift b/Adamant/Stories/Login/LoginViewController.swift index ec56a030b..825b2e7ad 100644 --- a/Adamant/Stories/Login/LoginViewController.swift +++ b/Adamant/Stories/Login/LoginViewController.swift @@ -306,7 +306,7 @@ class LoginViewController: FormViewController { extension LoginViewController { func loginWith(passphrase: String) { guard AdamantUtilities.validateAdamantPassphrase(passphrase: passphrase) else { - dialogService.showError(withMessage: AccountServiceError.wrongPassphrase.localized) + dialogService.showWarning(withMessage: AccountServiceError.wrongPassphrase.localized) return } From ed1f75142366ccd572caa6347bfd82e25dade4a3 Mon Sep 17 00:00:00 2001 From: Pavel Anokhov Date: Fri, 1 Jun 2018 13:19:13 +0300 Subject: [PATCH 29/34] DialogServcie.showError(withMessage: , error: ) now sends detailed error description in email. String(describing: ) error refactored. --- Adamant/AppDelegate.swift | 2 +- .../Assets/l18n/en.lproj/Localizable.strings | 5 +++- .../Assets/l18n/ru.lproj/Localizable.strings | Bin 45170 -> 45692 bytes Adamant/Helpers/String+localized.swift | 4 +-- Adamant/ServiceProtocols/AccountService.swift | 2 +- Adamant/ServiceProtocols/ApiService.swift | 2 +- Adamant/ServiceProtocols/DialogService.swift | 2 +- Adamant/Services/AdamantAccountService.swift | 2 +- Adamant/Services/AdamantDialogService.swift | 27 ++++++++++++------ .../ApiService/AdamantApi+Transactions.swift | 1 - .../Account/TransferViewController.swift | 6 ++-- .../Chats/ChatViewController+MessageKit.swift | 6 ++-- .../Stories/Chats/NewChatViewController.swift | 14 ++++++++- .../Login/LoginViewController+Pinpad.swift | 2 +- .../Stories/Login/LoginViewController.swift | 4 +-- .../Settings/QRGeneratorViewController.swift | 4 +-- .../SettingsViewController+StayIn.swift | 2 +- 17 files changed, 54 insertions(+), 31 deletions(-) diff --git a/Adamant/AppDelegate.swift b/Adamant/AppDelegate.swift index c4f48d37b..8613c5ec7 100644 --- a/Adamant/AppDelegate.swift +++ b/Adamant/AppDelegate.swift @@ -245,7 +245,7 @@ extension AppDelegate { func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) { if let service = container.resolve(DialogService.self) { - service.showError(withMessage: String.localizedStringWithFormat(String.adamantLocalized.notifications.registerRemotesError, error.localizedDescription)) + service.showError(withMessage: String.localizedStringWithFormat(String.adamantLocalized.notifications.registerRemotesError, error.localizedDescription), error: error) } } } diff --git a/Adamant/Assets/l18n/en.lproj/Localizable.strings b/Adamant/Assets/l18n/en.lproj/Localizable.strings index 60e16067e..c0e72c25d 100644 --- a/Adamant/Assets/l18n/en.lproj/Localizable.strings +++ b/Adamant/Assets/l18n/en.lproj/Localizable.strings @@ -135,7 +135,10 @@ "Error.Mail.Title" = "I have error in my iOS app"; /* Error messge body for support email */ -"Error.Mail.Body" = "Hello,\nI have this error:\n\n%@\n\nMy device info:\n%2"; +"Error.Mail.Body" = "Hello,\nI have this error:\n\n%@\n\nMy device 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%@"; /* 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"; diff --git a/Adamant/Assets/l18n/ru.lproj/Localizable.strings b/Adamant/Assets/l18n/ru.lproj/Localizable.strings index f7c7c90e0f2064b81990c03658a446b214484320..c4169c9f823b4ac76bce3f307d04268d2f6bf2ad 100644 GIT binary patch delta 339 zcmezLfa%W@rVUS=B$ZhlS)5oLSnOCV!7L*n%Zed}A#d_U0pZDZj#86VoOEP$7!(-F z88R747&3sY6oynFD-p=g0kTpiPE@Ve0}6)&B~uxSfLMVc4JcW}Pz+S2z@WyR z7$jT7kPpP_Ksf~<&H(d3s&av{#Xy+IkPc*lEG!1n$w2u$poPe$pji);OPPGtLR1%| z2joDI&6qmMfa1wO6Fq_Er7`4B_IJ``*JE%2ij+)#C@Ly14-5ng79$o@U~qs;5@0c5 eF<`L)Vq2hy;^f&*o|A7l@d#rHu+7rW;kf`yBtiH9 delta 46 zcmezKgz3`*rVUS=7$qj(ml5YwV6b7aV=-m1napn{%5KVH#Sp`gH~FG~@Mb$#yIcTy Ct_}nM diff --git a/Adamant/Helpers/String+localized.swift b/Adamant/Helpers/String+localized.swift index e4fd13557..931457f0e 100644 --- a/Adamant/Helpers/String+localized.swift +++ b/Adamant/Helpers/String+localized.swift @@ -34,8 +34,8 @@ extension String { static let noInternetNotificationBoby = NSLocalizedString("Shared.NoInternet.Body", comment: "Shared alert notification: body message for no internet connection.") static let emailErrorMessageTitle = NSLocalizedString("Error.Mail.Title", comment: "Error messge title for support email") - static let emailErrorMessageBody = NSLocalizedString("Error.Mail.Body", comment: "SError messge body for support email") - + static let emailErrorMessageBody = NSLocalizedString("Error.Mail.Body", comment: "Error messge body for support email") + static let emailErrorMessageBodyWithDescription = NSLocalizedString("Error.Mail.Body.Detailed", comment: "Error messge body for support email, with detailed error description. Where first %@ - error's short message, second %@ - detailed description, third %@ - deviceInfo") } private init() { } diff --git a/Adamant/ServiceProtocols/AccountService.swift b/Adamant/ServiceProtocols/AccountService.swift index 09e4a3c41..6d2421bb9 100644 --- a/Adamant/ServiceProtocols/AccountService.swift +++ b/Adamant/ServiceProtocols/AccountService.swift @@ -49,7 +49,7 @@ enum AccountServiceResult { case failure(AccountServiceError) } -enum AccountServiceError { +enum AccountServiceError: Error { case userNotLogged case invalidPassphrase case wrongPassphrase diff --git a/Adamant/ServiceProtocols/ApiService.swift b/Adamant/ServiceProtocols/ApiService.swift index 962c42516..0e50b5545 100644 --- a/Adamant/ServiceProtocols/ApiService.swift +++ b/Adamant/ServiceProtocols/ApiService.swift @@ -36,7 +36,7 @@ enum ApiServiceError: Error { if let apiError = error as? ApiServiceError { message = apiError.localized } else if let error = error { - message = String(describing: error) + message = error.localizedDescription } else { message = msg } diff --git a/Adamant/ServiceProtocols/DialogService.swift b/Adamant/ServiceProtocols/DialogService.swift index 07c8b8dae..a6708d9bb 100644 --- a/Adamant/ServiceProtocols/DialogService.swift +++ b/Adamant/ServiceProtocols/DialogService.swift @@ -88,7 +88,7 @@ protocol DialogService: class { func dismissProgress() func showSuccess(withMessage: String) func showWarning(withMessage: String) - func showError(withMessage: String) + func showError(withMessage: String, error: Error?) func showNoConnectionNotification() func dissmisNoConnectionNotification() diff --git a/Adamant/Services/AdamantAccountService.swift b/Adamant/Services/AdamantAccountService.swift index a6b17e915..34da8a1cf 100644 --- a/Adamant/Services/AdamantAccountService.swift +++ b/Adamant/Services/AdamantAccountService.swift @@ -179,7 +179,7 @@ extension AdamantAccountService: AccountService { self?.setState(.loggedIn) case .failure(let error): - print("Error update account: \(String(describing: error))") + print("Error update account: \(error.localized))") } } } diff --git a/Adamant/Services/AdamantDialogService.swift b/Adamant/Services/AdamantDialogService.swift index 2fa97a3f2..abc9bf0cd 100644 --- a/Adamant/Services/AdamantDialogService.swift +++ b/Adamant/Services/AdamantDialogService.swift @@ -95,7 +95,7 @@ extension AdamantDialogService { FTIndicator.showError(withMessage: message) } - func showError(withMessage message: String) { + func showError(withMessage message: String, error: Error? = nil) { if Thread.isMainThread { FTIndicator.dismissProgress() } else { @@ -115,8 +115,10 @@ extension AdamantDialogService { let supportBtn = PMAlertAction(title: AdamantResources.iosAppSupportEmail, style: .default) { DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in - print("Send mail") - + guard let presenter = self else { + return + } + let mailVC = MFMailComposeViewController() mailVC.mailComposeDelegate = self?.mailDelegate mailVC.setToRecipients([AdamantResources.iosAppSupportEmail]) @@ -125,14 +127,21 @@ extension AdamantDialogService { let systemVersion = UIDevice.current.systemVersion let model = AdamantUtilities.deviceModelCode let deviceInfo = "Model: \(model)\n" + "iOS: \(systemVersion)\n" + "App version: \(AdamantUtilities.applicationVersion)" - - let body = String(format: String.adamantLocalized.alert.emailErrorMessageBody, message, deviceInfo) - + + let body: String + + if let error = error { + let errorDescription = String(describing: error) + body = String(format: String.adamantLocalized.alert.emailErrorMessageBodyWithDescription, message, errorDescription, deviceInfo) + } else { + body = String(format: String.adamantLocalized.alert.emailErrorMessageBody, message, deviceInfo) + } + mailVC.setMessageBody(body, isHTML: false) - self?.present(mailVC, animated: true, completion: nil) + presenter.present(mailVC, animated: true, completion: nil) } - }) + } supportBtn.titleLabel?.font = UIFont.adamantPrimary(size: 16) supportBtn.setTitleColor(UIColor(hex: "#00B6FF"), for: .normal) @@ -214,7 +223,7 @@ extension AdamantDialogService { self?.present(vc, animated: true, completion: completion) case .failure(error: let error): - self?.showError(withMessage: String(describing: error)) + self?.showError(withMessage: error.localizedDescription, error: error) } }) diff --git a/Adamant/Services/ApiService/AdamantApi+Transactions.swift b/Adamant/Services/ApiService/AdamantApi+Transactions.swift index 107ae7c35..70dcd2d08 100644 --- a/Adamant/Services/ApiService/AdamantApi+Transactions.swift +++ b/Adamant/Services/ApiService/AdamantApi+Transactions.swift @@ -71,7 +71,6 @@ extension AdamantApiService { switch serverResponse { case .success(let response): if let collection = response.collection { - print("Recive \(collection.count) trantaction(s)") completion(.success(collection)) } else { let error = AdamantApiService.translateServerError(response.error) diff --git a/Adamant/Stories/Account/TransferViewController.swift b/Adamant/Stories/Account/TransferViewController.swift index 562f915fc..e5bfe9601 100644 --- a/Adamant/Stories/Account/TransferViewController.swift +++ b/Adamant/Stories/Account/TransferViewController.swift @@ -314,14 +314,14 @@ class TransferViewController: FormViewController { } case .failure(let error): - dialogService.showError(withMessage: String(describing: error)) + dialogService.showError(withMessage: error.localized, error: error) } } - case .failure(_): - dialogService.showError(withMessage: String.adamantLocalized.transfer.accountNotFound) + case .failure(let error): + dialogService.showError(withMessage: String.adamantLocalized.transfer.accountNotFound, error: error) } } }) diff --git a/Adamant/Stories/Chats/ChatViewController+MessageKit.swift b/Adamant/Stories/Chats/ChatViewController+MessageKit.swift index ce1a17e05..9009acece 100644 --- a/Adamant/Stories/Chats/ChatViewController+MessageKit.swift +++ b/Adamant/Stories/Chats/ChatViewController+MessageKit.swift @@ -152,11 +152,11 @@ extension ChatViewController: MessageInputBarDelegate { case .dependencyError(let error): message = String.localizedStringWithFormat(String.adamantLocalized.chat.internalErrorFormat, error) case .internalError(let error): - message = String.localizedStringWithFormat(String.adamantLocalized.chat.internalErrorFormat, String(describing: error)) + message = String.localizedStringWithFormat(String.adamantLocalized.chat.internalErrorFormat, error.localizedDescription) case .notLogged: message = String.localizedStringWithFormat(String.adamantLocalized.chat.internalErrorFormat, "User not logged") case .serverError(let error): - message = String.localizedStringWithFormat(String.adamantLocalized.chat.serverErrorFormat, String(describing: error)) + message = String.localizedStringWithFormat(String.adamantLocalized.chat.serverErrorFormat, error.localizedDescription) case .networkError: message = String.adamantLocalized.chat.noNetwork @@ -178,7 +178,7 @@ extension ChatViewController: MessageInputBarDelegate { } // TODO: Log this - self.dialogService.showError(withMessage: message) + self.dialogService.showError(withMessage: message, error: error) } }) diff --git a/Adamant/Stories/Chats/NewChatViewController.swift b/Adamant/Stories/Chats/NewChatViewController.swift index bb4de5340..83cdb0454 100644 --- a/Adamant/Stories/Chats/NewChatViewController.swift +++ b/Adamant/Stories/Chats/NewChatViewController.swift @@ -200,8 +200,20 @@ class NewChatViewController: FormViewController { case .notFound: self.dialogService.showWarning(withMessage: String.localizedStringWithFormat(String.adamantLocalized.newChat.addressNotFoundFormat, address)) + case .serverError(let error as ApiServiceError): + let message: String + switch error { + case .networkError(let internalError): + message = internalError.localizedDescription + + default: + message = error.localized + } + + self.dialogService.showError(withMessage: String.localizedStringWithFormat(String.adamantLocalized.newChat.serverErrorFormat, message), error: error) + case .serverError(let error): - self.dialogService.showError(withMessage: String.localizedStringWithFormat(String.adamantLocalized.newChat.serverErrorFormat, String(describing: error))) + self.dialogService.showError(withMessage: String.localizedStringWithFormat(String.adamantLocalized.newChat.serverErrorFormat, error.localizedDescription), error: error) } } } diff --git a/Adamant/Stories/Login/LoginViewController+Pinpad.swift b/Adamant/Stories/Login/LoginViewController+Pinpad.swift index 68fae3881..6c953b331 100644 --- a/Adamant/Stories/Login/LoginViewController+Pinpad.swift +++ b/Adamant/Stories/Login/LoginViewController+Pinpad.swift @@ -67,7 +67,7 @@ extension LoginViewController { } case .failure(let error): - self?.dialogService.showError(withMessage: error.localized) + self?.dialogService.showError(withMessage: error.localized, error: error) if let pinpad = self?.presentedViewController as? PinpadViewController { pinpad.clearPin() diff --git a/Adamant/Stories/Login/LoginViewController.swift b/Adamant/Stories/Login/LoginViewController.swift index 825b2e7ad..6ae07051e 100644 --- a/Adamant/Stories/Login/LoginViewController.swift +++ b/Adamant/Stories/Login/LoginViewController.swift @@ -350,7 +350,7 @@ extension LoginViewController { self?.loginIntoExistingAccount(passphrase: passphrase) case .failure(let error): - self?.dialogService.showError(withMessage: error.localized) + self?.dialogService.showError(withMessage: error.localized, error: error) } }) } @@ -368,7 +368,7 @@ extension LoginViewController { self?.dialogService.dismissProgress() case .failure(let error): - self?.dialogService.showError(withMessage: error.localized) + self?.dialogService.showError(withMessage: error.localized, error: error) } }) } diff --git a/Adamant/Stories/Settings/QRGeneratorViewController.swift b/Adamant/Stories/Settings/QRGeneratorViewController.swift index 99b5adab5..884692747 100644 --- a/Adamant/Stories/Settings/QRGeneratorViewController.swift +++ b/Adamant/Stories/Settings/QRGeneratorViewController.swift @@ -105,7 +105,7 @@ class QRGeneratorViewController: FormViewController { if completed { self?.dialogService.showToastMessage(String.adamantLocalized.alert.done) } else if let error = error { - self?.dialogService.showToastMessage(String(describing: error)) + self?.dialogService.showToastMessage(error.localizedDescription) } } self?.present(vc, animated: true, completion: nil) @@ -187,7 +187,7 @@ extension QRGeneratorViewController { setQr(image: qr) case .failure(let error): - dialogService.showError(withMessage: String.localizedStringWithFormat(String.adamantLocalized.qrGenerator.internalError, String(describing: error))) + dialogService.showError(withMessage: String.localizedStringWithFormat(String.adamantLocalized.qrGenerator.internalError, error.localizedDescription), error: error) } } diff --git a/Adamant/Stories/Settings/SettingsViewController+StayIn.swift b/Adamant/Stories/Settings/SettingsViewController+StayIn.swift index 51c5f30dc..4c95bfd83 100644 --- a/Adamant/Stories/Settings/SettingsViewController+StayIn.swift +++ b/Adamant/Stories/Settings/SettingsViewController+StayIn.swift @@ -150,7 +150,7 @@ extension SettingsViewController: PinpadViewControllerDelegate { } case .failure(let error): - self?.dialogService.showError(withMessage: error.localized) + self?.dialogService.showError(withMessage: error.localized, error: error) } } From d14a559fb99b6808fdb822f85121d1ac14ae5bcf Mon Sep 17 00:00:00 2001 From: Pavel Anokhov Date: Fri, 1 Jun 2018 16:41:10 +0300 Subject: [PATCH 30/34] Transaction details export simplified. --- .../TransactionDetailsViewController.swift | 33 +------------------ 1 file changed, 1 insertion(+), 32 deletions(-) diff --git a/Adamant/Stories/Transactions/TransactionDetailsViewController.swift b/Adamant/Stories/Transactions/TransactionDetailsViewController.swift index 0a268ef08..2aab84408 100644 --- a/Adamant/Stories/Transactions/TransactionDetailsViewController.swift +++ b/Adamant/Stories/Transactions/TransactionDetailsViewController.swift @@ -139,43 +139,12 @@ extension TransactionDetailsViewController: UITableViewDataSource, UITableViewDe } guard let cell = tableView.cellForRow(at: indexPath), - let row = Row(rawValue: indexPath.row), let details = cell.detailTextLabel?.text else { tableView.deselectRow(at: indexPath, animated: true) return } - let payload: String - switch row { - case .amount: - payload = "\(row.localized): \(details)" - - case .date: - payload = "\(row.localized): \(details)" - - case .confirmations: - payload = "\(row.localized): \(details)" - - case .fee: - payload = "\(row.localized): \(details)" - - case .transactionNumber: - payload = "\(row.localized): \(details)" - - case .from: - payload = "\(row.localized): \(details)" - - case .to: - payload = "\(row.localized): \(details)" - - case .block: - payload = "\(row.localized): \(details)" - - case .openInExplorer: - payload = "" - } - - dialogService.presentShareAlertFor(string: payload, + dialogService.presentShareAlertFor(string: details, types: [.copyToPasteboard, .share], excludedActivityTypes: nil, animated: true) { From 3a619c0382faf11256b5a602edf47afaccf8ff3a Mon Sep 17 00:00:00 2001 From: Pavel Anokhov Date: Fri, 1 Jun 2018 16:57:05 +0300 Subject: [PATCH 31/34] Transactions list and details scenes fixes ru localization file encoding fixed --- .../Assets/l18n/en.lproj/Localizable.strings | 6 ++++++ .../Assets/l18n/ru.lproj/Localizable.strings | Bin 45692 -> 28976 bytes .../TransactionDetailsViewController.swift | 7 +++++++ .../TransactionsViewController.swift | 9 +++++++++ 4 files changed, 22 insertions(+) diff --git a/Adamant/Assets/l18n/en.lproj/Localizable.strings b/Adamant/Assets/l18n/en.lproj/Localizable.strings index c0e72c25d..f3ad99734 100644 --- a/Adamant/Assets/l18n/en.lproj/Localizable.strings +++ b/Adamant/Assets/l18n/en.lproj/Localizable.strings @@ -374,6 +374,12 @@ /* Main tab bar: Settings page */ "Tabs.Settings" = "Settings"; +/* TransactionList: scene title */ +"TransactionListScene.Title" = "Transactions"; + +/* Transaction details: scene title */ +"TransactionDetailsScene.Title" = "Details"; + /* Transaction details: amount row. */ "TransactionDetailsScene.Row.Amount" = "Amount"; diff --git a/Adamant/Assets/l18n/ru.lproj/Localizable.strings b/Adamant/Assets/l18n/ru.lproj/Localizable.strings index c4169c9f823b4ac76bce3f307d04268d2f6bf2ad..bf66843430d34905cc5ecdc17dd593a81e521c5b 100644 GIT binary patch literal 28976 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 Date: Fri, 1 Jun 2018 17:04:02 +0300 Subject: [PATCH 32/34] Pushes removed. Wait for v0.4 --- .../NotificationsViewController.swift | 35 +------------------ 1 file changed, 1 insertion(+), 34 deletions(-) diff --git a/Adamant/Stories/Settings/NotificationsViewController.swift b/Adamant/Stories/Settings/NotificationsViewController.swift index ed007e9cf..e110e09b8 100644 --- a/Adamant/Stories/Settings/NotificationsViewController.swift +++ b/Adamant/Stories/Settings/NotificationsViewController.swift @@ -130,7 +130,7 @@ class NotificationsViewController: FormViewController { $0.tag = Rows.notificationsMode.tag $0.title = Rows.notificationsMode.localized $0.selectorTitle = Rows.notificationsMode.localized - $0.options = [.disabled, .backgroundFetch, .push] + $0.options = [.disabled, .backgroundFetch] $0.value = notificationsService.notificationsMode }.onChange({ [weak self] row in guard let mode = row.value else { @@ -141,39 +141,6 @@ class NotificationsViewController: FormViewController { }).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 - }).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) - }).cellUpdate({ (cell, _) in - cell.accessoryType = .disclosureIndicator - }) } // MARK: Logic From 99f54f430263ff2519e2016f4bae5e48be668de7 Mon Sep 17 00:00:00 2001 From: Pavel Anokhov Date: Fri, 1 Jun 2018 17:05:38 +0300 Subject: [PATCH 33/34] Version. --- Adamant/Info.plist | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Adamant/Info.plist b/Adamant/Info.plist index cc3c97d3a..65b5ebe3d 100644 --- a/Adamant/Info.plist +++ b/Adamant/Info.plist @@ -17,9 +17,9 @@ CFBundlePackageType APPL CFBundleShortVersionString - 0.3.7 + 0.3.8 CFBundleVersion - 25 + 26 LSRequiresIPhoneOS NSCameraUsageDescription From c58c9e51a9c7e18bc6b8e764be6a8b5ebc54cadc Mon Sep 17 00:00:00 2001 From: Pavel Anokhov Date: Fri, 1 Jun 2018 17:11:19 +0300 Subject: [PATCH 34/34] readme update --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ba0d03393..c1a0cd937 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Adamant-iOS -iOS native client for ADAMANT Messenger. Available at App Store *(now it's in TestFlight)*. You can use this repository to build your own ADAMANT iOS application. +iOS native client for ADAMANT Messenger. Available at App Store *(via TestFlight)*. You can use this repository to build your own ADAMANT iOS application. ADAMANT is the most secure and anonymous messenger, encrypted with Blockchain. @@ -12,7 +12,7 @@ Highlights: - The only one which is Blockchain-powered - Integrated token transfers -Read more about ADAMANT at https://adamant.im +Read more about ADAMANT at [adamant.im](https://adamant.im) ## Build Setup