diff --git a/Adamant.xcodeproj/project.pbxproj b/Adamant.xcodeproj/project.pbxproj index dfae408fc..f5633c1ba 100644 --- a/Adamant.xcodeproj/project.pbxproj +++ b/Adamant.xcodeproj/project.pbxproj @@ -7,14 +7,18 @@ objects = { /* Begin PBXBuildFile section */ - 3A64FAA627BA67BF007D5588 /* AdamantSecret.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A64FAA527BA67BF007D5588 /* AdamantSecret.swift */; }; - 3A64FAA727BA67BF007D5588 /* AdamantSecret.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A64FAA527BA67BF007D5588 /* AdamantSecret.swift */; }; - 3A64FAA827BA67BF007D5588 /* AdamantSecret.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A64FAA527BA67BF007D5588 /* AdamantSecret.swift */; }; - 3A64FAA927BA67BF007D5588 /* AdamantSecret.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A64FAA527BA67BF007D5588 /* AdamantSecret.swift */; }; 3A8875EF27BBF38D00436195 /* Parchment in Frameworks */ = {isa = PBXBuildFile; productRef = 3A8875EE27BBF38D00436195 /* Parchment */; }; 3AA2D5F7280EADE3000ED971 /* SocketService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AA2D5F6280EADE3000ED971 /* SocketService.swift */; }; 3AA2D5FA280EAF5D000ED971 /* AdamantSocketService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AA2D5F9280EAF5D000ED971 /* AdamantSocketService.swift */; }; + 3AD95B96284121E100767396 /* HeaderReusableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AD95B95284121E100767396 /* HeaderReusableView.swift */; }; + 3AE89DD92837F5BF0051D3A9 /* ChatRooms.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AE89DD82837F5BF0051D3A9 /* ChatRooms.swift */; }; + 3AE89DDB2837F5D30051D3A9 /* ChatRoomsChats.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AE89DDA2837F5D30051D3A9 /* ChatRoomsChats.swift */; }; 3C06931576393125C61FB8F6 /* Pods_Adamant.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 33975C0D891698AA7E74EBCC /* Pods_Adamant.framework */; }; + 41935848287841E20083363B /* MacOSDeterminer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 41935847287841E20083363B /* MacOSDeterminer.swift */; }; + 41E857A42847ADA200BC0783 /* AdamantSecret.swift in Sources */ = {isa = PBXBuildFile; fileRef = 41E857A32847ADA200BC0783 /* AdamantSecret.swift */; }; + 41E857A52847ADA200BC0783 /* AdamantSecret.swift in Sources */ = {isa = PBXBuildFile; fileRef = 41E857A32847ADA200BC0783 /* AdamantSecret.swift */; }; + 41E857A62847ADA200BC0783 /* AdamantSecret.swift in Sources */ = {isa = PBXBuildFile; fileRef = 41E857A32847ADA200BC0783 /* AdamantSecret.swift */; }; + 41E857A72847ADA200BC0783 /* AdamantSecret.swift in Sources */ = {isa = PBXBuildFile; fileRef = 41E857A32847ADA200BC0783 /* AdamantSecret.swift */; }; 553B1284281C6EE100FFF24C /* GCDUtilites.swift in Sources */ = {isa = PBXBuildFile; fileRef = 553B1283281C6EE100FFF24C /* GCDUtilites.swift */; }; 6403F5DB2272389800D58779 /* (null) in Sources */ = {isa = PBXBuildFile; }; 6403F5DE22723C6800D58779 /* DashMainnet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6403F5DD22723C6800D58779 /* DashMainnet.swift */; }; @@ -171,7 +175,7 @@ E9079A7D229DEF9C0022CA0D /* UserNotificationsUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E957E112229B04280019732A /* UserNotificationsUI.framework */; }; E9079A80229DEF9C0022CA0D /* NotificationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9079A7F229DEF9C0022CA0D /* NotificationViewController.swift */; }; E9079A83229DEF9C0022CA0D /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = E9079A81229DEF9C0022CA0D /* MainInterface.storyboard */; }; - E9079A87229DEF9C0022CA0D /* MessageNotificationContentExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = E9079A7B229DEF9C0022CA0D /* MessageNotificationContentExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + E9079A87229DEF9C0022CA0D /* MessageNotificationContentExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = E9079A7B229DEF9C0022CA0D /* MessageNotificationContentExtension.appex */; platformFilter = ios; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; E9079A8C229DF19A0022CA0D /* AdamantAvatarService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 648BCA6A213D37A800875EB5 /* AdamantAvatarService.swift */; }; E9079A8D229DF19A0022CA0D /* KeychainStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = E905D39A2048A9BD00DDB504 /* KeychainStore.swift */; }; E9079A8E229DF1A40022CA0D /* NativeAdamantCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = E96D64D52295C9CA00CA5587 /* NativeAdamantCore.swift */; }; @@ -321,7 +325,7 @@ E957E12F229B10F80019732A /* UserNotificationsUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E957E112229B04280019732A /* UserNotificationsUI.framework */; }; E957E132229B10F80019732A /* NotificationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E957E131229B10F80019732A /* NotificationViewController.swift */; }; E957E135229B10F80019732A /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = E957E133229B10F80019732A /* MainInterface.storyboard */; }; - E957E139229B10F80019732A /* TransferNotificationContentExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = E957E12D229B10F80019732A /* TransferNotificationContentExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + E957E139229B10F80019732A /* TransferNotificationContentExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = E957E12D229B10F80019732A /* TransferNotificationContentExtension.appex */; platformFilter = ios; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; E957E13E229BFEB60019732A /* AdamantNotificationKeys.swift in Sources */ = {isa = PBXBuildFile; fileRef = E957E123229B0C330019732A /* AdamantNotificationKeys.swift */; }; E957E13F229BFEBB0019732A /* AdamantNotificationKeys.swift in Sources */ = {isa = PBXBuildFile; fileRef = E957E123229B0C330019732A /* AdamantNotificationKeys.swift */; }; E957E140229BFF210019732A /* AdamantBalanceFormat.swift in Sources */ = {isa = PBXBuildFile; fileRef = E940086F2114EA6800CD2D67 /* AdamantBalanceFormat.swift */; }; @@ -407,7 +411,7 @@ E96D64D22295C82B00CA5587 /* TransactionSend.json in Resources */ = {isa = PBXBuildFile; fileRef = E95F85B9200A4DC90070534A /* TransactionSend.json */; }; E96D64D62295C9CB00CA5587 /* NativeAdamantCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = E96D64D52295C9CA00CA5587 /* NativeAdamantCore.swift */; }; E96D64DE2295CD4700CA5587 /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = E96D64DD2295CD4700CA5587 /* NotificationService.swift */; }; - E96D64E22295CD4700CA5587 /* NotificationServiceExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = E96D64DB2295CD4700CA5587 /* NotificationServiceExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + E96D64E22295CD4700CA5587 /* NotificationServiceExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = E96D64DB2295CD4700CA5587 /* NotificationServiceExtension.appex */; platformFilter = ios; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; E96D64E72295CF4600CA5587 /* KeychainStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = E905D39A2048A9BD00DDB504 /* KeychainStore.swift */; }; E96E86B821679C120061F80A /* EthTransactionDetailsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E96E86B721679C120061F80A /* EthTransactionDetailsViewController.swift */; }; E971591A21681D6900A5F904 /* TransactionStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = E971591921681D6900A5F904 /* TransactionStatus.swift */; }; @@ -609,9 +613,13 @@ /* Begin PBXFileReference section */ 33975C0D891698AA7E74EBCC /* Pods_Adamant.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Adamant.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 36AB8CE9537B3B873972548B /* Pods_AdmCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_AdmCore.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 3A64FAA527BA67BF007D5588 /* AdamantSecret.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AdamantSecret.swift; sourceTree = ""; }; 3AA2D5F6280EADE3000ED971 /* SocketService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SocketService.swift; sourceTree = ""; }; 3AA2D5F9280EAF5D000ED971 /* AdamantSocketService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdamantSocketService.swift; sourceTree = ""; }; + 3AD95B95284121E100767396 /* HeaderReusableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeaderReusableView.swift; sourceTree = ""; }; + 3AE89DD82837F5BF0051D3A9 /* ChatRooms.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatRooms.swift; sourceTree = ""; }; + 3AE89DDA2837F5D30051D3A9 /* ChatRoomsChats.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatRoomsChats.swift; sourceTree = ""; }; + 41935847287841E20083363B /* MacOSDeterminer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MacOSDeterminer.swift; sourceTree = ""; }; + 41E857A32847ADA200BC0783 /* AdamantSecret.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AdamantSecret.swift; sourceTree = ""; }; 4A4D67BD3DC89C07D1351248 /* Pods-AdmCore.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AdmCore.release.xcconfig"; path = "Target Support Files/Pods-AdmCore/Pods-AdmCore.release.xcconfig"; sourceTree = ""; }; 553B1283281C6EE100FFF24C /* GCDUtilites.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GCDUtilites.swift; sourceTree = ""; }; 6403F5DD22723C6800D58779 /* DashMainnet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashMainnet.swift; sourceTree = ""; }; @@ -1354,6 +1362,7 @@ E9256F5E2034C21100DE86E9 /* String+localized.swift */, E98FC34320F920BD00032D65 /* UIFont+adamant.swift */, 645AE06521E67D3300AD3623 /* UITextField+adamant.swift */, + 41935847287841E20083363B /* MacOSDeterminer.swift */, ); path = Helpers; sourceTree = ""; @@ -1622,6 +1631,7 @@ E95F85C5200A9B070070534A /* ChatTableViewCell.swift */, E95F85C6200A9B070070534A /* ChatTableViewCell.xib */, E974D167215D033C003AD7E8 /* ChatCell.swift */, + 3AD95B95284121E100767396 /* HeaderReusableView.swift */, E9240BF8215D813A00187B09 /* CustomCellDeleage.swift */, 649D6BF121C27D5C009E727B /* SearchResultsViewController.swift */, 6406D74821C7F06000196713 /* SearchResultsViewController.xib */, @@ -1715,7 +1725,7 @@ E9D664C222A0037600733F8A /* View */, E957E123229B0C330019732A /* AdamantNotificationKeys.swift */, E9771D802299602D0099AAC7 /* AdamantResources.swift */, - 3A64FAA527BA67BF007D5588 /* AdamantSecret.swift */, + 41E857A32847ADA200BC0783 /* AdamantSecret.swift */, E957E170229C56EE0019732A /* Shared.xcassets */, ); path = AdamantShared; @@ -1742,6 +1752,8 @@ E93EB0A220DA4CCA001F9601 /* Node.swift */, E965A53320B833A00041A3EA /* StateAsset.swift */, E965A53120B82C850041A3EA /* StateType.swift */, + 3AE89DD82837F5BF0051D3A9 /* ChatRooms.swift */, + 3AE89DDA2837F5D30051D3A9 /* ChatRoomsChats.swift */, E9E7CDC920040CC200DFC4DB /* Transaction.swift */, E965A53520B8370C0041A3EA /* TransactionAsset.swift */, E9E7CDCB20040FDC00DFC4DB /* TransactionType.swift */, @@ -2359,7 +2371,7 @@ E9079A92229DF1B80022CA0D /* UIColor+hex.swift in Sources */, E9079A8D229DF19A0022CA0D /* KeychainStore.swift in Sources */, E9079AA4229DF6850022CA0D /* AdamantUtilities.swift in Sources */, - 3A64FAA927BA67BF007D5588 /* AdamantSecret.swift in Sources */, + 41E857A72847ADA200BC0783 /* AdamantSecret.swift in Sources */, E951466C22A691EF007564D7 /* String+utilites.swift in Sources */, E9079A8F229DF1A40022CA0D /* Crypto.swift in Sources */, E9079A90229DF1A40022CA0D /* Mnemonic.swift in Sources */, @@ -2399,6 +2411,7 @@ E9256F5F2034C21100DE86E9 /* String+localized.swift in Sources */, 6414C18E217DF43100373FA6 /* String+adamant.swift in Sources */, E908472A2196FEA80095825D /* RichMessageTransaction+CoreDataClass.swift in Sources */, + 41E857A42847ADA200BC0783 /* AdamantSecret.swift in Sources */, 64A223D620F760BB005157CB /* Localization.swift in Sources */, 64E1C82F222E95F6006C4DA7 /* DogeWallet.swift in Sources */, E9484B7D2285BAD9008E10F0 /* PrivateKeyGenerator.swift in Sources */, @@ -2509,7 +2522,6 @@ E9A03FD220DBC0F2007653A1 /* NodeEditorViewController.swift in Sources */, E96D64B82295BED700CA5587 /* ChatType.swift in Sources */, E9393FAA2055D03300EE6F30 /* AdamantMessage.swift in Sources */, - 3A64FAA627BA67BF007D5588 /* AdamantSecret.swift in Sources */, E96D64D62295C9CB00CA5587 /* NativeAdamantCore.swift in Sources */, E90A494D204DA932009F6A65 /* LocalAuthentication.swift in Sources */, E96D64C62295C3ED00CA5587 /* Mnemonic+extended.swift in Sources */, @@ -2536,6 +2548,7 @@ E9AA8BFA212C166600F9249F /* EthWalletService+Send.swift in Sources */, 6449BA68235CA0930033B936 /* ERC20WalletService.swift in Sources */, 644793C32166314A00FC4CF5 /* OnboardPage.swift in Sources */, + 3AE89DDB2837F5D30051D3A9 /* ChatRoomsChats.swift in Sources */, 64E1C831222E9617006C4DA7 /* DogeWalletService.swift in Sources */, E91947B22000246A001362F8 /* AdamantError.swift in Sources */, 3AA2D5F7280EADE3000ED971 /* SocketService.swift in Sources */, @@ -2590,6 +2603,7 @@ E91947AC20001A9A001362F8 /* ApiService.swift in Sources */, E96D64B72295BED700CA5587 /* DelegateVote.swift in Sources */, E993302221354BC300CD5200 /* EthWalletRoutes.swift in Sources */, + 3AE89DD92837F5BF0051D3A9 /* ChatRooms.swift in Sources */, E90055F520EBF5DA00D0CB2D /* AboutViewController.swift in Sources */, E965A53020B594120041A3EA /* AdamantApi+States.swift in Sources */, E96D64BB2295BED700CA5587 /* VotesAsset.swift in Sources */, @@ -2604,6 +2618,7 @@ E940086B2114A70600CD2D67 /* LskAccount.swift in Sources */, 6416B19D21AD7B92006089AC /* LskWalletRoutes.swift in Sources */, E9B3D3A1201FA26B0019EB36 /* AdamantAccountsProvider.swift in Sources */, + 3AD95B96284121E100767396 /* HeaderReusableView.swift in Sources */, E9FAE5DA203DBFEF008D3A6B /* Comparable+clamped.swift in Sources */, E94008802114EE2000CD2D67 /* AdmWallet.swift in Sources */, E9A03FDA20DC0B14007653A1 /* NodesSource.swift in Sources */, @@ -2653,6 +2668,7 @@ E90A4943204C5ED6009F6A65 /* EurekaPassphraseRow.swift in Sources */, E90847302196FEA80095825D /* ChatTransaction+CoreDataClass.swift in Sources */, E913C9081FFFA943001A83F7 /* AdamantCore.swift in Sources */, + 41935848287841E20083363B /* MacOSDeterminer.swift in Sources */, E9EC342120052ABB00C0E546 /* TransferViewControllerBase.swift in Sources */, 640EFA962558612400E9724B /* NotificationContent.swift in Sources */, E96D64B42295BED700CA5587 /* StateAsset.swift in Sources */, @@ -2738,7 +2754,7 @@ 64AE84622300012400F38FBD /* DashProvider.swift in Sources */, E957E16C229C53980019732A /* LskProvider.swift in Sources */, E9D664D022A009AB00733F8A /* AdamantLocalized.swift in Sources */, - 3A64FAA827BA67BF007D5588 /* AdamantSecret.swift in Sources */, + 41E857A62847ADA200BC0783 /* AdamantSecret.swift in Sources */, E957E14D229C3E530019732A /* Node.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -2781,7 +2797,7 @@ E957E0ED229982FF0019732A /* Keypair.swift in Sources */, 6423038324B26CAF004CF3FE /* ERC20Token.swift in Sources */, E957E0FB229AB9310019732A /* AdamantProvider.swift in Sources */, - 3A64FAA727BA67BF007D5588 /* AdamantSecret.swift in Sources */, + 41E857A52847ADA200BC0783 /* AdamantSecret.swift in Sources */, E957E157229C3F840019732A /* ExtensionsApi.swift in Sources */, E957E0F722999BD50019732A /* TransferBaseProvider.swift in Sources */, E9771D9622996FEB0099AAC7 /* StateType.swift in Sources */, @@ -2814,16 +2830,19 @@ /* Begin PBXTargetDependency section */ E9079A86229DEF9C0022CA0D /* PBXTargetDependency */ = { isa = PBXTargetDependency; + platformFilter = ios; target = E9079A7A229DEF9C0022CA0D /* MessageNotificationContentExtension */; targetProxy = E9079A85229DEF9C0022CA0D /* PBXContainerItemProxy */; }; E957E138229B10F80019732A /* PBXTargetDependency */ = { isa = PBXTargetDependency; + platformFilter = ios; target = E957E12C229B10F80019732A /* TransferNotificationContentExtension */; targetProxy = E957E137229B10F80019732A /* PBXContainerItemProxy */; }; E96D64E12295CD4700CA5587 /* PBXTargetDependency */ = { isa = PBXTargetDependency; + platformFilter = ios; target = E96D64DA2295CD4700CA5587 /* NotificationServiceExtension */; targetProxy = E96D64E02295CD4700CA5587 /* PBXContainerItemProxy */; }; @@ -2900,23 +2919,26 @@ CLANG_ENABLE_OBJC_WEAK = YES; CODE_SIGN_ENTITLEMENTS = MessageNotificationContentExtension/Debug.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 180; + CURRENT_PROJECT_VERSION = 202; DEVELOPMENT_TEAM = J2L77FMN46; INFOPLIST_FILE = MessageNotificationContentExtension/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 10.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2.4.0; + MARKETING_VERSION = 2.5.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "im.adamant.adamant-messenger-dev.MessageNotificationContentExtension"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = "ADAMANT Messenger Dev ext MessageNotification"; + "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "ADAMANT Messenger Dev ext MessageNotification Mac"; SKIP_INSTALL = YES; + SUPPORTS_MACCATALYST = NO; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; @@ -2929,22 +2951,25 @@ CODE_SIGN_ENTITLEMENTS = MessageNotificationContentExtension/Release.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 180; + CURRENT_PROJECT_VERSION = 202; DEVELOPMENT_TEAM = J2L77FMN46; INFOPLIST_FILE = MessageNotificationContentExtension/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 10.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2.4.0; + MARKETING_VERSION = 2.5.0; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "im.adamant.adamant-messenger.MessageNotificationContentExtension"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = "ADAMANT Messenger ext MessageNotification"; + "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "ADAMANT Messenger ext MessageNotification Mac"; SKIP_INSTALL = YES; + SUPPORTS_MACCATALYST = NO; SWIFT_COMPILATION_MODE = singlefile; SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 5.0; @@ -3076,22 +3101,25 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon_Debug; CODE_SIGN_ENTITLEMENTS = Adamant/Debug.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 180; + CURRENT_PROJECT_VERSION = 202; DEVELOPMENT_TEAM = J2L77FMN46; DISPLAY_NAME = ADM.Dev; EXCLUDED_SOURCE_FILE_NAMES = ""; INFOPLIST_FILE = Adamant/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 10.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.4.0; + MARKETING_VERSION = 2.5.0; PRODUCT_BUNDLE_IDENTIFIER = "im.adamant.adamant-messenger-dev"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = "e4233bbf-3705-44fe-95b0-e77475672c60"; PROVISIONING_PROFILE_SPECIFIER = "ADAMANT Messenger Dev"; + "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "ADAMANT Messenger Dev Mac"; + SUPPORTS_MACCATALYST = NO; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; @@ -3105,22 +3133,25 @@ CODE_SIGN_ENTITLEMENTS = Adamant/Release.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 180; + CURRENT_PROJECT_VERSION = 202; DEVELOPMENT_TEAM = J2L77FMN46; DISPLAY_NAME = Adamant; EXCLUDED_SOURCE_FILE_NAMES = Debug.xcassets; INFOPLIST_FILE = Adamant/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 10.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.4.0; + MARKETING_VERSION = 2.5.0; PRODUCT_BUNDLE_IDENTIFIER = "im.adamant.adamant-messenger"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = "bedd1b75-2f23-4a85-a0b2-14c424fcff42"; PROVISIONING_PROFILE_SPECIFIER = "ADAMANT Messenger"; + "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "ADAMANT Messenger Mac"; + SUPPORTS_MACCATALYST = NO; SWIFT_COMPILATION_MODE = singlefile; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; @@ -3133,23 +3164,26 @@ CLANG_ENABLE_OBJC_WEAK = YES; CODE_SIGN_ENTITLEMENTS = TransferNotificationContentExtension/Debug.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 180; + CURRENT_PROJECT_VERSION = 202; DEVELOPMENT_TEAM = J2L77FMN46; INFOPLIST_FILE = TransferNotificationContentExtension/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 10.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2.4.0; + MARKETING_VERSION = 2.5.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "im.adamant.adamant-messenger-dev.TransferNotificationContentExtension"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = "ADAMANT Messenger Dev ext TransferNotification"; + "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "ADAMANT Messenger Dev ext TransferNotification Mac"; SKIP_INSTALL = YES; + SUPPORTS_MACCATALYST = NO; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; @@ -3162,22 +3196,25 @@ CODE_SIGN_ENTITLEMENTS = TransferNotificationContentExtension/Release.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 180; + CURRENT_PROJECT_VERSION = 202; DEVELOPMENT_TEAM = J2L77FMN46; INFOPLIST_FILE = TransferNotificationContentExtension/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 10.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2.4.0; + MARKETING_VERSION = 2.5.0; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "im.adamant.adamant-messenger.TransferNotificationContentExtension"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = "ADAMANT Messenger dist ext TransferNotification NE"; + "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "ADAMANT Messenger dist ext TransferNotification Ma"; SKIP_INSTALL = YES; + SUPPORTS_MACCATALYST = NO; SWIFT_COMPILATION_MODE = singlefile; SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 5.0; @@ -3191,23 +3228,26 @@ CLANG_ENABLE_OBJC_WEAK = YES; CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/Debug.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 180; + CURRENT_PROJECT_VERSION = 202; DEVELOPMENT_TEAM = J2L77FMN46; INFOPLIST_FILE = NotificationServiceExtension/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 10.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2.4.0; + MARKETING_VERSION = 2.5.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "im.adamant.adamant-messenger-dev.NotificationServiceExtension"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = "ADAMANT Messenger Dev ext NotificationService"; + "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "ADAMANT Messenger Dev ext NotificationService Maco"; SKIP_INSTALL = YES; + SUPPORTS_MACCATALYST = NO; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; @@ -3220,22 +3260,25 @@ CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/Release.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 180; + CURRENT_PROJECT_VERSION = 202; DEVELOPMENT_TEAM = J2L77FMN46; INFOPLIST_FILE = NotificationServiceExtension/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 10.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2.4.0; + MARKETING_VERSION = 2.5.0; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "im.adamant.adamant-messenger.NotificationServiceExtension"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = "ADAMANT Messenger ext NotificationService"; + "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "ADAMANT Messenger ext NotificationService Mac"; SKIP_INSTALL = YES; + SUPPORTS_MACCATALYST = NO; SWIFT_COMPILATION_MODE = singlefile; SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 5.0; diff --git a/Adamant.xcodeproj/xcshareddata/xcschemes/Adamant.Release.xcscheme b/Adamant.xcodeproj/xcshareddata/xcschemes/Adamant.Release.xcscheme index 1dfe61d5f..bc3808e31 100644 --- a/Adamant.xcodeproj/xcshareddata/xcschemes/Adamant.Release.xcscheme +++ b/Adamant.xcodeproj/xcshareddata/xcschemes/Adamant.Release.xcscheme @@ -53,7 +53,7 @@ buildConfiguration = "Release" selectedDebuggerIdentifier = "" selectedLauncherIdentifier = "Xcode.IDEFoundation.Launcher.PosixSpawn" - launchStyle = "0" + launchStyle = "1" useCustomWorkingDirectory = "NO" ignoresPersistentStateOnLaunch = "NO" debugDocumentVersioning = "YES" diff --git a/Adamant/AppDelegate.swift b/Adamant/AppDelegate.swift index 097d3b185..f22f809e2 100644 --- a/Adamant/AppDelegate.swift +++ b/Adamant/AppDelegate.swift @@ -170,11 +170,15 @@ class AppDelegate: UIResponder, UIApplicationDelegate { switch connection { case .cellular, .wifi: - self?.dialogService.dissmisNoConnectionNotification() + DispatchQueue.onMainSync { + self?.dialogService.dissmisNoConnectionNotification() + } repeater.resumeAll() case .none: - self?.dialogService.showNoConnectionNotification() + DispatchQueue.onMainSync { + self?.dialogService.showNoConnectionNotification() + } repeater.pauseAll() } } @@ -341,45 +345,51 @@ extension AppDelegate: UNUserNotificationCenterDelegate { //MARK: Open Chat From Notification func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { - if let recipientAddress = userInfo[AdamantNotificationUserInfoKeys.pushRecipient] as? String - { - if application.applicationState != .background && application.applicationState != .inactive { - completionHandler(.noData) - return - } - - if let tabbar = window?.rootViewController as? UITabBarController, - let chats = tabbar.viewControllers?.first as? UISplitViewController, - let chatList = chats.viewControllers.first as? UINavigationController, - let list = chatList.viewControllers.first as? ChatListViewController { - - switch list.accountService.state { - case .loggedIn: - self.openDialog(chatList: chatList, tabbar: tabbar, list: list, recipientAddress: recipientAddress) - case .notLogged: - break - case .isLoggingIn: - break - case .updating: - break - } - - // if not logged in - list.didLoadedMessages = { [weak self] in - DispatchQueue.main.asyncAfter(deadline: .now() + 1) { - self?.openDialog(chatList: chatList, tabbar: tabbar, list: list, recipientAddress: recipientAddress) - } - } - } - completionHandler(.newData) - } else { + guard let transactionID = userInfo[AdamantNotificationUserInfoKeys.transactionId] as? String, + let transactionRaw = userInfo[AdamantNotificationUserInfoKeys.transaction] as? String, + let data = transactionRaw.data(using: .utf8), + let trs = try? JSONDecoder().decode(Transaction.self, from: data), + let tabbar = window?.rootViewController as? UITabBarController, + let chats = tabbar.viewControllers?.first as? UISplitViewController, + let chatList = chats.viewControllers.first as? UINavigationController, + let list = chatList.viewControllers.first as? ChatListViewController, + (application.applicationState != .active) + else { completionHandler(.noData) + return + } + + if case .loggedIn = list.accountService.state { + self.openDialog(chatList: chatList, tabbar: tabbar, list: list, transactionID: transactionID, senderAddress: trs.senderId) + } + + // if not logged in + list.didLoadedMessages = { [weak self] in + var timeout = 2.0 + if #available(iOS 13.0, *) { timeout = 0.5 } + DispatchQueue.main.asyncAfter(deadline: .now() + timeout) { + self?.dialogService.dismissProgress() + self?.openDialog(chatList: chatList, tabbar: tabbar, list: list, transactionID: transactionID, senderAddress: trs.senderId) + } } + + completionHandler(.newData) } - func openDialog(chatList: UINavigationController, tabbar: UITabBarController, list: ChatListViewController, recipientAddress: String) { + func openDialog(chatList: UINavigationController, tabbar: UITabBarController, list: ChatListViewController, transactionID: String, senderAddress: String) { + if let chatVCNav = chatList.viewControllers.last as? UINavigationController, + let chatVC = chatVCNav.viewControllers.first as? ChatViewController, + chatVC.chatroom?.partner?.address == senderAddress { + chatVC.forceScrollToBottom = true + chatVC.scrollDown() + return + } + guard let chatroom = list.chatsController?.fetchedObjects?.first(where: { room in - return room.lastTransaction?.recipientAddress == recipientAddress + let transactionExist = room.transactions?.first(where: { message in + return (message as? ChatTransaction)?.senderAddress == senderAddress + }) + return transactionExist != nil }) else { return } chatList.popToRootViewController(animated: false) @@ -391,8 +401,8 @@ extension AppDelegate: UNUserNotificationCenterDelegate { var timeout = 0.25 if #available(iOS 13.0, *) { timeout = 0 } DispatchQueue.main.asyncAfter(deadline: .now() + timeout) { - let chat = UINavigationController(rootViewController:vc) - split.showDetailViewController(chat, sender: self) + let chat = UINavigationController(rootViewController: vc) + split.showDetailViewController(chat, sender: list) } } else { chatList.pushViewController(vc, animated: false) @@ -480,7 +490,6 @@ extension AppDelegate { } else { unread = true } - if let exchenge = AdamantContacts.adamantExchange.messages["chats.welcome_message"] { chatProvider.fakeReceived(message: exchenge.message, senderId: AdamantContacts.adamantExchange.address, @@ -529,9 +538,9 @@ extension AppDelegate { }) } - if let welcome = AdamantContacts.adamantBountyWallet.messages["chats.welcome_message"] { + if let welcome = AdamantContacts.adamantWelcomeWallet.messages["chats.welcome_message"] { chatProvider.fakeReceived(message: welcome.message, - senderId: AdamantContacts.adamantBountyWallet.name, + senderId: AdamantContacts.adamantWelcomeWallet.name, date: Date.adamantNullDate, unread: unread, silent: welcome.silentNotification, @@ -577,15 +586,8 @@ extension AppDelegate { let router = container.resolve(Router.self), let list = chatList.viewControllers.first as? ChatListViewController { - switch list.accountService.state { - case .loggedIn: + if case .loggedIn = list.accountService.state { self.openDialog(chatList: chatList, tabbar: tabbar, router: router, list: list, adamantAdr: adamantAdr) - case .notLogged: - break - case .isLoggingIn: - break - case .updating: - break } // if not logged in diff --git a/Adamant/Assets/l18n/de.lproj/Localizable.strings b/Adamant/Assets/l18n/de.lproj/Localizable.strings index 7088e686d..80e9bf6d9 100755 --- a/Adamant/Assets/l18n/de.lproj/Localizable.strings +++ b/Adamant/Assets/l18n/de.lproj/Localizable.strings @@ -73,6 +73,9 @@ /* System accounts: ADAMANT Tokens */ "Accounts.AdamantTokens" = "Willkommen in ADAMANT"; +/* System accounts: ADAMANT Bounty */ +"Accounts.AdamantBounty" = "ADAMANT Bounty Wallet"; + /* System accounts: ADAMANT iOS Support */ "Accounts.iOSSupport" = "ADAMANT iOS Support"; diff --git a/Adamant/Assets/l18n/en.lproj/Localizable.strings b/Adamant/Assets/l18n/en.lproj/Localizable.strings index dbc9da48d..f2b862a63 100755 --- a/Adamant/Assets/l18n/en.lproj/Localizable.strings +++ b/Adamant/Assets/l18n/en.lproj/Localizable.strings @@ -76,6 +76,9 @@ /* System accounts: ADAMANT Tokens */ "Accounts.AdamantTokens" = "Welcome to ADAMANT"; +/* System accounts: ADAMANT Bounty */ +"Accounts.AdamantBounty" = "ADAMANT Bounty Wallet"; + /* System accounts: ADAMANT iOS Support */ "Accounts.iOSSupport" = "ADAMANT iOS Support"; diff --git a/Adamant/Assets/l18n/ru.lproj/Localizable.strings b/Adamant/Assets/l18n/ru.lproj/Localizable.strings index 53601c937..52cb2cac0 100644 --- a/Adamant/Assets/l18n/ru.lproj/Localizable.strings +++ b/Adamant/Assets/l18n/ru.lproj/Localizable.strings @@ -73,6 +73,9 @@ /* System accounts: ADAMANT Tokens */ "Accounts.AdamantTokens" = "Приветствие АДАМАНТа"; +/* System accounts: ADAMANT Bounty */ +"Accounts.AdamantBounty" = "ADAMANT Bounty Wallet"; + /* System accounts: ADAMANT iOS Support */ "Accounts.iOSSupport" = "Техподдержка iOS ADAMANT"; diff --git a/Adamant/CoreData/Adamant.xcdatamodeld/Adamant.xcdatamodel/contents b/Adamant/CoreData/Adamant.xcdatamodeld/Adamant.xcdatamodel/contents index 63a430530..40bf6ce99 100644 --- a/Adamant/CoreData/Adamant.xcdatamodeld/Adamant.xcdatamodel/contents +++ b/Adamant/CoreData/Adamant.xcdatamodeld/Adamant.xcdatamodel/contents @@ -1,5 +1,5 @@ - + @@ -62,7 +62,7 @@ - + diff --git a/Adamant/Debug.entitlements b/Adamant/Debug.entitlements index 1cd3dde38..96fd56e77 100644 --- a/Adamant/Debug.entitlements +++ b/Adamant/Debug.entitlements @@ -8,6 +8,14 @@ applinks:msg.adamant.im + com.apple.security.app-sandbox + + com.apple.security.device.camera + + com.apple.security.network.client + + com.apple.security.personal-information.photos-library + keychain-access-groups $(AppIdentifierPrefix)im.adamant.messenger-dev diff --git a/Adamant/Helpers/MacOSDeterminer.swift b/Adamant/Helpers/MacOSDeterminer.swift new file mode 100644 index 000000000..f36dc4ee3 --- /dev/null +++ b/Adamant/Helpers/MacOSDeterminer.swift @@ -0,0 +1,21 @@ +// +// MacOSDeterminer.swift +// Adamant +// +// Created by Stanislav Jelezoglo on 08.07.2022. +// Copyright © 2022 Adamant. All rights reserved. +// + +import Foundation + +var isMacOS: Bool = { + #if targetEnvironment(macCatalyst) + return true + #else + if #available(iOS 14.0, *) { + return ProcessInfo.processInfo.isiOSAppOnMac + } else { + return false + } + #endif +}() diff --git a/Adamant/Release.entitlements b/Adamant/Release.entitlements index ca588cb06..de1075df5 100644 --- a/Adamant/Release.entitlements +++ b/Adamant/Release.entitlements @@ -8,6 +8,14 @@ applinks:msg.adamant.im + com.apple.security.app-sandbox + + com.apple.security.device.camera + + com.apple.security.network.client + + com.apple.security.personal-information.photos-library + keychain-access-groups $(AppIdentifierPrefix)im.adamant.messenger diff --git a/Adamant/ServiceProtocols/ApiService.swift b/Adamant/ServiceProtocols/ApiService.swift index 8e8695a6e..222887ec2 100644 --- a/Adamant/ServiceProtocols/ApiService.swift +++ b/Adamant/ServiceProtocols/ApiService.swift @@ -147,6 +147,11 @@ protocol ApiService: AnyObject { func getTransactions(forAccount: String, type: TransactionType, fromHeight: Int64?, offset: Int?, limit: Int?, completion: @escaping (ApiServiceResult<[Transaction]>) -> Void) + // MARK: - Chats Rooms + + func getChatRooms(address: String, offset: Int?, completion: @escaping (ApiServiceResult) -> Void) + func getChatMessages(address: String, addressRecipient: String, offset: Int?, completion: @escaping (ApiServiceResult) -> Void) + // MARK: - Funds func transferFunds(sender: String, recipient: String, amount: Decimal, keypair: Keypair, completion: @escaping (ApiServiceResult) -> Void) diff --git a/Adamant/ServiceProtocols/DataProviders/AccountsProvider.swift b/Adamant/ServiceProtocols/DataProviders/AccountsProvider.swift index c9cd3fdd3..12aa9a883 100644 --- a/Adamant/ServiceProtocols/DataProviders/AccountsProvider.swift +++ b/Adamant/ServiceProtocols/DataProviders/AccountsProvider.swift @@ -55,6 +55,11 @@ protocol AccountsProvider { /// - Returns: Account, if found, created in main viewContext func getAccount(byAddress address: String, completion: @escaping (AccountsProviderResult) -> Void) + /// Search for fetched account, if not found try to create or asks server for account. + /// + /// - Returns: Account, if found, created in main viewContext + func getAccount(byAddress address: String, publicKey: String, completion: @escaping (AccountsProviderResult) -> Void) + /* That one bugged. Will be fixed later. Maybe. */ /// Search for fetched account, if not found, asks server for account. /// @@ -83,9 +88,10 @@ enum AdamantContacts { case adamantExchange case betOnBitcoin case donate + case adamantWelcomeWallet static let systemAddresses: [String] = { - return [AdamantContacts.adamantExchange.name, AdamantContacts.betOnBitcoin.name, AdamantContacts.adamantIco.name, AdamantContacts.adamantBountyWallet.name, AdamantContacts.adamantNewBountyWallet.name, AdamantContacts.donate.name] + return [AdamantContacts.adamantExchange.name, AdamantContacts.betOnBitcoin.name, AdamantContacts.adamantIco.name, AdamantContacts.adamantBountyWallet.name, AdamantContacts.adamantNewBountyWallet.name, AdamantContacts.donate.name, AdamantContacts.adamantWelcomeWallet.name] }() static func messagesFor(address: String) -> [String:SystemMessage]? { @@ -112,13 +118,14 @@ enum AdamantContacts { var name: String { switch self { - case .adamantBountyWallet, .adamantNewBountyWallet: + case .adamantWelcomeWallet: return NSLocalizedString("Accounts.AdamantTokens", comment: "System accounts: ADAMANT Tokens") + case .adamantBountyWallet, .adamantNewBountyWallet: + return NSLocalizedString("Accounts.AdamantBounty", comment: "System accounts: ADAMANT Bounty") case .adamantIco: return "Adamant ICO" case .iosSupport: return NSLocalizedString("Accounts.iOSSupport", comment: "System accounts: ADAMANT iOS Support") - case .adamantExchange: return NSLocalizedString("Accounts.AdamantExchange", comment: "System accounts: ADAMANT Exchange") case .betOnBitcoin: @@ -146,6 +153,7 @@ enum AdamantContacts { case .adamantExchange: return AdamantResources.contacts.adamantExchange case .betOnBitcoin: return AdamantResources.contacts.betOnBitcoin case .donate: return AdamantResources.contacts.donateWallet + case .adamantWelcomeWallet: return AdamantResources.contacts.adamantWelcomeWallet } } @@ -158,12 +166,13 @@ enum AdamantContacts { case .iosSupport: return AdamantResources.contacts.iosSupportPK case .adamantIco: return AdamantResources.contacts.adamantIcoPK case .donate: return AdamantResources.contacts.donateWalletPK + case .adamantWelcomeWallet: return AdamantResources.contacts.adamantBountyWalletPK } } var isReadonly: Bool { switch self { - case .adamantBountyWallet, .adamantNewBountyWallet, .adamantIco: return true + case .adamantBountyWallet, .adamantNewBountyWallet, .adamantIco, .adamantWelcomeWallet: return true case .iosSupport, .adamantExchange, .betOnBitcoin, .donate: return false } } @@ -171,13 +180,13 @@ enum AdamantContacts { var isHidden: Bool { switch self { case .adamantBountyWallet, .adamantNewBountyWallet: return true - case .adamantIco, .iosSupport, .adamantExchange, .betOnBitcoin, .donate: return false + case .adamantIco, .iosSupport, .adamantExchange, .betOnBitcoin, .donate, .adamantWelcomeWallet: return false } } var avatar: String { switch self { - case .adamantExchange, .betOnBitcoin, .donate: + case .adamantExchange, .betOnBitcoin, .donate, .adamantBountyWallet, .adamantNewBountyWallet: return "" default: return "avatar_bots" @@ -189,7 +198,9 @@ enum AdamantContacts { case .adamantBountyWallet, .adamantNewBountyWallet: return ["chats.welcome_message": SystemMessage(message: AdamantMessage.markdownText(NSLocalizedString("Chats.WelcomeMessage", comment: "Known contacts: Adamant welcome message. Markdown supported.")), silentNotification: true)] - + case .adamantWelcomeWallet: + return ["chats.welcome_message": SystemMessage(message: AdamantMessage.markdownText(NSLocalizedString("Chats.WelcomeMessage", comment: "Known contacts: Adamant welcome message. Markdown supported.")), + silentNotification: true)] case .adamantIco: return [ "chats.preico_message": SystemMessage(message: AdamantMessage.text(NSLocalizedString("Chats.PreIcoMessage", comment: "Known contacts: Adamant pre ICO message")), diff --git a/Adamant/ServiceProtocols/DataProviders/ChatsProvider.swift b/Adamant/ServiceProtocols/DataProviders/ChatsProvider.swift index bcd5dbf7d..f6d1b57d6 100644 --- a/Adamant/ServiceProtocols/DataProviders/ChatsProvider.swift +++ b/Adamant/ServiceProtocols/DataProviders/ChatsProvider.swift @@ -174,9 +174,18 @@ protocol ChatsProvider: DataProvider { var chatPositon: [String: Double] { get set } var blackList: [String] { get } + var roomsMaxCount: Int? { get } + var roomsLoadedCount: Int? { get } + + var isChatLoaded: [String: Bool] { get } + var chatMaxMessages: [String: Int] { get } + var chatLoadedMessages: [String: Int] { get } + // MARK: - Getting chats and messages func getChatroomsController() -> NSFetchedResultsController func getChatController(for chatroom: Chatroom) -> NSFetchedResultsController + func getChatRooms(offset: Int?, completion: (() ->())?) + func getChatMessages(with addressRecipient: String, offset: Int?, completion: ((Int) ->())?) /// Unread messages controller. Sections by chatroom. func getUnreadMessagesController() -> NSFetchedResultsController diff --git a/Adamant/Services/AdamantCurrencyInfoService.swift b/Adamant/Services/AdamantCurrencyInfoService.swift index 2d1299294..b6e2972a6 100644 --- a/Adamant/Services/AdamantCurrencyInfoService.swift +++ b/Adamant/Services/AdamantCurrencyInfoService.swift @@ -8,6 +8,7 @@ import Foundation import Alamofire +import UIKit extension StoreKey { struct CoinInfo { @@ -54,6 +55,8 @@ class AdamantCurrencyInfoService: CurrencyInfoService { } } + private var observerActive: NSObjectProtocol? + // MARK: - Dependencies var accountService: AccountService! { didSet { @@ -75,6 +78,28 @@ class AdamantCurrencyInfoService: CurrencyInfoService { } } + // MARK: - Init + init() { + addObservers() + } + + deinit { + removeObservers() + } + + // MARK: - Observers + func addObservers() { + observerActive = NotificationCenter.default.addObserver(forName: UIApplication.didBecomeActiveNotification, object: nil, queue: OperationQueue.main) { [weak self] notification in + self?.update() + } + } + + func removeObservers() { + if let observerActive = observerActive { + NotificationCenter.default.removeObserver(observerActive) + } + } + // MARK: - Info service func update() { guard let coins = rateCoins else { diff --git a/Adamant/Services/AdamantReachability.swift b/Adamant/Services/AdamantReachability.swift index a7a5b3e1d..92ddfe1b4 100644 --- a/Adamant/Services/AdamantReachability.swift +++ b/Adamant/Services/AdamantReachability.swift @@ -8,6 +8,7 @@ import Foundation import Reachability +import Network // MAKR: - Convinients extension Reachability.Connection { @@ -27,51 +28,39 @@ extension Reachability.Connection { // MARK: - AdamantReachability wrapper class AdamantReachability: ReachabilityMonitor { - let reachability: Reachability - - private(set) var isActive = false - + let monitorForWifi = NWPathMonitor(requiredInterfaceType: .wifi) + let monitorForCellular = NWPathMonitor(requiredInterfaceType: .cellular) + private var wifiStatus: NWPath.Status = .satisfied + private var cellularStatus: NWPath.Status = .satisfied + var connection: AdamantConnection { - return reachability.connection.adamantConnection + if wifiStatus == .satisfied { return AdamantConnection.wifi } + if cellularStatus == .satisfied { return AdamantConnection.cellular } + return AdamantConnection.none } - init() { - reachability = try! Reachability() // TODO: remove force try and make safe init - reachability.whenReachable = { [weak self] reachability in - let userInfo: [String:Any] = [AdamantUserInfoKey.ReachabilityMonitor.connection:reachability.connection.adamantConnection] + func start() { + monitorForWifi.pathUpdateHandler = { [weak self] path in + self?.wifiStatus = path.status + let status = path.status == .satisfied ? AdamantConnection.wifi : AdamantConnection.none + let userInfo: [String:Any] = [AdamantUserInfoKey.ReachabilityMonitor.connection: status] NotificationCenter.default.post(name: Notification.Name.AdamantReachabilityMonitor.reachabilityChanged, object: self, userInfo: userInfo) } - - reachability.whenUnreachable = { [weak self] reachability in - let userInfo: [String:Any] = [AdamantUserInfoKey.ReachabilityMonitor.connection:reachability.connection.adamantConnection] + monitorForCellular.pathUpdateHandler = { [weak self] path in + self?.cellularStatus = path.status + let status = path.status == .satisfied ? AdamantConnection.cellular : AdamantConnection.none + let userInfo: [String:Any] = [AdamantUserInfoKey.ReachabilityMonitor.connection: status] NotificationCenter.default.post(name: Notification.Name.AdamantReachabilityMonitor.reachabilityChanged, object: self, userInfo: userInfo) } - } - - deinit { - stop() - } - func start() { - guard !isActive else { - return - } - - do { - try reachability.startNotifier() - isActive = true - } catch { - isActive = false - } + let queue = DispatchQueue(label: "NetworkMonitor") + monitorForCellular.start(queue: queue) + monitorForWifi.start(queue: queue) } func stop() { - guard isActive else { - return - } - - NotificationCenter.default.removeObserver(self) - reachability.stopNotifier() - isActive = false + monitorForWifi.cancel() + monitorForCellular.cancel() } + } diff --git a/Adamant/Services/ApiService/AdamantApi+Chats.swift b/Adamant/Services/ApiService/AdamantApi+Chats.swift index c9cfd29ca..d6fb98553 100644 --- a/Adamant/Services/ApiService/AdamantApi+Chats.swift +++ b/Adamant/Services/ApiService/AdamantApi+Chats.swift @@ -14,7 +14,8 @@ extension AdamantApiService.ApiCommands { root: "/api/chats", get: "/api/chats/get", normalizeTransaction: "/api/chats/normalize", - processTransaction: "/api/chats/process" + processTransaction: "/api/chats/process", + getChatRooms: "/api/chatrooms" ) } @@ -133,4 +134,61 @@ extension AdamantApiService { } } } + + // new api + func getChatRooms(address: String, offset: Int?, completion: @escaping (ApiServiceResult) -> Void) { + // MARK: 1. Prepare params + var queryItems: [URLQueryItem] = [] + if let offset = offset { queryItems.append(URLQueryItem(name: "offset", value: String(offset))) } + queryItems.append(URLQueryItem(name: "limit", value: "20")) + + // MARK: 2. Build endpoint + let endpoint: URL + do { + endpoint = try buildUrl(path: ApiCommands.Chats.getChatRooms + "/\(address)", queryItems: queryItems) + } catch { + let err = InternalError.endpointBuildFailed.apiServiceErrorWith(error: error) + completion(.failure(err)) + return + } + + // MARK: 3. Send + sendRequest(url: endpoint) { (serverResponse: ApiServiceResult) in + switch serverResponse { + case .success(let response): + completion(.success(response)) + + case .failure(let error): + completion(.failure(.networkError(error: error))) + } + } + } + + func getChatMessages(address: String, addressRecipient: String, offset: Int?, completion: @escaping (ApiServiceResult) -> Void) { + // MARK: 1. Prepare params + var queryItems: [URLQueryItem] = [] + if let offset = offset { queryItems.append(URLQueryItem(name: "offset", value: String(offset))) } + queryItems.append(URLQueryItem(name: "limit", value: "50")) + + // MARK: 2. Build endpoint + let endpoint: URL + do { + endpoint = try buildUrl(path: ApiCommands.Chats.getChatRooms + "/\(address)/\(addressRecipient)", queryItems: queryItems) + } catch { + let err = InternalError.endpointBuildFailed.apiServiceErrorWith(error: error) + completion(.failure(err)) + return + } + + // MARK: 3. Send + sendRequest(url: endpoint) { (serverResponse: ApiServiceResult) in + switch serverResponse { + case .success(let response): + completion(.success(response)) + + case .failure(let error): + completion(.failure(.networkError(error: error))) + } + } + } } diff --git a/Adamant/Services/DataProviders/AdamantAccountsProvider.swift b/Adamant/Services/DataProviders/AdamantAccountsProvider.swift index 4e84c0307..249e5f87d 100644 --- a/Adamant/Services/DataProviders/AdamantAccountsProvider.swift +++ b/Adamant/Services/DataProviders/AdamantAccountsProvider.swift @@ -52,6 +52,7 @@ class AdamantAccountsProvider: AccountsProvider { init() { let ico = KnownContact(contact: AdamantContacts.adamantIco) let bounty = KnownContact(contact: AdamantContacts.adamantBountyWallet) + let welcome = KnownContact(contact: AdamantContacts.adamantWelcomeWallet) let newBounty = KnownContact(contact: AdamantContacts.adamantNewBountyWallet) let iosSupport = KnownContact(contact: AdamantContacts.iosSupport) @@ -74,7 +75,10 @@ class AdamantAccountsProvider: AccountsProvider { AdamantContacts.betOnBitcoin.address: betOnBitcoin, AdamantContacts.betOnBitcoin.name: betOnBitcoin, - AdamantContacts.donate.address: donate + AdamantContacts.donate.address: donate, + + AdamantContacts.adamantWelcomeWallet.address: welcome, + AdamantContacts.adamantWelcomeWallet.name: welcome, ] NotificationCenter.default.addObserver(forName: Notification.Name.AdamantAddressBookService.addressBookUpdated, object: nil, queue: nil) { [weak self] notification in @@ -215,7 +219,7 @@ extension AdamantAccountsProvider { } } - /// Get account info from servier. + /// Get account info from server. /// /// - Parameters: /// - address: address of an account @@ -227,8 +231,6 @@ extension AdamantAccountsProvider { return } - let context = stack.container.viewContext - // Go background, to not to hang threads (especially main) on semaphores and dispatch groups queue.async { self.groupsSemaphore.wait() // 1 @@ -281,6 +283,7 @@ extension AdamantAccountsProvider { var coreAccount: CoreDataAccount! = nil DispatchQueue.main.sync { + let context = self.stack.container.viewContext coreAccount = self.createCoreDataAccount(from: account, context: context) if let dummy = dummy { @@ -344,7 +347,102 @@ extension AdamantAccountsProvider { } } - + /// Get account info from server or create instantly + /// + /// - Parameters: + /// - address: address of an account + /// - publicKey: publicKey of an account + /// - completion: returns Account created in viewContext + func getAccount(byAddress address: String, publicKey: String, completion: @escaping (AccountsProviderResult) -> Void) { + let validation = AdamantUtilities.validateAdamantAddress(address: address) + if validation == .invalid { + completion(.invalidAddress(address: address)) + return + } + + if publicKey.isEmpty { + getAccount(byAddress: address) { _account in + completion(_account) + } + return + } + let context = stack.container.viewContext + + // Go background, to not to hang threads (especially main) on semaphores and dispatch groups + queue.async { + self.groupsSemaphore.wait() // 1 + + // If there is already request for a this address, wait + if let group = self.requestGroups[address] { + self.groupsSemaphore.signal() // 1 + group.wait() + self.groupsSemaphore.wait() // 2 + } + + // Check if there is an account, that we are looking for + let dummy: DummyAccount? + switch self.getAccount(byPredicate: NSPredicate(format: "address == %@", address)) { + case .core(let account): + self.groupsSemaphore.signal() // 1 or 2 + completion(.success(account)) + return + + case .dummy(let account): + dummy = account + + case .notFound: + dummy = nil + } + + // No, we need to get one + let group = DispatchGroup() + self.requestGroups[address] = group + group.enter() + + self.groupsSemaphore.signal() // 1 or 2 + + switch validation { + case .valid: + var coreAccount: CoreDataAccount! = nil + DispatchQueue.main.sync { + coreAccount = self.createCoreDataAccount(with: address, publicKey: publicKey, contextt: context) + if let dummy = dummy { + coreAccount.name = dummy.name + + if let transfers = dummy.transfers { + dummy.removeFromTransfers(transfers) + coreAccount.addToTransfers(transfers) + + if let chatroom = coreAccount.chatroom { + chatroom.addToTransactions(transfers) + chatroom.updateLastTransaction() + } + } + context.delete(dummy) + } + + try? context.save() + } + self.removeSafeFromRequests(address) + group.leave() + + completion(.success(coreAccount)) + + case .system: + let coreAccount = self.createCoreDataAccount(with: address, publicKey: "") + self.removeSafeFromRequests(address) + group.leave() + + completion(.success(coreAccount)) + + case .invalid: + self.removeSafeFromRequests(address) + group.leave() + + completion(.invalidAddress(address: address)) + } + } + } /* @@ -409,6 +507,19 @@ extension AdamantAccountsProvider { */ + private func createCoreDataAccount(with address: String, publicKey: String, contextt: NSManagedObjectContext) -> CoreDataAccount { + var coreAccount: CoreDataAccount! + + DispatchQueue.onMainSync { + coreAccount = createCoreDataAccount( + with: address, + publicKey: publicKey, + context: contextt + ) + } + return coreAccount + } + private func createCoreDataAccount(with address: String, publicKey: String) -> CoreDataAccount { var coreAccount: CoreDataAccount! diff --git a/Adamant/Services/DataProviders/AdamantChatsProvider.swift b/Adamant/Services/DataProviders/AdamantChatsProvider.swift index 57bace107..763671e2c 100644 --- a/Adamant/Services/DataProviders/AdamantChatsProvider.swift +++ b/Adamant/Services/DataProviders/AdamantChatsProvider.swift @@ -38,6 +38,13 @@ class AdamantChatsProvider: ChatsProvider { private(set) var blackList: [String] = [] private(set) var removedMessages: [String] = [] + public var isChatLoaded: [String : Bool] = [:] + public var chatMaxMessages: [String : Int] = [:] + public var chatLoadedMessages: [String : Int] = [:] + private var connection: AdamantConnection? + private var isConnectedToTheInthernet = true + private var isRestoredConnectionToTheInthernet: (() ->())? + private(set) var isInitiallySynced: Bool = false { didSet { NotificationCenter.default.post(name: Notification.Name.AdamantChatsProvider.initiallySyncedChanged, object: self, userInfo: [AdamantUserInfoKey.ChatProvider.initiallySynced : isInitiallySynced]) @@ -54,6 +61,9 @@ class AdamantChatsProvider: ChatsProvider { private var previousAppState: UIApplication.State? + private(set) var roomsMaxCount: Int? + private(set) var roomsLoadedCount: Int? + // MARK: Lifecycle init() { NotificationCenter.default.addObserver(forName: Notification.Name.AdamantAccountService.userLoggedIn, object: nil, queue: nil) { [weak self] notification in @@ -79,7 +89,8 @@ class AdamantChatsProvider: ChatsProvider { self?.dropStateData() store.set(loggedAddress, for: StoreKey.chatProvider.address) } - self?.update() + + self?.getChatRooms(offset: nil, completion: nil) self?.connectToSocket() } @@ -121,7 +132,31 @@ class AdamantChatsProvider: ChatsProvider { } NotificationCenter.default.addObserver(forName: UIApplication.willResignActiveNotification, object: nil, queue: OperationQueue.main) { [weak self] notification in - self?.previousAppState = .background + if self?.isInitiallySynced ?? false { + self?.previousAppState = .background + } + } + + NotificationCenter.default.addObserver( + forName: Notification.Name.AdamantReachabilityMonitor.reachabilityChanged, + object: nil, + queue: nil + ) { [weak self] notification in + guard let connection = notification + .userInfo?[AdamantUserInfoKey.ReachabilityMonitor.connection] as? AdamantConnection + else { + return + } + self?.connection = connection + switch self?.connection { + case .some(.cellular), .some(.wifi): + if self?.isConnectedToTheInthernet == false { + self?.isRestoredConnectionToTheInthernet?() + } + self?.isConnectedToTheInthernet = true + case nil, .some(.none): + self?.isConnectedToTheInthernet = false + } } } @@ -172,6 +207,11 @@ extension AdamantChatsProvider { // Drop props receivedLastHeight = nil readedLastHeight = nil + roomsMaxCount = nil + roomsLoadedCount = nil + isChatLoaded.removeAll() + chatMaxMessages.removeAll() + chatLoadedMessages.removeAll() // Drop store securedStore.remove(StoreKey.chatProvider.address) @@ -196,6 +236,150 @@ extension AdamantChatsProvider { setState(.empty, previous: prevState, notify: notify) } + func getChatRooms(offset: Int?, completion: (() ->())?) { + guard let address = accountService.account?.address, + let privateKey = accountService.keypair?.privateKey else { + completion?() + return + } + + let prevState = state + state = .updating + + let cms = DispatchSemaphore(value: 1) + // MARK: 3. Get transactions + let privateContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType) + privateContext.parent = self.stack.container.viewContext + + apiGetChatrooms(address: address, offset: offset) { [weak self] chatrooms in + guard let chatrooms = chatrooms else { + if let synced = self?.isInitiallySynced, !synced { + self?.isInitiallySynced = true + } + self?.setState(.upToDate, previous: prevState) + completion?() + return + } + + self?.roomsMaxCount = chatrooms.count + + if let roomsLoadedCount = self?.roomsLoadedCount { + self?.roomsLoadedCount = roomsLoadedCount + (chatrooms.chats?.count ?? 0) + } else { + self?.roomsLoadedCount = chatrooms.chats?.count ?? 0 + } + + var array = Array() + chatrooms.chats?.forEach({ room in + if let last = room.lastTransaction { + array.append(last) + } + }) + + self?.processingQueue.async { + self?.process(messageTransactions: array, + senderId: address, + privateKey: privateKey, + context: privateContext, + contextMutatingSemaphore: cms, + completion: { + if let synced = self?.isInitiallySynced, !synced { + self?.isInitiallySynced = true + } + self?.setState(.upToDate, previous: prevState) + completion?() + }) + } + } + } + + func apiGetChatrooms(address: String, offset: Int?, completion: ((ChatRooms?) ->())?) { + apiService.getChatRooms(address: address, offset: offset) { [weak self] result in + switch result { + case .success(let chatrooms): + completion?(chatrooms) + case .failure(let error): + switch error { + case .networkError: + if self?.isConnectedToTheInthernet ?? false { + self?.apiGetChatrooms(address: address, offset: offset) { result in + completion?(result) + } + } else { + self?.isRestoredConnectionToTheInthernet = { + self?.apiGetChatrooms(address: address, offset: offset) { result in + completion?(result) + } + } + } + default: + completion?(nil) + } + } + } + } + + func getChatMessages(with addressRecipient: String, offset: Int?, completion: ((Int) ->())?) { + guard let address = accountService.account?.address, + let privateKey = accountService.keypair?.privateKey else { + completion?(0) + return + } + + let cms = DispatchSemaphore(value: 1) + // MARK: 3. Get transactions + let privateContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType) + privateContext.parent = self.stack.container.viewContext + + apiGetChatMessages(address: address, addressRecipient: addressRecipient, offset: offset) { [weak self] chatroom in + self?.processingQueue.async { + self?.isChatLoaded[addressRecipient] = true + self?.chatMaxMessages[addressRecipient] = chatroom?.count ?? 0 + let loadedCount = self?.chatLoadedMessages[addressRecipient] ?? 0 + self?.chatLoadedMessages[addressRecipient] = loadedCount + (chatroom?.messages?.count ?? 0) + guard let transactions = chatroom?.messages, + transactions.count > 0 else { + completion?(0) + return + } + self?.process(messageTransactions: transactions, + senderId: address, + privateKey: privateKey, + context: privateContext, + contextMutatingSemaphore: cms, + completion: { + completion?(transactions.count) + }) + } + } + } + + func apiGetChatMessages(address: String, addressRecipient: String, offset: Int?, completion: ((ChatRooms?) ->())?) { + apiService.getChatMessages(address: address, addressRecipient: addressRecipient, offset: offset) { [weak self] result in + switch result { + case .success(let chatroom): + completion?(chatroom) + case .failure(let error): + switch error { + case .networkError: + if self?.isConnectedToTheInthernet ?? false { + self?.apiGetChatMessages(address: address, addressRecipient: addressRecipient, offset: offset) { result in + completion?(result) + } + } else { + self?.isRestoredConnectionToTheInthernet = { + self?.apiGetChatMessages(address: address, addressRecipient: addressRecipient, offset: offset) { result in + completion?(result) + } + } + } + default: + completion?(nil) + } + } + } + } + func update() { self.update(completion: nil) } @@ -789,9 +973,25 @@ extension AdamantChatsProvider { return controller } + + /// Search transaction in local storage + /// + /// - Parameter id: Transacton ID + /// - Returns: Transaction, if found + func getTransfer(id: String, context: NSManagedObjectContext) -> TransferTransaction? { + let request = NSFetchRequest(entityName: TransferTransaction.entityName) + request.predicate = NSPredicate(format: "transactionId == %@", String(id)) + request.fetchLimit = 1 + + do { + let result = try context.fetch(request) + return result.first + } catch { + return nil + } + } } - // MARK: - Processing extension AdamantChatsProvider { /// Get new transactions @@ -868,7 +1068,8 @@ extension AdamantChatsProvider { senderId: String, privateKey: String, context: NSManagedObjectContext, - contextMutatingSemaphore: DispatchSemaphore) { + contextMutatingSemaphore: DispatchSemaphore, + completion: (() -> ())? = nil) { struct DirectionedTransaction { let transaction: Transaction let isOut: Bool @@ -908,7 +1109,11 @@ extension AdamantChatsProvider { for address in notFound { keysGroup.enter() // Enter 1 - accountsProvider.getAccount(byAddress: address) { result in + let transaction = grouppedTransactions[address]?.first + let isOut = transaction?.isOut ?? false + let publicKey = isOut ? transaction?.transaction.recipientPublicKey : transaction?.transaction.senderPublicKey + + accountsProvider.getAccount(byAddress: address, publicKey: publicKey ?? "") { result in defer { keysGroup.leave() // Exit 1 } @@ -936,7 +1141,6 @@ extension AdamantChatsProvider { print("Failed to get all accounts: Needed keys:\n\(grouppedTransactions.keys.joined(separator: "\n"))\nFounded Addresses: \(partners.keys.compactMap { $0.address }.joined(separator: "\n"))") } - // MARK: 3. Process Chatrooms and Transactions var height: Int64 = 0 let privateContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType) @@ -977,12 +1181,12 @@ extension AdamantChatsProvider { height = chatTransaction.height } - let trans = privateChatroom.transactions?.first(where: { message in + let transactionExist = privateChatroom.transactions?.first(where: { message in return (message as? ChatTransaction)?.txId == chatTransaction.txId }) as? ChatTransaction if !trs.isOut { - if trans == nil { + if transactionExist == nil { newMessageTransactions.append(chatTransaction) } @@ -1009,16 +1213,16 @@ extension AdamantChatsProvider { } } - if trans == nil { + if transactionExist == nil { if (chatTransaction.blockId?.isEmpty ?? true) && (chatTransaction.amountValue ?? 0.0 > 0.0) { chatTransaction.statusEnum = .pending } messages.insert(chatTransaction) } else { - trans?.height = chatTransaction.height - trans?.blockId = chatTransaction.blockId - trans?.confirmations = chatTransaction.confirmations - trans?.statusEnum = .delivered + transactionExist?.height = chatTransaction.height + transactionExist?.blockId = chatTransaction.blockId + transactionExist?.confirmations = chatTransaction.confirmations + transactionExist?.statusEnum = .delivered } } } @@ -1094,6 +1298,7 @@ extension AdamantChatsProvider { receivedLastHeight = height } highSemaphore.signal() + completion?() } } @@ -1148,11 +1353,36 @@ extension AdamantChatsProvider { /// - context: context to insert parsed transaction to /// - Returns: New parsed transaction private func chatTransaction(from transaction: Transaction, isOutgoing: Bool, publicKey: String, privateKey: String, partner: BaseAccount, context: NSManagedObjectContext) -> ChatTransaction? { + let messageTransaction: ChatTransaction guard let chat = transaction.asset.chat else { + if transaction.type == .send { + if let trs = getTransfer(id: String(transaction.id), context: context) { + messageTransaction = trs + } else { + messageTransaction = TransferTransaction(context: context) + } + messageTransaction.amount = transaction.amount as NSDecimalNumber + messageTransaction.date = transaction.date as NSDate + messageTransaction.recipientId = transaction.recipientId + messageTransaction.senderId = transaction.senderId + messageTransaction.transactionId = String(transaction.id) + messageTransaction.type = Int16(TransactionType.chatMessage.rawValue) + messageTransaction.showsChatroom = true + messageTransaction.height = Int64(transaction.height) + messageTransaction.isConfirmed = true + messageTransaction.isOutgoing = isOutgoing + messageTransaction.blockId = transaction.blockId + messageTransaction.confirmations = transaction.confirmations + messageTransaction.chatMessageId = UUID().uuidString + messageTransaction.fee = transaction.fee as NSDecimalNumber + messageTransaction.statusEnum = MessageStatus.delivered + messageTransaction.partner = partner + return messageTransaction + } return nil } - let messageTransaction: ChatTransaction + // MARK: Decode message, message must contain data if let decodedMessage = adamantCore.decodeMessage(rawMessage: chat.message, rawNonce: chat.ownMessage, senderPublicKey: publicKey, privateKey: privateKey)?.trimmingCharacters(in: .whitespacesAndNewlines) { if (decodedMessage.isEmpty && transaction.amount > 0) || !decodedMessage.isEmpty { @@ -1160,9 +1390,13 @@ extension AdamantChatsProvider { // MARK: Text message case .message, .messageOld, .signal, .unknown: if transaction.amount > 0 { - let trs = TransferTransaction(entity: TransferTransaction.entity(), insertInto: context) - trs.comment = decodedMessage - messageTransaction = trs + if let trs = getTransfer(id: String(transaction.id), context: context) { + messageTransaction = trs + } else { + let trs = TransferTransaction(entity: TransferTransaction.entity(), insertInto: context) + trs.comment = decodedMessage + messageTransaction = trs + } } else { let trs = MessageTransaction(entity: MessageTransaction.entity(), insertInto: context) trs.message = decodedMessage diff --git a/Adamant/Services/DataProviders/AdamantTransfersProvider.swift b/Adamant/Services/DataProviders/AdamantTransfersProvider.swift index e454b3136..437d05daa 100644 --- a/Adamant/Services/DataProviders/AdamantTransfersProvider.swift +++ b/Adamant/Services/DataProviders/AdamantTransfersProvider.swift @@ -110,7 +110,7 @@ class AdamantTransfersProvider: TransfersProvider { extension AdamantTransfersProvider { func reload() { reset(notify: false) - + update() } @@ -624,7 +624,22 @@ extension AdamantTransfersProvider { } } - + /// Search transaction in local storage + /// + /// - Parameter id: Transacton ID, context: NSManagedObjectContext + /// - Returns: Transaction, if found + func getTransfer(id: String, context: NSManagedObjectContext) -> TransferTransaction? { + let request = NSFetchRequest(entityName: TransferTransaction.entityName) + request.predicate = NSPredicate(format: "transactionId == %@", String(id)) + request.fetchLimit = 1 + + do { + let result = try context.fetch(request) + return result.first + } catch { + return nil + } + } /// Call Server, check if transaction updated /// /// - Parameters: @@ -660,6 +675,7 @@ extension AdamantTransfersProvider { } trsfr.confirmations = transaction.confirmations + trsfr.blockId = transaction.blockId do { try context.save() @@ -872,32 +888,35 @@ extension AdamantTransfersProvider { } else { unconfirmedsSemaphore.signal() } - - let transfer = TransferTransaction(context: 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.statusEnum = .delivered - transfer.showsChatroom = false - transfer.isConfirmed = true - transfer.chatMessageId = UUID().uuidString - + let transfer: TransferTransaction + if let trs = getTransfer(id: String(t.id), context: context) { + transfer = trs + transfer.confirmations = t.confirmations + transfer.statusEnum = .delivered + transfer.blockId = t.blockId + } else { + transfer = TransferTransaction(context: 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.statusEnum = .delivered + transfer.showsChatroom = false + transfer.isConfirmed = true + transfer.chatMessageId = UUID().uuidString + } + transfer.isOutgoing = t.senderId == address let partnerId = transfer.isOutgoing ? t.recipientId : t.senderId if let partner = partners[partnerId] { transfer.partner = partner - - if let chatroom = (partner as? CoreDataAccount)?.chatroom { - transfer.chatroom = chatroom - } } if t.height > height { @@ -939,7 +958,7 @@ extension AdamantTransfersProvider { 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 { diff --git a/Adamant/Stories/Account/AccountViewController.swift b/Adamant/Stories/Account/AccountViewController.swift index a299672f8..2d0ca635f 100644 --- a/Adamant/Stories/Account/AccountViewController.swift +++ b/Adamant/Stories/Account/AccountViewController.swift @@ -680,6 +680,9 @@ class AccountViewController: FormViewController { if UIScreen.main.traitCollection.userInterfaceIdiom == .pad, !initiated { layoutTableHeaderView() layoutTableFooterView() + if !initiated { + initiated = true + } } pagingViewController?.indicatorColor = UIColor.adamant.primary diff --git a/Adamant/Stories/Chats/ChatListViewController.swift b/Adamant/Stories/Chats/ChatListViewController.swift index b4660d0ed..6d56ef73c 100644 --- a/Adamant/Stories/Chats/ChatListViewController.swift +++ b/Adamant/Stories/Chats/ChatListViewController.swift @@ -25,6 +25,7 @@ extension String.adamantLocalized { class ChatListViewController: UIViewController { let cellIdentifier = "cell" + let loadingCellIdentifier = "loadingCell" let cellHeight: CGFloat = 76.0 // MARK: Dependencies @@ -70,6 +71,8 @@ class ChatListViewController: UIViewController { return parser }() + private var defaultSeparatorInstets: UIEdgeInsets? + // MARK: Busy indicator @IBOutlet weak var busyBackgroundView: UIView! @@ -77,6 +80,7 @@ class ChatListViewController: UIViewController { @IBOutlet weak var busyIndicatorLabel: UILabel! private(set) var isBusy: Bool = true + private var lastSystemChatPositionRow: Int? // MARK: Keyboard // SplitView sends double notifications about keyboard. @@ -104,6 +108,7 @@ class ChatListViewController: UIViewController { tableView.dataSource = self tableView.delegate = self tableView.register(UINib(nibName: "ChatTableViewCell", bundle: nil), forCellReuseIdentifier: cellIdentifier) + tableView.register(LoadingTableViewCell.self, forCellReuseIdentifier: loadingCellIdentifier) tableView.refreshControl = refreshControl if self.accountService.account != nil { @@ -157,6 +162,7 @@ class ChatListViewController: UIViewController { if let synced = notification.userInfo?[AdamantUserInfoKey.ChatProvider.initiallySynced] as? Bool { self?.didLoadedMessages?() self?.setIsBusy(!synced) + self?.tableView.reloadData() } else if let synced = self?.chatsProvider.isInitiallySynced { self?.setIsBusy(!synced) } else { @@ -349,6 +355,9 @@ class ChatListViewController: UIViewController { extension ChatListViewController: UITableViewDelegate, UITableViewDataSource { func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { if let f = chatsController?.fetchedObjects { + if f.count > 0 { + return isBusy ? f.count + 1 : f.count + } return f.count } else { return 0 @@ -359,12 +368,24 @@ extension ChatListViewController: UITableViewDelegate, UITableViewDataSource { return cellHeight } + func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat { + return cellHeight + } + func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? { return UIView() } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - if let chatroom = chatsController?.object(at: indexPath) { + if isBusy, + indexPath.row == lastSystemChatPositionRow, + let cell = tableView.cellForRow(at: indexPath), + let _ = cell as? LoadingTableViewCell { + tableView.deselectRow(at: indexPath, animated: true) + return + } + let nIndexPath = isBusy && indexPath.row >= (lastSystemChatPositionRow ?? 0) ? IndexPath(row: indexPath.row - 1, section: 0) : indexPath + if let chatroom = chatsController?.object(at: nIndexPath) { let vc = chatViewController(for: chatroom) if let split = self.splitViewController { @@ -384,6 +405,12 @@ extension ChatListViewController: UITableViewDelegate, UITableViewDataSource { // MARK: - UITableView Cells extension ChatListViewController { func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + if isBusy && indexPath.row == lastSystemChatPositionRow { + let cell = tableView.dequeueReusableCell(withIdentifier: loadingCellIdentifier, for: indexPath) as! LoadingTableViewCell + cell.startLoadAnimating() + return cell + } + let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! ChatTableViewCell cell.accessoryType = .disclosureIndicator @@ -399,9 +426,46 @@ extension ChatListViewController { } func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { - if let cell = cell as? ChatTableViewCell, let chat = chatsController?.object(at: indexPath) { - configureCell(cell, for: chat) + if isBusy, + indexPath.row == lastSystemChatPositionRow, + let cell = cell as? LoadingTableViewCell { + configureCell(cell) + } else if let cell = cell as? ChatTableViewCell { + let nIndexPath = isBusy && indexPath.row >= (lastSystemChatPositionRow ?? 0) ? IndexPath(row: indexPath.row - 1, section: 0) : indexPath + if let chat = chatsController?.object(at: nIndexPath) { + configureCell(cell, for: chat) + } + if isBusy, + indexPath.row == (lastSystemChatPositionRow ?? 0) - 1 { + cell.separatorInset = UIEdgeInsets(top: 0, left: 15, bottom: 0, right: 0) + } else { + if let defaultSeparatorInstets = defaultSeparatorInstets { + cell.separatorInset = defaultSeparatorInstets + } else { + defaultSeparatorInstets = cell.separatorInset + } + } } + + guard let roomsLoadedCount = chatsProvider.roomsLoadedCount, + let roomsMaxCount = chatsProvider.roomsMaxCount, + roomsLoadedCount < roomsMaxCount, + roomsMaxCount > 0, + !isBusy, + tableView.numberOfRows(inSection: 0) >= roomsLoadedCount, + let lastVisibleIndexPath = tableView.indexPathsForVisibleRows, + lastVisibleIndexPath.contains(IndexPath(row: tableView.numberOfRows(inSection: 0) - 3, section: 0)) + else { + return + } + + isBusy = true + insertReloadRow() + loadNewChats(offset: roomsLoadedCount) + } + + private func configureCell(_ cell: LoadingTableViewCell) { + cell.startLoadAnimating() } private func configureCell(_ cell: ChatTableViewCell, for chatroom: Chatroom) { @@ -410,10 +474,12 @@ extension ChatListViewController { cell.accountLabel.text = title } else if let name = partner.name { cell.accountLabel.text = name + } else if let address = partner.address, + let name = self.addressBook.addressBook[address] { + cell.accountLabel.text = name.checkAndReplaceSystemWallets() } else { cell.accountLabel.text = partner.address } - if let avatarName = partner.avatar, let avatar = UIImage.init(named: avatarName) { cell.avatarImage = avatar cell.avatarImageView.tintColor = UIColor.adamant.primary @@ -452,18 +518,48 @@ extension ChatListViewController { cell.dateLabel.text = nil } } + + private func insertReloadRow() { + lastSystemChatPositionRow = getBottomSystemChatIndex() + DispatchQueue.main.async { [weak self] in + if #available(iOS 11.0, *) { + self?.tableView.performBatchUpdates { + self?.tableView.insertRows(at: [ + IndexPath(row: self?.lastSystemChatPositionRow ?? 0, section: 0) + ], with: .none) + self?.tableView.reloadRows(at: [IndexPath(row: (self?.lastSystemChatPositionRow ?? 0) - 1, section: 0)], with: .none) + } + } else { + self?.tableView.beginUpdates() + self?.tableView.insertRows(at: [IndexPath(row: self?.lastSystemChatPositionRow ?? 0, section: 0)], with: .none) + self?.tableView.reloadRows(at: [IndexPath(row: (self?.lastSystemChatPositionRow ?? 0) - 1, section: 0)], with: .none) + self?.tableView.endUpdates() + } + } + } + + private func loadNewChats(offset: Int) { + chatsProvider.getChatRooms(offset: offset, completion: { [weak self] in + DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: { + self?.isBusy = false + self?.tableView.reloadData() + }) + }) + } } // MARK: - NSFetchedResultsControllerDelegate extension ChatListViewController: NSFetchedResultsControllerDelegate { func controllerWillChangeContent(_ controller: NSFetchedResultsController) { + if isBusy { return } if controller == chatsController { tableView.beginUpdates() } } func controllerDidChangeContent(_ controller: NSFetchedResultsController) { + if isBusy { return } switch controller { case let c where c == chatsController: tableView.endUpdates() @@ -477,6 +573,7 @@ extension ChatListViewController: NSFetchedResultsControllerDelegate { } func controller(_ controller: NSFetchedResultsController, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) { + if isBusy { return } switch controller { // MARK: Chats controller case let c where c == chatsController: @@ -884,6 +981,23 @@ extension ChatListViewController { return vc.chatroom } + + /// First system botoom chat index + func getBottomSystemChatIndex() -> Int { + var index = 0 + try? chatsController?.performFetch() + chatsController?.fetchedObjects?.enumerated().forEach({ (i, room) in + guard index == 0, + let date = room.updatedAt as? Date, + date == Date.adamantNullDate + else { + return + } + index = i + }) + + return index + } } // MARK: Search diff --git a/Adamant/Stories/Chats/ChatTableViewCell.xib b/Adamant/Stories/Chats/ChatTableViewCell.xib index 253280fde..4bcd46133 100644 --- a/Adamant/Stories/Chats/ChatTableViewCell.xib +++ b/Adamant/Stories/Chats/ChatTableViewCell.xib @@ -1,12 +1,9 @@ - - - - + + - - + @@ -17,37 +14,37 @@ - + - + - + @@ -83,6 +80,11 @@ + + + + + diff --git a/Adamant/Stories/Chats/ChatViewController+MessageKit.swift b/Adamant/Stories/Chats/ChatViewController+MessageKit.swift index 78e6ebc20..c887946e3 100644 --- a/Adamant/Stories/Chats/ChatViewController+MessageKit.swift +++ b/Adamant/Stories/Chats/ChatViewController+MessageKit.swift @@ -68,9 +68,8 @@ extension ChatViewController: MessagesDataSource { switch transaction.statusEnum { case .failed: return NSAttributedString(string: String.adamantLocalized.chat.failToSend, attributes: [NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: 10), NSAttributedString.Key.foregroundColor: UIColor.adamant.primary]) - case .pending: - return NSAttributedString(string: String.adamantLocalized.chat.pending, attributes: [NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: 10), NSAttributedString.Key.foregroundColor: UIColor.adamant.primary]) + return nil case .delivered: return nil @@ -89,6 +88,21 @@ extension ChatViewController: MessagesDataSource { return nil } + if isFromCurrentSender(message: message), + let transaction = message as? ChatTransaction { + if case .pending = transaction.statusEnum { + let attachment = NSTextAttachment() + attachment.image = UIImage(named: "status_pending") + attachment.bounds = CGRect(x: 0, y: -1, width: 7, height: 7) + let attachmentStr = NSAttributedString(attachment: attachment) + + let mutableAttributedString = NSMutableAttributedString() + mutableAttributedString.append(attachmentStr) + + return mutableAttributedString + } + } + let humanizedTime = message.sentDate.humanizedTime() if let expire = humanizedTime.expireIn { @@ -288,6 +302,16 @@ extension ChatViewController: MessagesDisplayDelegate { let timeIntervalSinceLastMessage = message.sentDate.timeIntervalSince(previousMessage.sentDate) return timeIntervalSinceLastMessage >= self.showsDateHeaderAfterTimeInterval } + + func messageHeaderView(for indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> MessageReusableView { + let header = messagesCollectionView.dequeueReusableHeaderView(HeaderReusableView.self, for: indexPath) + if (indexPath.section == 0 && isBusy) { + header.setupLoadAnimating() + } else { + header.stopLoadAnimating() + } + return header + } } extension ChatViewController: MessageCellDelegate { @@ -500,12 +524,12 @@ extension ChatViewController: MessagesLayoutDelegate { guard let transaction = message as? ChatTransaction else { return 0 } - + switch transaction.statusEnum { - case .failed, .pending: + case .failed: return 16 - case .delivered: + case .delivered, .pending: return 0 } } else { @@ -524,10 +548,10 @@ extension ChatViewController: MessagesLayoutDelegate { } switch transaction.statusEnum { - case .failed, .pending: + case .failed: return 0 - case .delivered: + case .delivered, .pending: return 16 } } else { @@ -550,6 +574,14 @@ extension ChatViewController: MessagesLayoutDelegate { return (messagesCollectionView.collectionViewLayout as! MessagesCollectionViewFlowLayout).textMessageSizeCalculator } } + + func headerViewSize(for section: Int, in messagesCollectionView: MessagesCollectionView) -> CGSize { + if (section == 0 && isNeedToLoadMoore()) { + return CGSize(width: messagesCollectionView.bounds.width, height: HeaderReusableView.height) + } else { + return .zero + } + } } diff --git a/Adamant/Stories/Chats/ChatViewController.swift b/Adamant/Stories/Chats/ChatViewController.swift index a06b95a39..7d676a004 100644 --- a/Adamant/Stories/Chats/ChatViewController.swift +++ b/Adamant/Stories/Chats/ChatViewController.swift @@ -105,6 +105,7 @@ class ChatViewController: MessagesViewController { internal var showsDateHeaderAfterTimeInterval: TimeInterval = 3600 private var isFirstLayout = true + private var didLoaded = false // Content insets are broken after modal view dissapears private var fixKeyboardInsets = false @@ -122,6 +123,8 @@ class ChatViewController: MessagesViewController { var feeUpdateTimer: Timer? + private let headerHeight: CGFloat = 38 + // MARK: Rich Messages var richMessageProviders = [String:RichMessageProvider]() var cellCalculators = [String:CellSizeCalculator]() @@ -156,6 +159,8 @@ class ChatViewController: MessagesViewController { return richMessageStatusUpdating.contains(id) } + private let averageVisibleCount = 8 + // MARK: Fee label private var feeIsVisible: Bool = false private var feeTimer: Timer? @@ -196,6 +201,17 @@ class ChatViewController: MessagesViewController { return queue }() + // MARK: Busy indicator + + private var busyBackgroundView: UIView? + private var spinner = UIActivityIndicatorView(style: .whiteLarge) + var isBusy = false + + //MARK: Background UI + private let amadantLogoImageView: UIImageView = { + return UIImageView(image: UIImage(named: "Adamant-logo")) + }() + // MARK: - Lifecycle override func viewDidLoad() { super.viewDidLoad() @@ -219,6 +235,8 @@ class ChatViewController: MessagesViewController { messagesCollectionView.messageCellDelegate = self maintainPositionOnKeyboardFrameChanged = true + messagesCollectionView.register(HeaderReusableView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader) + if let layout = messagesCollectionView.collectionViewLayout as? MessagesCollectionViewFlowLayout { for messageSizeCalculator in layout.messageSizeCalculators() { messageSizeCalculator.outgoingAvatarSize = .zero @@ -282,6 +300,8 @@ class ChatViewController: MessagesViewController { $0.setImage(#imageLiteral(resourceName: "Arrow_innactive"), for: UIControl.State.disabled) } + messageInputBar.inputTextView.autocorrectionType = .no + if UIScreen.main.traitCollection.userInterfaceIdiom == .pad { self.edgesForExtendedLayout = UIRectEdge.top @@ -296,8 +316,11 @@ class ChatViewController: MessagesViewController { let keyboardHeight = notification.endFrame.height let tabBarHeight = self?.tabBarController?.tabBar.bounds.height ?? 0 - self?.messagesCollectionView.contentInset.bottom = barHeight + keyboardHeight - self?.messagesCollectionView.scrollIndicatorInsets.bottom = barHeight + keyboardHeight - tabBarHeight + if !isMacOS { + self?.messagesCollectionView.contentInset.bottom = barHeight + keyboardHeight + self?.messagesCollectionView.scrollIndicatorInsets.bottom = barHeight + keyboardHeight - tabBarHeight + } + DispatchQueue.main.async { self?.messagesCollectionView.scrollToBottom(animated: false) } @@ -398,10 +421,11 @@ class ChatViewController: MessagesViewController { scrollToBottomBtn.frame = CGRect.zero scrollToBottomBtn.translatesAutoresizingMaskIntoConstraints = false scrollToBottomBtn.addTarget(self, action: #selector(scrollDown), for: .touchUpInside) - + scrollToBottomBtn.isHidden = true self.view.addSubview(scrollToBottomBtn) keyboardManager.on(event: .willChangeFrame) { [weak self] (notification) in + if isMacOS { return } let barHeight = self?.messageInputBar.bounds.height ?? 0 let keyboardHeight = notification.endFrame.height @@ -422,6 +446,8 @@ class ChatViewController: MessagesViewController { UIMenuController.shared.menuItems = [ UIMenuItem(title: String.adamantLocalized.chat.remove, action: NSSelectorFromString("remove:")), UIMenuItem(title: String.adamantLocalized.chat.report, action: NSSelectorFromString("report:"))] + + loadFirstMessagesIfNeeded() } override func canPerformAction(_ action: Selector, withSender sender: Any!) -> Bool { @@ -507,9 +533,11 @@ class ChatViewController: MessagesViewController { scrollToBottomBtn.isHidden = chatPositionOffset < chatPositionDelata scrollToBottomBtnOffetConstraint?.constant = -20 - self.messageInputBar.bounds.height - if forceScrollToBottom ?? false { + if forceScrollToBottom ?? false && !scrollToBottomBtn.isHidden { scrollDown() } + + didLoaded = true } override func viewDidDisappear(_ animated: Bool) { @@ -530,7 +558,6 @@ class ChatViewController: MessagesViewController { override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() - guard let address = chatroom?.partner?.address else { return } // MARK: 4.2 Scroll to message @@ -540,6 +567,12 @@ class ChatViewController: MessagesViewController { isFirstLayout = false self.chatsProvider.chatPositon.removeValue(forKey: address) self.messageToShow = nil + + didLoaded = true + if indexPath.row >= 0 && indexPath.row <= averageVisibleCount { + self.loadMooreMessagesIfNeeded(indexPath: IndexPath(row: 0, section: 2)) + self.reloadTopSectionIfNeeded() + } return } } @@ -556,8 +589,8 @@ class ChatViewController: MessagesViewController { if let offset = self.chatsProvider.chatPositon[address] { self.chatPositionOffset = CGFloat(offset) self.scrollToBottomBtn.isHidden = chatPositionOffset < chatPositionDelata - let collectionViewContentHeight = messagesCollectionView.collectionViewLayout.collectionViewContentSize.height - CGFloat(offset) - (messagesCollectionView.scrollIndicatorInsets.bottom + messagesCollectionView.contentInset.bottom) - + let collectionViewContentHeight = messagesCollectionView.collectionViewLayout.collectionViewContentSize.height - CGFloat(offset) - (messagesCollectionView.scrollIndicatorInsets.bottom + messagesCollectionView.contentInset.bottom) + headerHeight + messagesCollectionView.performBatchUpdates(nil) { _ in self.messagesCollectionView.scrollRectToVisible(CGRect(x: 0.0, y: collectionViewContentHeight - 1.0, width: 1.0, height: 1.0), animated: false) } } else { @@ -791,7 +824,9 @@ extension ChatViewController: NSFetchedResultsControllerDelegate { } func controllerDidChangeContent(_ controller: NSFetchedResultsController) { - performBatchChanges(controllerChanges) + if !isBusy { + performBatchChanges(controllerChanges) + } } func controller(_ controller: NSFetchedResultsController, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) { @@ -824,9 +859,15 @@ extension ChatViewController: NSFetchedResultsControllerDelegate { let chat = messagesCollectionView var scrollToBottom = changes.first { $0.type == .insert } != nil + var forceScrollToBottom = false - if !isFirstLayout { - scrollToBottom = scrollToBottomBtn.isHidden + if !isFirstLayout && changes.first?.type != nil { + if self.forceScrollToBottom ?? false { + scrollToBottom = true + self.forceScrollToBottom = false + } else { + scrollToBottom = scrollToBottomBtn.isHidden + } } chat.performBatchUpdates({ @@ -840,6 +881,7 @@ extension ChatViewController: NSFetchedResultsControllerDelegate { chat.insertSections(IndexSet(integer: newIndexPath.row)) if scrollToBottom { chat.scrollToBottom(animated: true) + forceScrollToBottom = true } case .delete: @@ -858,14 +900,16 @@ extension ChatViewController: NSFetchedResultsControllerDelegate { guard let section = change.indexPath?.row else { continue } - - chat.reloadItems(at: [IndexPath(row: 0, section: section)]) + if chat.indexPathsForVisibleItems.contains(IndexPath(row: 0, section: section)) { + chat.reloadItems(at: [IndexPath(row: 0, section: section)]) + } + scrollToBottom = false @unknown default: break } } }, completion: { animationSuccess in - if scrollToBottom { + if scrollToBottom || forceScrollToBottom { chat.scrollToBottom(animated: animationSuccess) } }) @@ -1024,3 +1068,167 @@ private class StatusUpdateProcedure: Procedure { } } } + +// MARK: - Busy Indicator View +extension ChatViewController { + func setupBusyBackgroundView() { + busyBackgroundView = UIView() + busyBackgroundView?.backgroundColor = UIColor(white: 0, alpha: 0.1) + busyBackgroundView?.frame = view.frame + view.addSubview(busyBackgroundView!) + + spinner.translatesAutoresizingMaskIntoConstraints = false + spinner.startAnimating() + busyBackgroundView?.addSubview(spinner) + spinner.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true + spinner.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true + } + + func setBusyIndicator(state: Bool) { + isBusy = state + if busyBackgroundView == nil && state { + setBackgroundUI() + setupBusyBackgroundView() + messagesCollectionView.alpha = 0.0 + messageInputBar.sendButton.isEnabled = false + messageInputBar.inputTextView.isEditable = false + messageInputBar.leftStackView.isUserInteractionEnabled = false + } + + if !state { + if busyBackgroundView != nil { + reloadTopSectionIfNeeded() + } + + if chatroom?.isReadonly ?? false { + messageInputBar.inputTextView.backgroundColor = UIColor.adamant.chatSenderBackground + messageInputBar.inputTextView.isEditable = false + messageInputBar.sendButton.isEnabled = false + attachmentButton.isEnabled = false + } else { + messageInputBar.sendButton.isEnabled = true + messageInputBar.inputTextView.isEditable = true + messageInputBar.leftStackView.isUserInteractionEnabled = true + } + + UIView.animate(withDuration: 0.25, delay: 0.25) { [weak self] in + self?.busyBackgroundView?.backgroundColor = .clear + self?.messagesCollectionView.alpha = 1.0 + self?.amadantLogoImageView.alpha = 0.0 + } completion: { [weak self] _ in + self?.busyBackgroundView?.removeFromSuperview() + } + } + } +} + +//MARK: Load moore message +extension ChatViewController { + func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { + loadMooreMessagesIfNeeded(indexPath: indexPath) + } + + func loadFirstMessagesIfNeeded() { + guard let address = chatroom?.partner?.address else { return } + + if let isLoaded = chatsProvider.isChatLoaded[address], + isLoaded { + setBusyIndicator(state: false) + return + } + + if address == AdamantContacts.adamantWelcomeWallet.name { + setBusyIndicator(state: false) + return + } + + setBusyIndicator(state: true) + + chatsProvider.getChatMessages(with: address, offset: 0) { [weak self] count in + DispatchQueue.main.async { + self?.messagesCollectionView.reloadDataAndKeepOffset() + DispatchQueue.main.asyncAfter(deadline: .now() + 1) { + if count > 0 { + self?.messagesCollectionView.scrollToItem(at: IndexPath(row: 0, section: count - 1), at: .top, animated: false) + } + self?.setBusyIndicator(state: false) + } + } + } + } + + func loadMooreMessagesIfNeeded(indexPath: IndexPath) { + guard indexPath.section < 4, + let address = chatroom?.partner?.address, + !isBusy, + isNeedToLoadMoore(), + didLoaded + else { + return + } + + if address == AdamantContacts.adamantWelcomeWallet.name { return } + isBusy = true + let offset = chatsProvider.chatLoadedMessages[address] ?? 0 + chatsProvider.getChatMessages(with: address, offset: offset) { [weak self] _count in + DispatchQueue.main.async { + self?.messagesCollectionView.reloadDataAndKeepOffset() + self?.isBusy = false + self?.reloadTopSectionIfNeeded() + } + } + } + + func reloadTopSectionIfNeeded() { + try? chatController?.performFetch() + if let count = chatController?.fetchedObjects?.count, + count >= 1 { + self.messagesCollectionView.reloadSections(IndexSet(integer: 0)) + } + } + + func isNeedToLoadMoore() -> Bool { + if let address = chatroom?.partner?.address, + chatsProvider.chatLoadedMessages[address] ?? 0 < chatsProvider.chatMaxMessages[address] ?? 0 { + return true + } + return false + } +} + +// MARK: - Background UI +extension ChatViewController { + func setBackgroundUI() { + amadantLogoImageView.translatesAutoresizingMaskIntoConstraints = false + view.addSubview(amadantLogoImageView) + amadantLogoImageView.heightAnchor.constraint(equalToConstant: 100).isActive = true + amadantLogoImageView.widthAnchor.constraint(equalToConstant: 100).isActive = true + amadantLogoImageView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true + amadantLogoImageView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true + } +} + +// MARK: Mac OS HotKeys +extension InputTextView { + open override var keyCommands: [UIKeyCommand]? { + let commands = [UIKeyCommand(input: "\r", modifierFlags: [], action: #selector(sendKey(sender:))), + UIKeyCommand(input: "\r", modifierFlags: .alternate, action: #selector(newLineKey(sender:))), + UIKeyCommand(input: "\r", modifierFlags: .control, action: #selector(newLineKey(sender:)))] + if #available(iOS 15, *) { + commands.forEach { $0.wantsPriorityOverSystemBehavior = true } + } + return commands + } + + @objc func sendKey(sender: UIKeyCommand) { + if sender.modifierFlags == .control || sender.modifierFlags == .alternate { + newLineKey(sender: sender) + } else { + messageInputBar?.didSelectSendButton() + } + } + + @objc func newLineKey(sender: UIKeyCommand) { + messageInputBar?.inputTextView.text += "\n" + } +} diff --git a/Adamant/Stories/Chats/ComplexTransferViewController.swift b/Adamant/Stories/Chats/ComplexTransferViewController.swift index c29f73812..e3226886b 100644 --- a/Adamant/Stories/Chats/ComplexTransferViewController.swift +++ b/Adamant/Stories/Chats/ComplexTransferViewController.swift @@ -37,7 +37,7 @@ class ComplexTransferViewController: UIViewController { view.backgroundColor = UIColor.white if let partner = partner { - navigationItem.title = partner.name ?? partner.address + navigationItem.title = partner.name?.checkAndReplaceSystemWallets() ?? partner.address } navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(cancel)) @@ -88,7 +88,7 @@ extension ComplexTransferViewController: PagingViewControllerDataSource { let vc = service.transferViewController() if let v = vc as? TransferViewControllerBase { if let address = partner?.address { - let name = partner?.name + let name = partner?.name?.checkAndReplaceSystemWallets() v.admReportRecipient = address v.recipientIsReadonly = true v.commentsEnabled = service.commentsEnabledForRichMessages diff --git a/Adamant/Stories/Chats/HeaderReusableView.swift b/Adamant/Stories/Chats/HeaderReusableView.swift new file mode 100644 index 000000000..11520dd88 --- /dev/null +++ b/Adamant/Stories/Chats/HeaderReusableView.swift @@ -0,0 +1,101 @@ +// +// HeaderReusableView.swift +// Adamant +// +// Created by Stanislav Jelezoglo on 27.05.2022. +// Copyright © 2022 Adamant. All rights reserved. +// + +import Foundation +import MessageKit + +class HeaderReusableView: MessageReusableView { + // MARK: - Private Properties + static private let insets = UIEdgeInsets(top: 12, left: 80, bottom: 12, right: 80) + + private var spinner = UIActivityIndicatorView(style: .gray) + + // MARK: - Public Methods + static var height: CGFloat { + return insets.top + insets.bottom + 27 + } + + override init(frame: CGRect) { + super.init(frame: frame) + createUI() + } + + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + createUI() + } + + /// Start animation. + func setupLoadAnimating() { + spinner.startAnimating() + spinner.isHidden = false + } + + /// Stop animation. + func stopLoadAnimating() { + spinner.stopAnimating() + spinner.isHidden = true + } + + override func prepareForReuse() { + spinner.stopAnimating() + } + + // MARK: - Private Methods + private func createUI() { + spinner.translatesAutoresizingMaskIntoConstraints = false + addSubview(spinner) + spinner.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true + spinner.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true + } +} + +class LoadingTableViewCell: UITableViewCell { + // MARK: - Private Properties + static private let insets = UIEdgeInsets(top: 12, left: 80, bottom: 12, right: 80) + + private var spinner = UIActivityIndicatorView(style: .gray) + + // MARK: - Public Methods + static var height: CGFloat { + return insets.top + insets.bottom + 27 + } + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + createUI() + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + /// Start animation. + func startLoadAnimating() { + spinner.startAnimating() + spinner.isHidden = false + } + + /// Stop animation. + func stopLoadAnimating() { + spinner.stopAnimating() + spinner.isHidden = true + } + + override func prepareForReuse() { + spinner.stopAnimating() + } + + // MARK: - Private Methods + private func createUI() { + spinner.translatesAutoresizingMaskIntoConstraints = false + addSubview(spinner) + spinner.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true + spinner.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true + } +} diff --git a/Adamant/Stories/Shared/WelcomeViewController.xib b/Adamant/Stories/Shared/WelcomeViewController.xib index 4d0f64e5f..a9d154b98 100644 --- a/Adamant/Stories/Shared/WelcomeViewController.xib +++ b/Adamant/Stories/Shared/WelcomeViewController.xib @@ -1,11 +1,9 @@ - - - - + + - + @@ -22,7 +20,7 @@ - + @@ -31,7 +29,8 @@ - + + @@ -41,11 +40,13 @@ - + + + diff --git a/Adamant/Utilities/KeyboardManager.swift b/Adamant/Utilities/KeyboardManager.swift index 6850d342d..ed01feb63 100644 --- a/Adamant/Utilities/KeyboardManager.swift +++ b/Adamant/Utilities/KeyboardManager.swift @@ -141,7 +141,7 @@ open class KeyboardManager: NSObject, UIGestureRecognizerDelegate { guard let superview = inputAccessoryView.superview else { fatalError("`inputAccessoryView` must have a superview") } - let tabBarHeight = tabBar?.bounds.size.height ?? 0 + let tabBarHeight = isMacOS ? 0 : tabBar?.bounds.size.height ?? 0 self.inputAccessoryView = inputAccessoryView inputAccessoryView.translatesAutoresizingMaskIntoConstraints = false constraints = NSLayoutConstraintSet( diff --git a/Adamant/Wallets/Adamant/AdmTransferViewController.swift b/Adamant/Wallets/Adamant/AdmTransferViewController.swift index 0e27e794a..90fe70116 100644 --- a/Adamant/Wallets/Adamant/AdmTransferViewController.swift +++ b/Adamant/Wallets/Adamant/AdmTransferViewController.swift @@ -186,7 +186,7 @@ class AdmTransferViewController: TransferViewControllerBase { view.frame = prefix.frame $0.cell.textField.leftView = view $0.cell.textField.leftViewMode = .always - + $0.cell.textField.autocorrectionType = .no if recipientIsReadonly { $0.disabled = true // prefix.isEnabled = false diff --git a/Adamant/Wallets/Dash/DashTransferViewController.swift b/Adamant/Wallets/Dash/DashTransferViewController.swift index 73ccd4142..835e78e4a 100644 --- a/Adamant/Wallets/Dash/DashTransferViewController.swift +++ b/Adamant/Wallets/Dash/DashTransferViewController.swift @@ -181,6 +181,7 @@ class DashTransferViewController: TransferViewControllerBase { let row = TextRow() { $0.tag = BaseRows.address.tag $0.cell.textField.placeholder = String.adamantLocalized.newChat.addressPlaceholder + $0.cell.textField.autocorrectionType = .no if let recipient = recipientAddress { $0.value = recipient diff --git a/Adamant/Wallets/Doge/DogeTransferViewController.swift b/Adamant/Wallets/Doge/DogeTransferViewController.swift index 07402cf68..ffa2a9656 100644 --- a/Adamant/Wallets/Doge/DogeTransferViewController.swift +++ b/Adamant/Wallets/Doge/DogeTransferViewController.swift @@ -167,6 +167,7 @@ class DogeTransferViewController: TransferViewControllerBase { let row = TextRow() { $0.tag = BaseRows.address.tag $0.cell.textField.placeholder = String.adamantLocalized.newChat.addressPlaceholder + $0.cell.textField.autocorrectionType = .no if let recipient = recipientAddress { $0.value = recipient diff --git a/Adamant/Wallets/ERC20/ERC20TransferViewController.swift b/Adamant/Wallets/ERC20/ERC20TransferViewController.swift index 10fee704d..379d66e21 100644 --- a/Adamant/Wallets/ERC20/ERC20TransferViewController.swift +++ b/Adamant/Wallets/ERC20/ERC20TransferViewController.swift @@ -156,6 +156,7 @@ class ERC20TransferViewController: TransferViewControllerBase { $0.tag = BaseRows.address.tag $0.cell.textField.placeholder = String.adamantLocalized.newChat.addressPlaceholder $0.cell.textField.keyboardType = UIKeyboardType.namePhonePad + $0.cell.textField.autocorrectionType = .no if let recipient = recipientAddress { let trimmed = recipient.components(separatedBy: EthTransferViewController.invalidCharacters).joined() diff --git a/Adamant/Wallets/Ethereum/EthTransferViewController.swift b/Adamant/Wallets/Ethereum/EthTransferViewController.swift index 6f985b463..f141f2734 100644 --- a/Adamant/Wallets/Ethereum/EthTransferViewController.swift +++ b/Adamant/Wallets/Ethereum/EthTransferViewController.swift @@ -151,6 +151,7 @@ class EthTransferViewController: TransferViewControllerBase { $0.tag = BaseRows.address.tag $0.cell.textField.placeholder = String.adamantLocalized.newChat.addressPlaceholder $0.cell.textField.keyboardType = UIKeyboardType.namePhonePad + $0.cell.textField.autocorrectionType = .no if let recipient = recipientAddress { let trimmed = recipient.components(separatedBy: EthTransferViewController.invalidCharacters).joined() diff --git a/Adamant/Wallets/Lisk/LskTransferViewController.swift b/Adamant/Wallets/Lisk/LskTransferViewController.swift index f7b701976..ab669d7fb 100644 --- a/Adamant/Wallets/Lisk/LskTransferViewController.swift +++ b/Adamant/Wallets/Lisk/LskTransferViewController.swift @@ -159,6 +159,7 @@ class LskTransferViewController: TransferViewControllerBase { $0.tag = BaseRows.address.tag $0.cell.textField.placeholder = String.adamantLocalized.newChat.addressPlaceholder $0.cell.textField.keyboardType = UIKeyboardType.alphabet + $0.cell.textField.autocorrectionType = .no if let recipient = recipientAddress { $0.value = recipient diff --git a/Adamant/Wallets/TransactionDetailsViewControllerBase.swift b/Adamant/Wallets/TransactionDetailsViewControllerBase.swift index 96aa3709f..ffb204f20 100644 --- a/Adamant/Wallets/TransactionDetailsViewControllerBase.swift +++ b/Adamant/Wallets/TransactionDetailsViewControllerBase.swift @@ -204,7 +204,7 @@ class TransactionDetailsViewControllerBase: FormViewController { if let transaction = transaction { if transaction.senderAddress.count == 0 { $0.value = DoubleDetail(first: TransactionDetailsViewControllerBase.awaitingValueString, second: nil) - } else if let senderName = self?.senderName { + } else if let senderName = self?.senderName?.checkAndReplaceSystemWallets() { $0.value = DoubleDetail(first: senderName, second: transaction.senderAddress) } else { $0.value = DoubleDetail(first: transaction.senderAddress, second: nil) @@ -236,7 +236,7 @@ class TransactionDetailsViewControllerBase: FormViewController { if let transaction = self?.transaction { if transaction.senderAddress.count == 0 { row.value = DoubleDetail(first: TransactionDetailsViewControllerBase.awaitingValueString, second: nil) - } else if let senderName = self?.senderName { + } else if let senderName = self?.senderName?.checkAndReplaceSystemWallets() { row.value = DoubleDetail(first: senderName, second: transaction.senderAddress) } else { row.value = DoubleDetail(first: transaction.senderAddress, second: nil) @@ -255,7 +255,7 @@ class TransactionDetailsViewControllerBase: FormViewController { $0.cell.titleLabel.text = Rows.to.localized if let transaction = transaction { - if let recipientName = self?.recipientName { + if let recipientName = self?.recipientName?.checkAndReplaceSystemWallets() { $0.value = DoubleDetail(first: recipientName, second: transaction.recipientAddress) } else { $0.value = DoubleDetail(first: transaction.recipientAddress, second: nil) diff --git a/Adamant/Wallets/WalletViewControllerBase.swift b/Adamant/Wallets/WalletViewControllerBase.swift index b76441880..9cf1b6b1a 100644 --- a/Adamant/Wallets/WalletViewControllerBase.swift +++ b/Adamant/Wallets/WalletViewControllerBase.swift @@ -107,7 +107,13 @@ class WalletViewControllerBase: FormViewController, WalletViewController { let height = $0.value?.fiat != nil ? BalanceTableViewCell.fullHeight : BalanceTableViewCell.compactHeight $0.cell.height = { height } - }.cellUpdate { (cell, row) in + }.cellUpdate { [weak self] (cell, row) in + let symbol = self?.service?.tokenSymbol ?? "" + if let service = self?.service, let wallet = service.wallet { + row.value = self?.balanceRowValueFor(balance: wallet.balance, symbol: symbol, alert: wallet.notifications) + } else { + row.value = self?.balanceRowValueFor(balance: 0, symbol: symbol, alert: 0) + } let height = row.value?.fiat != nil ? BalanceTableViewCell.fullHeight : BalanceTableViewCell.compactHeight cell.height = { height } diff --git a/AdamantShared/AdamantResources.swift b/AdamantShared/AdamantResources.swift index 3c8a0f16f..3cde355d5 100644 --- a/AdamantShared/AdamantResources.swift +++ b/AdamantShared/AdamantResources.swift @@ -55,6 +55,8 @@ struct AdamantResources { // MARK: Contacts struct contacts { + static let adamantWelcomeWallet = "U00000000000000000001" + static let adamantBountyWallet = "U15423595369615486571" static let adamantBountyWalletPK = "cdab95b082b9774bd975677c868261618c7ce7bea97d02e0f56d483e30c077b6" diff --git a/AdamantShared/Models/ChatRooms.swift b/AdamantShared/Models/ChatRooms.swift new file mode 100644 index 000000000..fcd712c36 --- /dev/null +++ b/AdamantShared/Models/ChatRooms.swift @@ -0,0 +1,32 @@ +// +// ChatRooms.swift +// Adamant +// +// Created by Stanislav Jelezoglo on 20.05.2022. +// Copyright © 2022 Adamant. All rights reserved. +// + +import Foundation +struct ChatRooms : Codable { + let chats : [ChatRoomsChats]? + let messages : [Transaction]? + let count : Int? + + enum CodingKeys: String, CodingKey { + case chats = "chats" + case count = "count" + case messages = "messages" + } + + init(from decoder: Decoder) throws { + let values = try decoder.container(keyedBy: CodingKeys.self) + chats = try values.decodeIfPresent([ChatRoomsChats].self, forKey: .chats) + count = Int(try (values.decodeIfPresent(String.self, forKey: .count) ?? "0")) ?? 0 + messages = try values.decodeIfPresent([Transaction].self, forKey: .messages) + } + +} + +extension ChatRooms: WrappableCollection { + static let CollectionKey = "chatRooms" +} diff --git a/AdamantShared/Models/ChatRoomsChats.swift b/AdamantShared/Models/ChatRoomsChats.swift new file mode 100644 index 000000000..42b4e53b0 --- /dev/null +++ b/AdamantShared/Models/ChatRoomsChats.swift @@ -0,0 +1,24 @@ +// +// ChatRoomsChat.swift +// Adamant +// +// Created by Stanislav Jelezoglo on 20.05.2022. +// Copyright © 2022 Adamant. All rights reserved. +// + +import Foundation + +struct ChatRoomsChats : Codable { + + let lastTransaction : Transaction? + + enum CodingKeys: String, CodingKey { + case lastTransaction = "lastTransaction" + } + + init(from decoder: Decoder) throws { + let values = try decoder.container(keyedBy: CodingKeys.self) + lastTransaction = try values.decodeIfPresent(Transaction.self, forKey: .lastTransaction) + } + +} diff --git a/AdamantShared/Models/Transaction.swift b/AdamantShared/Models/Transaction.swift index 9243013e4..0cc7fc11c 100644 --- a/AdamantShared/Models/Transaction.swift +++ b/AdamantShared/Models/Transaction.swift @@ -63,7 +63,7 @@ extension Transaction: Codable { self.senderId = try container.decode(String.self, forKey: .senderId) self.recipientId = (try? container.decode(String.self, forKey: .recipientId)) ?? "" self.recipientPublicKey = try? container.decode(String.self, forKey: .recipientPublicKey) - self.signature = try container.decode(String.self, forKey: .signature) + self.signature = (try? container.decode(String.self, forKey: .signature)) ?? "" self.confirmations = (try? container.decode(Int64.self, forKey: .confirmations)) ?? 0 self.requesterPublicKey = try? container.decode(String.self, forKey: .requesterPublicKey) self.signSignature = try? container.decode(String.self, forKey: .signSignature) diff --git a/MessageNotificationContentExtension/Debug.entitlements b/MessageNotificationContentExtension/Debug.entitlements index f0007976d..6aa188f4e 100644 --- a/MessageNotificationContentExtension/Debug.entitlements +++ b/MessageNotificationContentExtension/Debug.entitlements @@ -2,6 +2,10 @@ + com.apple.security.app-sandbox + + com.apple.security.network.client + keychain-access-groups $(AppIdentifierPrefix)im.adamant.messenger-dev diff --git a/MessageNotificationContentExtension/Release.entitlements b/MessageNotificationContentExtension/Release.entitlements index 28b4a0f7d..155317021 100644 --- a/MessageNotificationContentExtension/Release.entitlements +++ b/MessageNotificationContentExtension/Release.entitlements @@ -2,6 +2,10 @@ + com.apple.security.app-sandbox + + com.apple.security.network.client + keychain-access-groups $(AppIdentifierPrefix)im.adamant.messenger diff --git a/NotificationServiceExtension/Debug.entitlements b/NotificationServiceExtension/Debug.entitlements index f0007976d..6aa188f4e 100644 --- a/NotificationServiceExtension/Debug.entitlements +++ b/NotificationServiceExtension/Debug.entitlements @@ -2,6 +2,10 @@ + com.apple.security.app-sandbox + + com.apple.security.network.client + keychain-access-groups $(AppIdentifierPrefix)im.adamant.messenger-dev diff --git a/NotificationServiceExtension/Release.entitlements b/NotificationServiceExtension/Release.entitlements index 28b4a0f7d..155317021 100644 --- a/NotificationServiceExtension/Release.entitlements +++ b/NotificationServiceExtension/Release.entitlements @@ -2,6 +2,10 @@ + com.apple.security.app-sandbox + + com.apple.security.network.client + keychain-access-groups $(AppIdentifierPrefix)im.adamant.messenger diff --git a/Podfile.lock b/Podfile.lock index 0c3b61078..da3ae8778 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -47,4 +47,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: 1d081ac466709ab7ca1f43d0d3bf418f86bd7d6d -COCOAPODS: 1.11.2 +COCOAPODS: 1.11.3 diff --git a/TransferNotificationContentExtension/Debug.entitlements b/TransferNotificationContentExtension/Debug.entitlements index f0007976d..6aa188f4e 100644 --- a/TransferNotificationContentExtension/Debug.entitlements +++ b/TransferNotificationContentExtension/Debug.entitlements @@ -2,6 +2,10 @@ + com.apple.security.app-sandbox + + com.apple.security.network.client + keychain-access-groups $(AppIdentifierPrefix)im.adamant.messenger-dev diff --git a/TransferNotificationContentExtension/Release.entitlements b/TransferNotificationContentExtension/Release.entitlements index 28b4a0f7d..155317021 100644 --- a/TransferNotificationContentExtension/Release.entitlements +++ b/TransferNotificationContentExtension/Release.entitlements @@ -2,6 +2,10 @@ + com.apple.security.app-sandbox + + com.apple.security.network.client + keychain-access-groups $(AppIdentifierPrefix)im.adamant.messenger