From 7556e3bd8cd8530c544ebc99a6fd5b269374caec Mon Sep 17 00:00:00 2001 From: Anton Makarov Date: Wed, 24 Oct 2018 13:08:21 +0300 Subject: [PATCH 01/68] Init new scheme --- .../NynjaUIKit.xcodeproj/project.pbxproj | 89 ++++++ Nynja-Share/Resources/Info.plist | 2 +- Nynja.xcodeproj/project.pbxproj | 274 ++++++++++++++++++ .../xcshareddata/xcschemes/LoadDB.xcscheme | 102 +++++++ .../AppIconChannels.appiconset/Contents.json | 158 ---------- .../Icon-App-20x20@1x.png | Bin 748 -> 0 bytes .../Icon-App-20x20@2x.png | Bin 1624 -> 0 bytes .../Icon-App-20x20@3x.png | Bin 2590 -> 0 bytes .../Icon-App-29x29@1x.png | Bin 1081 -> 0 bytes .../Icon-App-29x29@2x.png | Bin 2487 -> 0 bytes .../Icon-App-29x29@3x.png | Bin 3917 -> 0 bytes .../Icon-App-40x40@1x.png | Bin 1624 -> 0 bytes .../Icon-App-40x40@2x.png | Bin 3589 -> 0 bytes .../Icon-App-40x40@3x.png | Bin 5565 -> 0 bytes .../Icon-App-57x57@1x.png | Bin 2424 -> 0 bytes .../Icon-App-57x57@2x.png | Bin 5318 -> 0 bytes .../Icon-App-60x60@2x.png | Bin 5565 -> 0 bytes .../Icon-App-60x60@3x.png | Bin 8840 -> 0 bytes .../Icon-App-72x72@1x.png | Bin 3196 -> 0 bytes .../Icon-App-72x72@2x.png | Bin 6861 -> 0 bytes .../Icon-App-76x76@1x.png | Bin 3397 -> 0 bytes .../Icon-App-76x76@2x.png | Bin 7256 -> 0 bytes .../Icon-App-83.5x83.5@2x.png | Bin 8159 -> 0 bytes .../Icon-Small-50x50@1x.png | Bin 2072 -> 0 bytes .../Icon-Small-50x50@2x.png | Bin 4609 -> 0 bytes .../ItunesArtwork@2x.png | Bin 67866 -> 0 bytes .../AppIconSpotify.appiconset/Contents.json | 24 -- .../Icon-App-60x60@1x.png | Bin 2590 -> 0 bytes .../Icon-App-76x76@3x.png | Bin 11523 -> 0 bytes .../AppIconStickers.appiconset/Contents.json | 158 ---------- .../Icon-App-20x20@1x.png | Bin 748 -> 0 bytes .../Icon-App-20x20@2x.png | Bin 1624 -> 0 bytes .../Icon-App-20x20@3x.png | Bin 2590 -> 0 bytes .../Icon-App-29x29@1x.png | Bin 1081 -> 0 bytes .../Icon-App-29x29@2x.png | Bin 2487 -> 0 bytes .../Icon-App-29x29@3x.png | Bin 3917 -> 0 bytes .../Icon-App-40x40@1x.png | Bin 1624 -> 0 bytes .../Icon-App-40x40@2x.png | Bin 3589 -> 0 bytes .../Icon-App-40x40@3x.png | Bin 5565 -> 0 bytes .../Icon-App-57x57@1x.png | Bin 2424 -> 0 bytes .../Icon-App-57x57@2x.png | Bin 5318 -> 0 bytes .../Icon-App-60x60@2x.png | Bin 5565 -> 0 bytes .../Icon-App-60x60@3x.png | Bin 8840 -> 0 bytes .../Icon-App-72x72@1x.png | Bin 3196 -> 0 bytes .../Icon-App-72x72@2x.png | Bin 6861 -> 0 bytes .../Icon-App-76x76@1x.png | Bin 3397 -> 0 bytes .../Icon-App-76x76@2x.png | Bin 7256 -> 0 bytes .../Icon-App-83.5x83.5@2x.png | Bin 8159 -> 0 bytes .../Icon-Small-50x50@1x.png | Bin 2072 -> 0 bytes .../Icon-Small-50x50@2x.png | Bin 4609 -> 0 bytes .../ItunesArtwork@2x.png | Bin 67866 -> 0 bytes Nynja/Resources/Info.plist | 2 +- Nynja/Resources/LoadDBConfig.xcconfig | 20 ++ Nynja/Resources/ThirdPartyServices.swift | 11 +- 54 files changed, 493 insertions(+), 347 deletions(-) create mode 100644 Nynja.xcodeproj/xcshareddata/xcschemes/LoadDB.xcscheme delete mode 100644 Nynja/Resources/Assets.xcassets/AppIconChannels.appiconset/Contents.json delete mode 100644 Nynja/Resources/Assets.xcassets/AppIconChannels.appiconset/Icon-App-20x20@1x.png delete mode 100644 Nynja/Resources/Assets.xcassets/AppIconChannels.appiconset/Icon-App-20x20@2x.png delete mode 100644 Nynja/Resources/Assets.xcassets/AppIconChannels.appiconset/Icon-App-20x20@3x.png delete mode 100644 Nynja/Resources/Assets.xcassets/AppIconChannels.appiconset/Icon-App-29x29@1x.png delete mode 100644 Nynja/Resources/Assets.xcassets/AppIconChannels.appiconset/Icon-App-29x29@2x.png delete mode 100644 Nynja/Resources/Assets.xcassets/AppIconChannels.appiconset/Icon-App-29x29@3x.png delete mode 100644 Nynja/Resources/Assets.xcassets/AppIconChannels.appiconset/Icon-App-40x40@1x.png delete mode 100644 Nynja/Resources/Assets.xcassets/AppIconChannels.appiconset/Icon-App-40x40@2x.png delete mode 100644 Nynja/Resources/Assets.xcassets/AppIconChannels.appiconset/Icon-App-40x40@3x.png delete mode 100644 Nynja/Resources/Assets.xcassets/AppIconChannels.appiconset/Icon-App-57x57@1x.png delete mode 100644 Nynja/Resources/Assets.xcassets/AppIconChannels.appiconset/Icon-App-57x57@2x.png delete mode 100644 Nynja/Resources/Assets.xcassets/AppIconChannels.appiconset/Icon-App-60x60@2x.png delete mode 100644 Nynja/Resources/Assets.xcassets/AppIconChannels.appiconset/Icon-App-60x60@3x.png delete mode 100644 Nynja/Resources/Assets.xcassets/AppIconChannels.appiconset/Icon-App-72x72@1x.png delete mode 100644 Nynja/Resources/Assets.xcassets/AppIconChannels.appiconset/Icon-App-72x72@2x.png delete mode 100644 Nynja/Resources/Assets.xcassets/AppIconChannels.appiconset/Icon-App-76x76@1x.png delete mode 100644 Nynja/Resources/Assets.xcassets/AppIconChannels.appiconset/Icon-App-76x76@2x.png delete mode 100644 Nynja/Resources/Assets.xcassets/AppIconChannels.appiconset/Icon-App-83.5x83.5@2x.png delete mode 100644 Nynja/Resources/Assets.xcassets/AppIconChannels.appiconset/Icon-Small-50x50@1x.png delete mode 100644 Nynja/Resources/Assets.xcassets/AppIconChannels.appiconset/Icon-Small-50x50@2x.png delete mode 100644 Nynja/Resources/Assets.xcassets/AppIconChannels.appiconset/ItunesArtwork@2x.png delete mode 100644 Nynja/Resources/Assets.xcassets/AppIconSpotify.appiconset/Icon-App-60x60@1x.png delete mode 100644 Nynja/Resources/Assets.xcassets/AppIconSpotify.appiconset/Icon-App-76x76@3x.png delete mode 100755 Nynja/Resources/Assets.xcassets/AppIconStickers.appiconset/Contents.json delete mode 100644 Nynja/Resources/Assets.xcassets/AppIconStickers.appiconset/Icon-App-20x20@1x.png delete mode 100644 Nynja/Resources/Assets.xcassets/AppIconStickers.appiconset/Icon-App-20x20@2x.png delete mode 100644 Nynja/Resources/Assets.xcassets/AppIconStickers.appiconset/Icon-App-20x20@3x.png delete mode 100644 Nynja/Resources/Assets.xcassets/AppIconStickers.appiconset/Icon-App-29x29@1x.png delete mode 100644 Nynja/Resources/Assets.xcassets/AppIconStickers.appiconset/Icon-App-29x29@2x.png delete mode 100644 Nynja/Resources/Assets.xcassets/AppIconStickers.appiconset/Icon-App-29x29@3x.png delete mode 100644 Nynja/Resources/Assets.xcassets/AppIconStickers.appiconset/Icon-App-40x40@1x.png delete mode 100644 Nynja/Resources/Assets.xcassets/AppIconStickers.appiconset/Icon-App-40x40@2x.png delete mode 100644 Nynja/Resources/Assets.xcassets/AppIconStickers.appiconset/Icon-App-40x40@3x.png delete mode 100644 Nynja/Resources/Assets.xcassets/AppIconStickers.appiconset/Icon-App-57x57@1x.png delete mode 100644 Nynja/Resources/Assets.xcassets/AppIconStickers.appiconset/Icon-App-57x57@2x.png delete mode 100644 Nynja/Resources/Assets.xcassets/AppIconStickers.appiconset/Icon-App-60x60@2x.png delete mode 100644 Nynja/Resources/Assets.xcassets/AppIconStickers.appiconset/Icon-App-60x60@3x.png delete mode 100644 Nynja/Resources/Assets.xcassets/AppIconStickers.appiconset/Icon-App-72x72@1x.png delete mode 100644 Nynja/Resources/Assets.xcassets/AppIconStickers.appiconset/Icon-App-72x72@2x.png delete mode 100644 Nynja/Resources/Assets.xcassets/AppIconStickers.appiconset/Icon-App-76x76@1x.png delete mode 100644 Nynja/Resources/Assets.xcassets/AppIconStickers.appiconset/Icon-App-76x76@2x.png delete mode 100644 Nynja/Resources/Assets.xcassets/AppIconStickers.appiconset/Icon-App-83.5x83.5@2x.png delete mode 100644 Nynja/Resources/Assets.xcassets/AppIconStickers.appiconset/Icon-Small-50x50@1x.png delete mode 100644 Nynja/Resources/Assets.xcassets/AppIconStickers.appiconset/Icon-Small-50x50@2x.png delete mode 100644 Nynja/Resources/Assets.xcassets/AppIconStickers.appiconset/ItunesArtwork@2x.png create mode 100644 Nynja/Resources/LoadDBConfig.xcconfig diff --git a/Frameworks/NynjaUIKit/NynjaUIKit.xcodeproj/project.pbxproj b/Frameworks/NynjaUIKit/NynjaUIKit.xcodeproj/project.pbxproj index f64833620..9923d3c0b 100644 --- a/Frameworks/NynjaUIKit/NynjaUIKit.xcodeproj/project.pbxproj +++ b/Frameworks/NynjaUIKit/NynjaUIKit.xcodeproj/project.pbxproj @@ -36,6 +36,7 @@ /* Begin PBXFileReference section */ 1820AD65D6897E3E306C16A2 /* Pods-NynjaUIKit.translate.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NynjaUIKit.translate.xcconfig"; path = "../../Pods/Target Support Files/Pods-NynjaUIKit/Pods-NynjaUIKit.translate.xcconfig"; sourceTree = ""; }; + 600B9308D041B8E4DE0DBEF1 /* Pods-NynjaUIKit.loaddb.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NynjaUIKit.loaddb.xcconfig"; path = "../../Pods/Target Support Files/Pods-NynjaUIKit/Pods-NynjaUIKit.loaddb.xcconfig"; sourceTree = ""; }; 7336042AC840197E622730FD /* Pods-NynjaUIKit.prerelease.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NynjaUIKit.prerelease.xcconfig"; path = "../../Pods/Target Support Files/Pods-NynjaUIKit/Pods-NynjaUIKit.prerelease.xcconfig"; sourceTree = ""; }; 8514D4C120EE27080002378A /* NynjaUIKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = NynjaUIKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 8514D4C420EE27080002378A /* NynjaUIKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NynjaUIKit.h; sourceTree = ""; }; @@ -91,6 +92,7 @@ F6A55196057E1D4A2F24AC17 /* Pods-NynjaUIKit.devautotests.xcconfig */, E966C049045184395EBB9166 /* Pods-NynjaUIKit.release.xcconfig */, 7336042AC840197E622730FD /* Pods-NynjaUIKit.prerelease.xcconfig */, + 600B9308D041B8E4DE0DBEF1 /* Pods-NynjaUIKit.loaddb.xcconfig */, ); name = Pods; sourceTree = ""; @@ -434,6 +436,91 @@ /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ + 2611CEEC21807A6E00FFD4DD /* LoadDB */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = B90E6396110C47D18FB00838 /* Pods-NynjaUIKit.dev.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = LoadDB; + }; + 2611CEED21807A6E00FFD4DD /* LoadDB */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 600B9308D041B8E4DE0DBEF1 /* Pods-NynjaUIKit.loaddb.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Manual; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = NynjaUIKit/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.nynja.mobile.communicator.NynjaUIKit; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SKIP_INSTALL = YES; + SWIFT_VERSION = 4.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = LoadDB; + }; 8514D4C820EE27080002378A /* Dev */ = { isa = XCBuildConfiguration; baseConfigurationReference = B90E6396110C47D18FB00838 /* Pods-NynjaUIKit.dev.xcconfig */; @@ -951,6 +1038,7 @@ isa = XCConfigurationList; buildConfigurations = ( 8514D4C820EE27080002378A /* Dev */, + 2611CEEC21807A6E00FFD4DD /* LoadDB */, 85631BFC20EFC95E0002BE51 /* Translate */, 85631BF620EFC7EA0002BE51 /* Channels */, 85631BF220EFC50F0002BE51 /* DevAutoTests */, @@ -964,6 +1052,7 @@ isa = XCConfigurationList; buildConfigurations = ( 8514D4CB20EE27080002378A /* Dev */, + 2611CEED21807A6E00FFD4DD /* LoadDB */, 85631BFD20EFC95E0002BE51 /* Translate */, 85631BF720EFC7EA0002BE51 /* Channels */, 85631BF320EFC50F0002BE51 /* DevAutoTests */, diff --git a/Nynja-Share/Resources/Info.plist b/Nynja-Share/Resources/Info.plist index 7c5b3060f..1cd90a143 100644 --- a/Nynja-Share/Resources/Info.plist +++ b/Nynja-Share/Resources/Info.plist @@ -21,7 +21,7 @@ CFBundleShortVersionString 1.0 CFBundleVersion - 0.5.4.Dev + 0.5.4.Load Config $(Config) ModelsVersion diff --git a/Nynja.xcodeproj/project.pbxproj b/Nynja.xcodeproj/project.pbxproj index c86e8d39c..837d688fe 100644 --- a/Nynja.xcodeproj/project.pbxproj +++ b/Nynja.xcodeproj/project.pbxproj @@ -2524,6 +2524,7 @@ 2625DBF720EFC5DE00E01C05 /* FourCharCode+StringLiteralConvertible.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "FourCharCode+StringLiteralConvertible.swift"; sourceTree = ""; }; 2625F29E212463E8007C42B5 /* ProgressIdentifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressIdentifier.swift; sourceTree = ""; }; 262D43862033417F002F1E45 /* FriendExtansion+BERT.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FriendExtansion+BERT.swift"; sourceTree = ""; }; + 26305F702180765E00400DB0 /* LoadDBConfig.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = LoadDBConfig.xcconfig; sourceTree = ""; }; 2632139020D797F500C31144 /* TranslationViewProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TranslationViewProtocol.swift; sourceTree = ""; }; 2633EF6D205212F700DB3868 /* MemberDAOProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemberDAOProtocol.swift; sourceTree = ""; }; 2633EF6F2052130400DB3868 /* MemberDAO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemberDAO.swift; sourceTree = ""; }; @@ -3932,6 +3933,7 @@ D1D5302025583482829BBF2E /* GroupStorageViewController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = GroupStorageViewController.swift; sourceTree = ""; }; D270F638DBB2D8FC1BDEB633 /* ProfileViewController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ProfileViewController.swift; sourceTree = ""; }; D4B94201A3DDCB11C62D6A16 /* AddContactViewController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AddContactViewController.swift; sourceTree = ""; }; + D55BAC5832CAFB50A4F64387 /* Pods-Nynja.loaddb.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Nynja.loaddb.xcconfig"; path = "Pods/Target Support Files/Pods-Nynja/Pods-Nynja.loaddb.xcconfig"; sourceTree = ""; }; D7956526150F4211DE78173E /* AddContactViaPhoneInteractor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AddContactViaPhoneInteractor.swift; sourceTree = ""; }; D8AC83D4F29DA35FEFDFBC65 /* QRCodeGeneratorViewController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = QRCodeGeneratorViewController.swift; sourceTree = ""; }; D8F8C8AFEB81C734967FE902 /* EditGroupNameViewController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = EditGroupNameViewController.swift; sourceTree = ""; }; @@ -4080,6 +4082,7 @@ EE1EF22666DC92AE739E2DA5 /* Pods-Nynja.stickers.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Nynja.stickers.xcconfig"; path = "Pods/Target Support Files/Pods-Nynja/Pods-Nynja.stickers.xcconfig"; sourceTree = ""; }; EE2260535ED2762F80FA7A38 /* MapProtocols.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = MapProtocols.swift; sourceTree = ""; }; EE63F9D9E7D7D9B4CAF2FE90 /* TopUpAccountPresenter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = TopUpAccountPresenter.swift; sourceTree = ""; }; + EF57CF149EF8224DA702DEB9 /* Pods-Nynja-Share.loaddb.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Nynja-Share.loaddb.xcconfig"; path = "Pods/Target Support Files/Pods-Nynja-Share/Pods-Nynja-Share.loaddb.xcconfig"; sourceTree = ""; }; EF74AC6D4879E4DA38B3C352 /* AddContactByUsernameWireframe.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AddContactByUsernameWireframe.swift; sourceTree = ""; }; EFF6A30BDE89BF7887B67DA0 /* ProfileInteractor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ProfileInteractor.swift; sourceTree = ""; }; F01F3338177726EB10E0D040 /* LoginViewController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = LoginViewController.swift; sourceTree = ""; }; @@ -4190,6 +4193,7 @@ F1D4A49F20762A1D00F31089 /* Configurable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Configurable.swift; sourceTree = ""; }; F1EED41420C57C30001060C4 /* PhotoPreviewSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoPreviewSource.swift; sourceTree = ""; }; F1F219FC7966064C555AC2A4 /* TopUpAccountViewController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = TopUpAccountViewController.swift; sourceTree = ""; }; + F437803397DA4D6AA4459469 /* Pods-NynjaUnitTests.loaddb.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NynjaUnitTests.loaddb.xcconfig"; path = "Pods/Target Support Files/Pods-NynjaUnitTests/Pods-NynjaUnitTests.loaddb.xcconfig"; sourceTree = ""; }; F46A5D92A279FA0A509DA508 /* Pods-NynjaUnitTests.translate.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NynjaUnitTests.translate.xcconfig"; path = "Pods/Target Support Files/Pods-NynjaUnitTests/Pods-NynjaUnitTests.translate.xcconfig"; sourceTree = ""; }; F56141F2CF85255940EA304F /* EditPhotoWireframe.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = EditPhotoWireframe.swift; sourceTree = ""; }; F79C9355E1AA4B373567F765 /* LanguageSettingsInteractor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = LanguageSettingsInteractor.swift; sourceTree = ""; }; @@ -6174,6 +6178,7 @@ 3A22662B1EFEF31E00D6A867 /* Assets.xcassets */, 6D36F8E41F0ADBD300FA1AC8 /* Localizable.strings */, F1313AFE20888CAB00E04092 /* DevConfig.xcconfig */, + 26305F702180765E00400DB0 /* LoadDBConfig.xcconfig */, F10AFE9A20EF8B9A00C7CE83 /* DevAutoTests.xcconfig */, F1313AFF20888CB800E04092 /* PrereleaseConfig.xcconfig */, F1313B0020888CC400E04092 /* ReleaseConfig.xcconfig */, @@ -7530,6 +7535,9 @@ 61CB12AA514912C6B8E4F670 /* Pods-Nynja.devautotests.xcconfig */, 3A8C12DDFD0D831F21959665 /* Pods-Nynja-Share.devautotests.xcconfig */, CB70AD73977CD00AD11C287C /* Pods-NynjaUnitTests.devautotests.xcconfig */, + D55BAC5832CAFB50A4F64387 /* Pods-Nynja.loaddb.xcconfig */, + EF57CF149EF8224DA702DEB9 /* Pods-Nynja-Share.loaddb.xcconfig */, + F437803397DA4D6AA4459469 /* Pods-NynjaUnitTests.loaddb.xcconfig */, ); name = Pods; sourceTree = ""; @@ -16797,6 +16805,267 @@ /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ + 26305F6B2180763600400DB0 /* LoadDB */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 26305F702180765E00400DB0 /* LoadDBConfig.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 10.3; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_SWIFT3_OBJC_INFERENCE = Off; + SWIFT_WHOLE_MODULE_OPTIMIZATION = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = LoadDB; + }; + 26305F6C2180763600400DB0 /* LoadDB */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D55BAC5832CAFB50A4F64387 /* Pods-Nynja.loaddb.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(inherited)"; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIconSpotify; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Nynja/Resources/Nynja.entitlements; + CODE_SIGN_IDENTITY = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_STYLE = Manual; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 9GKQ5AMF2B; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = "$(SRCROOT)/Nynja/Resources/Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + OTHER_SWIFT_FLAGS = "$(inherited) -D SQLITE_HAS_CODEC -D GRDBCIPHER $(inherited) \"-D\" \"COCOAPODS\" -Xfrontend -debug-time-function-bodies -Xfrontend -warn-long-expression-type-checking=20 -Xfrontend -warn-long-function-bodies=20"; + PRODUCT_BUNDLE_IDENTIFIER = "$(BundleIdentifier)"; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE = "84763be4-0d2a-4728-9e67-b65eb99614ad"; + PROVISIONING_PROFILE_SPECIFIER = DevBundle_Dev; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OBJC_BRIDGING_HEADER = "Nynja-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_SWIFT3_OBJC_INFERENCE = Off; + SWIFT_VERSION = 4.0; + SWIFT_WHOLE_MODULE_OPTIMIZATION = YES; + }; + name = LoadDB; + }; + 26305F6D2180763600400DB0 /* LoadDB */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = EF57CF149EF8224DA702DEB9 /* Pods-Nynja-Share.loaddb.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(inherited)"; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_ENTITLEMENTS = "Nynja-Share/Resources/Nynja-Share.entitlements"; + CODE_SIGN_IDENTITY = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_STYLE = Manual; + DEBUG_INFORMATION_FORMAT = dwarf; + DEVELOPMENT_TEAM = 9GKQ5AMF2B; + ENABLE_BITCODE = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = "Nynja-Share/Resources/Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; + OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" -DSHARE_EXTENSION -Xfrontend -warn-long-expression-type-checking=20 -Xfrontend -warn-long-function-bodies=20"; + PRODUCT_BUNDLE_IDENTIFIER = "$(ExtensionBundleIdentifier)"; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE = "ee586ed5-2bed-44bc-ab45-42f41bfa7316"; + PROVISIONING_PROFILE_SPECIFIER = DevBundle_DevExt; + SKIP_INSTALL = YES; + SWIFT_VERSION = 4.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = LoadDB; + }; + 26305F6E2180763600400DB0 /* LoadDB */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = F437803397DA4D6AA4459469 /* Pods-NynjaUnitTests.loaddb.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + DEVELOPMENT_TEAM = 9GKQ5AMF2B; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + INFOPLIST_FILE = NynjaUnitTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 11.3; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.nynja.mobile.communicator.NynjaUnitTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 4.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = LoadDB; + }; + 26305F6F2180763600400DB0 /* LoadDB */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + BUNDLE_LOADER = "$(TEST_HOST)"; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + DEVELOPMENT_TEAM = 9GKQ5AMF2B; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + INFOPLIST_FILE = NynjaIntegrationTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 11.3; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.nynja.dev.mobile.communicator.NynjaIntegrationTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 4.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Nynja.app/Nynja"; + }; + name = LoadDB; + }; 357809AE1F9765CF00C9680C /* Dev */ = { isa = XCBuildConfiguration; baseConfigurationReference = A169D8E4AB2003F96040DD7A /* Pods-Nynja-Share.dev.xcconfig */; @@ -17625,6 +17894,7 @@ isa = XCConfigurationList; buildConfigurations = ( 357809AE1F9765CF00C9680C /* Dev */, + 26305F6D2180763600400DB0 /* LoadDB */, F10AFE9E20EF8BBE00C7CE83 /* DevAutoTests */, F1313AFD20888BD300E04092 /* Prerelease */, 357809AF1F9765CF00C9680C /* Release */, @@ -17636,6 +17906,7 @@ isa = XCConfigurationList; buildConfigurations = ( 3ABCE8FD1EC9330D00A80B15 /* Dev */, + 26305F6B2180763600400DB0 /* LoadDB */, F10AFE9C20EF8BBE00C7CE83 /* DevAutoTests */, F1313AFB20888BD300E04092 /* Prerelease */, 3ABCE8FE1EC9330D00A80B15 /* Release */, @@ -17647,6 +17918,7 @@ isa = XCConfigurationList; buildConfigurations = ( 3ABCE9001EC9330D00A80B15 /* Dev */, + 26305F6C2180763600400DB0 /* LoadDB */, F10AFE9D20EF8BBE00C7CE83 /* DevAutoTests */, F1313AFC20888BD300E04092 /* Prerelease */, 3ABCE9011EC9330D00A80B15 /* Release */, @@ -17658,6 +17930,7 @@ isa = XCConfigurationList; buildConfigurations = ( F1C37AB0209A1BF4005EA197 /* Dev */, + 26305F6E2180763600400DB0 /* LoadDB */, F10AFE9F20EF8BBE00C7CE83 /* DevAutoTests */, F1C37AB1209A1BF4005EA197 /* Prerelease */, F1C37AB2209A1BF4005EA197 /* Release */, @@ -17669,6 +17942,7 @@ isa = XCConfigurationList; buildConfigurations = ( FE21ACAE2113AA7F006010A0 /* Dev */, + 26305F6F2180763600400DB0 /* LoadDB */, FE21ACB02113AA7F006010A0 /* DevAutoTests */, FE21ACB32113AA7F006010A0 /* Prerelease */, FE21ACB42113AA7F006010A0 /* Release */, diff --git a/Nynja.xcodeproj/xcshareddata/xcschemes/LoadDB.xcscheme b/Nynja.xcodeproj/xcshareddata/xcschemes/LoadDB.xcscheme new file mode 100644 index 000000000..10f32b01c --- /dev/null +++ b/Nynja.xcodeproj/xcshareddata/xcschemes/LoadDB.xcscheme @@ -0,0 +1,102 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Nynja/Resources/Assets.xcassets/AppIconChannels.appiconset/Contents.json b/Nynja/Resources/Assets.xcassets/AppIconChannels.appiconset/Contents.json deleted file mode 100644 index 406236518..000000000 --- a/Nynja/Resources/Assets.xcassets/AppIconChannels.appiconset/Contents.json +++ /dev/null @@ -1,158 +0,0 @@ -{ - "images" : [ - { - "size" : "20x20", - "idiom" : "iphone", - "filename" : "Icon-App-20x20@2x.png", - "scale" : "2x" - }, - { - "size" : "20x20", - "idiom" : "iphone", - "filename" : "Icon-App-20x20@3x.png", - "scale" : "3x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@1x.png", - "scale" : "1x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@2x.png", - "scale" : "2x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@3x.png", - "scale" : "3x" - }, - { - "size" : "40x40", - "idiom" : "iphone", - "filename" : "Icon-App-40x40@2x.png", - "scale" : "2x" - }, - { - "size" : "40x40", - "idiom" : "iphone", - "filename" : "Icon-App-40x40@3x.png", - "scale" : "3x" - }, - { - "size" : "57x57", - "idiom" : "iphone", - "filename" : "Icon-App-57x57@1x.png", - "scale" : "1x" - }, - { - "size" : "57x57", - "idiom" : "iphone", - "filename" : "Icon-App-57x57@2x.png", - "scale" : "2x" - }, - { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "Icon-App-60x60@2x.png", - "scale" : "2x" - }, - { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "Icon-App-60x60@3x.png", - "scale" : "3x" - }, - { - "size" : "20x20", - "idiom" : "ipad", - "filename" : "Icon-App-20x20@1x.png", - "scale" : "1x" - }, - { - "size" : "20x20", - "idiom" : "ipad", - "filename" : "Icon-App-20x20@2x.png", - "scale" : "2x" - }, - { - "size" : "29x29", - "idiom" : "ipad", - "filename" : "Icon-App-29x29@1x.png", - "scale" : "1x" - }, - { - "size" : "29x29", - "idiom" : "ipad", - "filename" : "Icon-App-29x29@2x.png", - "scale" : "2x" - }, - { - "size" : "40x40", - "idiom" : "ipad", - "filename" : "Icon-App-40x40@1x.png", - "scale" : "1x" - }, - { - "size" : "40x40", - "idiom" : "ipad", - "filename" : "Icon-App-40x40@2x.png", - "scale" : "2x" - }, - { - "size" : "50x50", - "idiom" : "ipad", - "filename" : "Icon-Small-50x50@1x.png", - "scale" : "1x" - }, - { - "size" : "50x50", - "idiom" : "ipad", - "filename" : "Icon-Small-50x50@2x.png", - "scale" : "2x" - }, - { - "size" : "72x72", - "idiom" : "ipad", - "filename" : "Icon-App-72x72@1x.png", - "scale" : "1x" - }, - { - "size" : "72x72", - "idiom" : "ipad", - "filename" : "Icon-App-72x72@2x.png", - "scale" : "2x" - }, - { - "size" : "76x76", - "idiom" : "ipad", - "filename" : "Icon-App-76x76@1x.png", - "scale" : "1x" - }, - { - "size" : "76x76", - "idiom" : "ipad", - "filename" : "Icon-App-76x76@2x.png", - "scale" : "2x" - }, - { - "size" : "83.5x83.5", - "idiom" : "ipad", - "filename" : "Icon-App-83.5x83.5@2x.png", - "scale" : "2x" - }, - { - "size" : "1024x1024", - "idiom" : "ios-marketing", - "filename" : "ItunesArtwork@2x.png", - "scale" : "1x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Nynja/Resources/Assets.xcassets/AppIconChannels.appiconset/Icon-App-20x20@1x.png b/Nynja/Resources/Assets.xcassets/AppIconChannels.appiconset/Icon-App-20x20@1x.png deleted file mode 100644 index bb676f8c4bba57356bbeb0848322855fbdefa576..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 748 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 ALI3~& diff --git a/Nynja/Resources/Assets.xcassets/AppIconChannels.appiconset/Icon-App-29x29@1x.png b/Nynja/Resources/Assets.xcassets/AppIconChannels.appiconset/Icon-App-29x29@1x.png deleted file mode 100644 index 8ee922c9d4a256126e532dfde177ba455d054ba4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1081 zcmV-91jhS`P)7EBIL1tiDMkau-M8qJ(A-KR8{0BfHA;2s&hK(B+iW{R57HXnf zG;!fyAR1$gMorwgF(d>Nf==Y2$-MjaeblYvqI;Tw0cJWGS@~O3aw}E!)$g2B=gP&4 z7nS=gbH9QAL;rIHOFslb6dVOApnxpScUCNywnuG`oqY-cwtd#kdhI|3A=Mm8hC$y; z;J_ew6op%yU)dn6xwe6)o^*a*fhIo{y_9BL=*- zA`s9HMXm+4u*xgNI7hyJmtTe}u)Yi;paEsd45;C?=pe5~`Sy>W|BW-v^W1RrH1ia37TXAkE-PG`K;pYYAKpRu(X#XtbPK|5C-ODlIN4p0oHRX|OLoz5wyrg-7OaXx+I6f^m)z+0Sl zCPau`%GrmHF)~o6$YO#-F{#I+2!=v4Qk?}Hf@#ec5adEZ%E$v-I6HQXxJWUwyxx~V z1tC*M%~besYyykHcFe)fHqjre5dJi*dhEVcW1jOM?6B7h{FnW@jt&(|n$47R@ z=3vDzTf{tD+0L8wBRm>zVk{VLZP$#6@;=_F9Al!q4>Us%#=>Eqi0YgkI?QC#Xqn$ysP5mVz(v>O2~xJh1be8vSgi2#S9-HezT^O}&X09+B;S|A3= zD1BJ5lqBMKG>&g~8?yp%RZwU-R^UwYYcfp$A*TQgsV2PFJddI)I_wI%*kAjk4BTD< zN@xRY7eB03JN0;uN8=b@>V(p(M}iU)mHn;Ox0X;Y|#xxJrMtRGSr_ diff --git a/Nynja/Resources/Assets.xcassets/AppIconChannels.appiconset/Icon-App-29x29@2x.png b/Nynja/Resources/Assets.xcassets/AppIconChannels.appiconset/Icon-App-29x29@2x.png deleted file mode 100644 index 9c7c309b7f2406db4a4a48b8a9308213798f8fd3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2487 zcmV;o2}t&dP)FHh;y6 zHLDobK9)DSS?+pR;<{Pny6E#|xbxI~aUeRu{>nH9WQ2YHdYA9$jch2dAklQj^=2e$5bB}H=IUBL_0J3W z@jK7KMa!T+0%M_ApjN6dNEbue&9czTg}#sFp_gU8m!VLjKUC?~G6gJkZO&?6MX5=N z49}Qpa?I2@WXAZ99c7<6!G1f!esh9@GRje6#xwyk2p-Uo$>a-}07hE_5CalXpu_-G211dY(Iy@&Z|8(2 z*pI|MPoPICJlXgyulT(jk(8svWHdn?gpex{1iXM6uolw@MDNx~-#^UtE4J~oE#F|& z)!RBt+hC+9iBEar@_X6o_H*dSL8Otw(hAaFMnn*=5b`AyC8(0o`_9*?*#c#5`{ILFOz~(~*!C<6_AX9!D395=7N#MPhbS!4c(1kp4^?f8h zMHQrthqO&E2B8r$mPWlib;*5DDZSYsja@O~XWNWdwWsf8w@ro&^U%wG2o ze$;%Gef}6PrF(fPdxN+AVZN2Tz@hK~LIH=LjI>kQNiO?v9rlew;<3dDUuf{qMZ4KC za1|rjIMFPAIUQ|%bUK16wW!SPZ~uTj`(DGAd+-GXD^Rk~Q-n%HYY%^Tk=7%N!E2H~ zks=Tf?8zx%;)TS)i4+bSMoKM_Gmr%En#+q_{yQ=lF@(@$sBf63*WE*=ZqBOD zi>N_JG-KHG@zlEeplA>u@&ZL$^@>+$`uy(v};g4anGIc^CDOSdM1ne0|YQu3NeVmyPH7QFR*GnOG{SDXZ-; ztL-qVDYJZyDUwF8#^OTEw!v$8Wbsap`Eeq#^HuvaPcIYxy5epY)rJUJlL8it+I;doX1w)$!nu;P$+g$j*9$Sj`O;_i%b(= zk^%;UBeXt%p(KS#f_ZRqTvNE1Rb~KJaA>i~#^Ocn=(?PdP@lJ7PRE?{=xhimHB-*R zzXp1x!tb-ca?|MdF%$`CUIr8RUe_J$E?x`3FVa8paQrNaAuwV2l;?ZC#*V_r^4eGo zp1{~V^5tweZHrF8AdH73k3j-44OYnjgVN7Y9YKilx*Ygw{Bxd5Uk6}M_7*U8epMWU zwx3mIkOSd39-TZ}fAlbLdU`GZ3xZ|Ul`2-M)8-3a6E2R{@<4ez zW7@!Upp~`Y5(RJsYEt4SU3bwX6_hYZ)sweiCgv)hUenD_yY8VXMclNx3yF?&0Trc0 z#H-m`B$^6SBk_^GEj%B?`0le9jaLPBfrYrN~~tpYJ9w62!JuT>*#ic=9}N z`GZtik$R!A;6xRrNW?SA?`Y~cmZ+_j6va{?4x@ZKd6A;ba-umybY$xaaBX4rJjDx_ zmWIzbU(SXOL|q})l$(kh={Ey-vbIetK?v&TD)(_)Y3mu4dF}ODUO}dQO%-m&P7@ax^h1;tihXC4iA3kRbs1MLv z!f2j6go)MVi?y3pDbR&y5dSgQpaRdEf%9fFd3@`)^L4&En}v+V0GE9H(~?HdtN zb==au6=N!dh9ih#m-KV%$Oan5<;}EC>(TiIgg7+0y1a_5wad_?jxMrni!SBLc!=0` zrV=_?kFx+pv0}KrZyOX04A;2x*cEwkFk=sR;c>=2qq)F}VcaL|=-R;Io~4xhB0fF7 znsJ+UG^I?(Ss@CL5g3YkxUqi=l_TBsXqm%|5uGKXXWZ_b3sm!(C&F-C>llPysZ6z0 zPH~C55a_r?MTA13fJk2A4bcbWy`zZyy52d5cGy=tN>oH>G#VT|dX#3fiT55K@{^1; z7Hch~Qi*|q0m|iaN6B{FqN`ujszn(J48-}{{k0OE{UM-@#_Ep002ovPDHLkV1jVA BxgP)k diff --git a/Nynja/Resources/Assets.xcassets/AppIconChannels.appiconset/Icon-App-29x29@3x.png b/Nynja/Resources/Assets.xcassets/AppIconChannels.appiconset/Icon-App-29x29@3x.png deleted file mode 100644 index fc202f0686b7a1c916c1294a4b071eac31e105f2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3917 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 diff --git a/Nynja/Resources/Assets.xcassets/AppIconChannels.appiconset/Icon-App-57x57@1x.png b/Nynja/Resources/Assets.xcassets/AppIconChannels.appiconset/Icon-App-57x57@1x.png deleted file mode 100644 index a053209e804005fe0da9ec6caf7a1f1ba057b685..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2424 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

^@H!E&wnD9Fj8Wm%q z`E_EImu8jVqWSr`uT6VVMs0nrt>+xllY#W`1{(Q_?)MUuDV};nO3E1(P=0i>C`uCp zlN$IVdLMG;H>UTZPco*EG2SsSnMC4>e!5Qy>K&4ctzs+_MGb`#d#`OTdh5+|_6q#^ zY<73Lz#=ab!mm0YKeTnN*m>|V-AVxiJP{o@2BE2|?!>NM>AMj(fWxZ*IS7ReH_3b> zR(CngAupzLud$Wy0GATcn>y%6$go)jN$`46jgS1rLh9SoJ+seq;s@hb2FBy)WNl;# z?B&s&<+;nEu^{6;As@wO!u*L7an&~pw=ym%?>ghAJE5Px4GzhW=_OzK zKbt|u)J@n@>WCCC9OLQhE9zLdfT&_N3mY;KIl?!OZpds=ib)w`1rmR6WZn^O^Yplf z!@v4pv>-{wbS}uYrrSm_0zu^sTk(gS9a;uH9yTErIm?4QO&XrnNq#UFtdCWuwF z^O#zwo4bVR{UUvqB%Sydu@(9l(pnB%cya&AfDx6MkoMHI=ncb4S|skYmlBRiVn|7o z(H7C~SKdlEnlQ*v>ML)GO#4@3y;y{a@iX-tQ33E$YS9*6u0l?Ocv142)us{dnd^IF zB>fXFjg#nKcD>Hv`Rt2XUFI-z38}u&P|67<$sw^!d|N8^?fhpDux8Z5??b* z^R~nGCOV{GaybA?ykL2yp^cWRY78Uinj3F3IL2QJcmL@(Bym#X>stE$G1DF3ki@Xv zZjP`wdC;-$HFGkss<~2rOdwvwq^BSSh^$ljLQDJig!HTM0-MDODSMXfcol(9OsUmPPBsD?P0?<6%YSS`Z>$+o}{5S2z z&y|G|!wo@?{305&&LK8~o2sM1wRNp-HLGo;PCT0;(DN@`OZ2AZ)G0^JJT%A1{E5wj zhk_jObAgl%+4IEQ!L>pd0lHm|8|03)r*U}WP#P*?B;wlAm|&`N?%Iu3ks9a z7FuN@y>{`h#0&I>gtBxZBhWqUnNFYABFL(r%#_o!XJIPh5nQY;$D6e_>@6FXoh0c*>xX`Eg#+C^-_~FahTKIUOho-=rUvogjEGHGQO|l)uGv>zX}kg|QZQ*uCy2Lj z;=2d|1O0=3LF-!Tu5oGD+D9tc)lf9!*1NGdSxc3{4?rXHUdX+i%^+WxeP)bzk3G#i zhwb09faDH}|5Ls^X+s&Ru@4)WW*Kd4Kv_2+Iy9t*71)ifMD4SGcU4>ksvIg!NZVwK94ZMWkFsFV|;%=>bx$D{_8A`O}py85~r&s8LU72#3IcnsElAG^jD}t)`!~ ze!_B_GHC6k{N7W+Gca!Bc#l+L_U10j^2FR1#@sykKEFQBv4I=jmXP?qm%}?y3_&Z) zLGgK?x5mnoms4x>8V83@V1aRJ^hpN9#y-RgwZSINh_5tlViicQ<}ttSz3OFB1OIrH z!ksf%CWqES2!(z)^k}Nl5);rD(-dnmxo;@8yIL>G=nke!h!!Q0L35sy?hb-25 z<72hv<9{6DES0UYqHd*z7bQFMiErJ7`Nu3<>nOi@mcTJ zDTU>?#=b8%5PMmZP2oe|J2+ELiYGf$*<1pIs~sOT2}hc(xwko7&Qh_vn7ZL0;S~R_ z0u2^oXDr@l-tT5A&hY;Ahc`<`OC3b@VJ_`Aeq9Uw^zuoD*F8gZdNwu}2ZtDFhi?5# zF1X{mtP%jiQec?OPD&aRWh|f}onO(`mJ_D5GI#nFju&!qXpfu~@ME5c3c_%Fpp+sd znA(-Z$-N2x|Fs6?owJoJUptQklb_=^YCS_9Xs#04gPXf_usP#7%?6T=Et+!#G4a d`5$fxlR|~v^4b8QL1(^{{x4v6x#p* diff --git a/Nynja/Resources/Assets.xcassets/AppIconChannels.appiconset/Icon-App-60x60@2x.png b/Nynja/Resources/Assets.xcassets/AppIconChannels.appiconset/Icon-App-60x60@2x.png deleted file mode 100644 index 2e68bfd69244aed1f418ad8da95e8451ed886350..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5565 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 diff --git a/Nynja/Resources/Assets.xcassets/AppIconChannels.appiconset/Icon-App-60x60@3x.png b/Nynja/Resources/Assets.xcassets/AppIconChannels.appiconset/Icon-App-60x60@3x.png deleted file mode 100644 index 098a96998f645e5fedb7e8bd94cde276c18c3e11..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8840 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 Ju9h?n{vUHi7q$QZ diff --git a/Nynja/Resources/Assets.xcassets/AppIconChannels.appiconset/Icon-App-72x72@1x.png b/Nynja/Resources/Assets.xcassets/AppIconChannels.appiconset/Icon-App-72x72@1x.png deleted file mode 100644 index 0f2a4f9bfc185a564c57d5cf04494b70ca0d8197..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3196 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 diff --git a/Nynja/Resources/Assets.xcassets/AppIconChannels.appiconset/Icon-App-76x76@1x.png b/Nynja/Resources/Assets.xcassets/AppIconChannels.appiconset/Icon-App-76x76@1x.png deleted file mode 100644 index 07fe4980249f70a9c266f492741049a9eac74ad8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3397 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 Cx+Q`D diff --git a/Nynja/Resources/Assets.xcassets/AppIconChannels.appiconset/Icon-App-83.5x83.5@2x.png b/Nynja/Resources/Assets.xcassets/AppIconChannels.appiconset/Icon-App-83.5x83.5@2x.png deleted file mode 100644 index 2008709fa59ab71b591943718f074f3f5472f0c9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8159 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 Zvl9fC3;1(~ycF({l;qT8t7OcA{|7jY0S^EG diff --git a/Nynja/Resources/Assets.xcassets/AppIconChannels.appiconset/Icon-Small-50x50@1x.png b/Nynja/Resources/Assets.xcassets/AppIconChannels.appiconset/Icon-Small-50x50@1x.png deleted file mode 100644 index 392fec50044c22bf63c6fe97073a74499b2271ef..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2072 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 diff --git a/Nynja/Resources/Assets.xcassets/AppIconSpotify.appiconset/Contents.json b/Nynja/Resources/Assets.xcassets/AppIconSpotify.appiconset/Contents.json index 191d43356..406236518 100644 --- a/Nynja/Resources/Assets.xcassets/AppIconSpotify.appiconset/Contents.json +++ b/Nynja/Resources/Assets.xcassets/AppIconSpotify.appiconset/Contents.json @@ -149,30 +149,6 @@ "idiom" : "ios-marketing", "filename" : "ItunesArtwork@2x.png", "scale" : "1x" - }, - { - "size" : "40x40", - "idiom" : "iphone", - "filename" : "Icon-App-40x40@1x.png", - "scale" : "1x" - }, - { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "Icon-App-60x60@1x.png", - "scale" : "1x" - }, - { - "size" : "76x76", - "idiom" : "iphone", - "filename" : "Icon-App-76x76@1x.png", - "scale" : "1x" - }, - { - "size" : "76x76", - "idiom" : "ipad", - "filename" : "Icon-App-76x76@3x.png", - "scale" : "3x" } ], "info" : { diff --git a/Nynja/Resources/Assets.xcassets/AppIconSpotify.appiconset/Icon-App-60x60@1x.png b/Nynja/Resources/Assets.xcassets/AppIconSpotify.appiconset/Icon-App-60x60@1x.png deleted file mode 100644 index ba5bf8699c8433a52d871b9fe9e1380a613abf53..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2590 zcmV+(3gPvMP)S%MMFdD9Hdz)n7(}>)TTVG75fH3M95}!Q zDdLnIq9{ZmkVLsgTtXBgmLy;lOcrCnE6jX*cU8R{y62lQ%x1>lL~;C=db;&ZmHPc( zy|t?4(4j-h{}If8TY$yJ1;Xb$E)YK7ae?spjthj(cU&NRz9X7zfF}Tg6n~}vGMyC^ zP_%ef>>O)2D`6wTu-0(i;UwTh@e&9qD%yMFq$19}LMEvc&&7L11%nW&5UC|nLu4cB z(jn-~{m`_86^O)e$~E{|@@F=TtYSs0%2HQhD3qv$gh+Eu212fk;_gM!LO|(VW==)q zghG$lghkm2LKq@7m|_K;1UkksZabWg+8nF4`A6d;ZtHGlNi=}dFzq|1B`knb&&qh1 z*N(i+V@rNQviULwO^M~M$|^s^8b8EZH^Le>#40z$a#v$XC{qgwiCT;bf!rn9qpx+N6)M<5JwcRIJWZ2v(l$9^n|v76IU0>|BpTxbJH|(5oa0H0dPqqRy~o|BuIIqo z8)#`dql#sweppc=u|z?*>$QWtl^urlOJRAGo0SP*4Oj~%p-P#hR9VRoqhXk}I>MST z!e|&|yIDtt7>|BV3`L2sG_}iL^#eXMb&i-%I3k~N)QoXV>YUOhO*#-#@E%+qR|p1< zW$^w9cEp=_ao1yHR`Kd*RPKy~0lWrkw#?!57`>~)X`G!!;&@{9a#5JUtbfwqHxHsGUi zs>1`kbkjjLR4=CGIvANbaLpRhtQZ=;&DQcLPj3Atz8c_@F8=*7{K*#HDxoA03t}To zlweE+D>Wi&SS=GV#BaK<^6T_XMx;s?hbQ0(ba7lHHJ{s>REe~Xn$PZ5)I z13`<7AOhnl93F$C4Xhe`sm=WxzRrECZsAl`ClWgso!L!#0$OOs7{lG~JjLHWeg{(; zAViv**I)v}3B(qrNeKo+&=&WUc5_X%jcK0LDbmfTl*+uc{V_IHR?+kw>>Q>% zfq9pf7DFR+*j8G@lbaqQL=NMbOqwWk95fxc&+AZ8j3h)dfR)@wBN@Oq6Z5U)W-VaF;%Rc<+S}Q`^kz=G#=QJ?D$crjFd~d)Eq<_iFI!h^!>7%> z`q6^4l0Q#d-U$kLZQ-@m`)!67=82Ri#1R)ZG zH8!vI#1bUsiP8OBU0z2cwCC)K&M9HY{rRM8@W9AT+&6j~S@$G9YvNr4=jy1>dSfjH z)egI&&D@h*%L%P>LhIaiG(u+{eb?+*5Me>^1QH^i9z6i$_4jfa5TqfjsqO;@-=}XB6U%_@^}}9U?2NDA#%Kd=O^Ny9zB&{7vWYiQ zd_7jVlaiDPxMFp*P)-#q)ndK0uo6ciWgf5GIZfO?cjyaYgHYEl*GHG|bnSjnKec-9 zbw!iLt{9YOYY%c=w4R1`7aW{smaR{iSf#`MI67Sy^qJ8?Js;~BGge;b2z?eJ4}6Ic z%D6fG{)?e6ge``;w)t{=1?$ZyA&{vw85M{!_6*#>%4~pCeBX?^Z-gyEQ@h+W+96|uS6kg2oxQ4((ubXBlmi_4SOhujj*pkg8)}br(WR=wxT^ zbqT2Vuxx<)-A-IscsDc``byXoX1z1Ml%3_v(QXsl4f#eNV1p1!j%$anU{`Yu4cou` zJ1JA2p(rty{e!zf1KRP>^?9oG3MeH9Iz^8Z?=PFX_c&xsc0$| z<)aoWLQ7Nb9lV}OJWS}Mgc4MjujJm-TWH!0tBdpX$f6Jygmy^Tlw8cMLp#uJ2VE6& zN4AxXoh5xwMN_e8pDl?9P6PXfc0(mWmJhQ3)Kz3A^!BY51LtHt1Sr=stKjWLXkjdA?=ak||uE^~OziCGb% zIHpu8v0}vvhKGmyPP*@eRh2l7snu%4ag6uAN6Hw3wU#7Fh~v0t0sX|H5Uy6M3=R$g zpZC=Xl2aAYPb`WLhY;}I&pPJ33R30Nv;Y7A07*qoM6N<$f;nB& ALI3~& diff --git a/Nynja/Resources/Assets.xcassets/AppIconSpotify.appiconset/Icon-App-76x76@3x.png b/Nynja/Resources/Assets.xcassets/AppIconSpotify.appiconset/Icon-App-76x76@3x.png deleted file mode 100644 index ca372c421e2455ac4f7d9ada4a2dd634a7f81ddc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11523 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 diff --git a/Nynja/Resources/Assets.xcassets/AppIconStickers.appiconset/Contents.json b/Nynja/Resources/Assets.xcassets/AppIconStickers.appiconset/Contents.json deleted file mode 100755 index 406236518..000000000 --- a/Nynja/Resources/Assets.xcassets/AppIconStickers.appiconset/Contents.json +++ /dev/null @@ -1,158 +0,0 @@ -{ - "images" : [ - { - "size" : "20x20", - "idiom" : "iphone", - "filename" : "Icon-App-20x20@2x.png", - "scale" : "2x" - }, - { - "size" : "20x20", - "idiom" : "iphone", - "filename" : "Icon-App-20x20@3x.png", - "scale" : "3x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@1x.png", - "scale" : "1x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@2x.png", - "scale" : "2x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@3x.png", - "scale" : "3x" - }, - { - "size" : "40x40", - "idiom" : "iphone", - "filename" : "Icon-App-40x40@2x.png", - "scale" : "2x" - }, - { - "size" : "40x40", - "idiom" : "iphone", - "filename" : "Icon-App-40x40@3x.png", - "scale" : "3x" - }, - { - "size" : "57x57", - "idiom" : "iphone", - "filename" : "Icon-App-57x57@1x.png", - "scale" : "1x" - }, - { - "size" : "57x57", - "idiom" : "iphone", - "filename" : "Icon-App-57x57@2x.png", - "scale" : "2x" - }, - { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "Icon-App-60x60@2x.png", - "scale" : "2x" - }, - { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "Icon-App-60x60@3x.png", - "scale" : "3x" - }, - { - "size" : "20x20", - "idiom" : "ipad", - "filename" : "Icon-App-20x20@1x.png", - "scale" : "1x" - }, - { - "size" : "20x20", - "idiom" : "ipad", - "filename" : "Icon-App-20x20@2x.png", - "scale" : "2x" - }, - { - "size" : "29x29", - "idiom" : "ipad", - "filename" : "Icon-App-29x29@1x.png", - "scale" : "1x" - }, - { - "size" : "29x29", - "idiom" : "ipad", - "filename" : "Icon-App-29x29@2x.png", - "scale" : "2x" - }, - { - "size" : "40x40", - "idiom" : "ipad", - "filename" : "Icon-App-40x40@1x.png", - "scale" : "1x" - }, - { - "size" : "40x40", - "idiom" : "ipad", - "filename" : "Icon-App-40x40@2x.png", - "scale" : "2x" - }, - { - "size" : "50x50", - "idiom" : "ipad", - "filename" : "Icon-Small-50x50@1x.png", - "scale" : "1x" - }, - { - "size" : "50x50", - "idiom" : "ipad", - "filename" : "Icon-Small-50x50@2x.png", - "scale" : "2x" - }, - { - "size" : "72x72", - "idiom" : "ipad", - "filename" : "Icon-App-72x72@1x.png", - "scale" : "1x" - }, - { - "size" : "72x72", - "idiom" : "ipad", - "filename" : "Icon-App-72x72@2x.png", - "scale" : "2x" - }, - { - "size" : "76x76", - "idiom" : "ipad", - "filename" : "Icon-App-76x76@1x.png", - "scale" : "1x" - }, - { - "size" : "76x76", - "idiom" : "ipad", - "filename" : "Icon-App-76x76@2x.png", - "scale" : "2x" - }, - { - "size" : "83.5x83.5", - "idiom" : "ipad", - "filename" : "Icon-App-83.5x83.5@2x.png", - "scale" : "2x" - }, - { - "size" : "1024x1024", - "idiom" : "ios-marketing", - "filename" : "ItunesArtwork@2x.png", - "scale" : "1x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Nynja/Resources/Assets.xcassets/AppIconStickers.appiconset/Icon-App-20x20@1x.png b/Nynja/Resources/Assets.xcassets/AppIconStickers.appiconset/Icon-App-20x20@1x.png deleted file mode 100644 index bb676f8c4bba57356bbeb0848322855fbdefa576..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 748 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 ALI3~& diff --git a/Nynja/Resources/Assets.xcassets/AppIconStickers.appiconset/Icon-App-29x29@1x.png b/Nynja/Resources/Assets.xcassets/AppIconStickers.appiconset/Icon-App-29x29@1x.png deleted file mode 100644 index 8ee922c9d4a256126e532dfde177ba455d054ba4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1081 zcmV-91jhS`P)7EBIL1tiDMkau-M8qJ(A-KR8{0BfHA;2s&hK(B+iW{R57HXnf zG;!fyAR1$gMorwgF(d>Nf==Y2$-MjaeblYvqI;Tw0cJWGS@~O3aw}E!)$g2B=gP&4 z7nS=gbH9QAL;rIHOFslb6dVOApnxpScUCNywnuG`oqY-cwtd#kdhI|3A=Mm8hC$y; z;J_ew6op%yU)dn6xwe6)o^*a*fhIo{y_9BL=*- zA`s9HMXm+4u*xgNI7hyJmtTe}u)Yi;paEsd45;C?=pe5~`Sy>W|BW-v^W1RrH1ia37TXAkE-PG`K;pYYAKpRu(X#XtbPK|5C-ODlIN4p0oHRX|OLoz5wyrg-7OaXx+I6f^m)z+0Sl zCPau`%GrmHF)~o6$YO#-F{#I+2!=v4Qk?}Hf@#ec5adEZ%E$v-I6HQXxJWUwyxx~V z1tC*M%~besYyykHcFe)fHqjre5dJi*dhEVcW1jOM?6B7h{FnW@jt&(|n$47R@ z=3vDzTf{tD+0L8wBRm>zVk{VLZP$#6@;=_F9Al!q4>Us%#=>Eqi0YgkI?QC#Xqn$ysP5mVz(v>O2~xJh1be8vSgi2#S9-HezT^O}&X09+B;S|A3= zD1BJ5lqBMKG>&g~8?yp%RZwU-R^UwYYcfp$A*TQgsV2PFJddI)I_wI%*kAjk4BTD< zN@xRY7eB03JN0;uN8=b@>V(p(M}iU)mHn;Ox0X;Y|#xxJrMtRGSr_ diff --git a/Nynja/Resources/Assets.xcassets/AppIconStickers.appiconset/Icon-App-29x29@2x.png b/Nynja/Resources/Assets.xcassets/AppIconStickers.appiconset/Icon-App-29x29@2x.png deleted file mode 100644 index 9c7c309b7f2406db4a4a48b8a9308213798f8fd3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2487 zcmV;o2}t&dP)FHh;y6 zHLDobK9)DSS?+pR;<{Pny6E#|xbxI~aUeRu{>nH9WQ2YHdYA9$jch2dAklQj^=2e$5bB}H=IUBL_0J3W z@jK7KMa!T+0%M_ApjN6dNEbue&9czTg}#sFp_gU8m!VLjKUC?~G6gJkZO&?6MX5=N z49}Qpa?I2@WXAZ99c7<6!G1f!esh9@GRje6#xwyk2p-Uo$>a-}07hE_5CalXpu_-G211dY(Iy@&Z|8(2 z*pI|MPoPICJlXgyulT(jk(8svWHdn?gpex{1iXM6uolw@MDNx~-#^UtE4J~oE#F|& z)!RBt+hC+9iBEar@_X6o_H*dSL8Otw(hAaFMnn*=5b`AyC8(0o`_9*?*#c#5`{ILFOz~(~*!C<6_AX9!D395=7N#MPhbS!4c(1kp4^?f8h zMHQrthqO&E2B8r$mPWlib;*5DDZSYsja@O~XWNWdwWsf8w@ro&^U%wG2o ze$;%Gef}6PrF(fPdxN+AVZN2Tz@hK~LIH=LjI>kQNiO?v9rlew;<3dDUuf{qMZ4KC za1|rjIMFPAIUQ|%bUK16wW!SPZ~uTj`(DGAd+-GXD^Rk~Q-n%HYY%^Tk=7%N!E2H~ zks=Tf?8zx%;)TS)i4+bSMoKM_Gmr%En#+q_{yQ=lF@(@$sBf63*WE*=ZqBOD zi>N_JG-KHG@zlEeplA>u@&ZL$^@>+$`uy(v};g4anGIc^CDOSdM1ne0|YQu3NeVmyPH7QFR*GnOG{SDXZ-; ztL-qVDYJZyDUwF8#^OTEw!v$8Wbsap`Eeq#^HuvaPcIYxy5epY)rJUJlL8it+I;doX1w)$!nu;P$+g$j*9$Sj`O;_i%b(= zk^%;UBeXt%p(KS#f_ZRqTvNE1Rb~KJaA>i~#^Ocn=(?PdP@lJ7PRE?{=xhimHB-*R zzXp1x!tb-ca?|MdF%$`CUIr8RUe_J$E?x`3FVa8paQrNaAuwV2l;?ZC#*V_r^4eGo zp1{~V^5tweZHrF8AdH73k3j-44OYnjgVN7Y9YKilx*Ygw{Bxd5Uk6}M_7*U8epMWU zwx3mIkOSd39-TZ}fAlbLdU`GZ3xZ|Ul`2-M)8-3a6E2R{@<4ez zW7@!Upp~`Y5(RJsYEt4SU3bwX6_hYZ)sweiCgv)hUenD_yY8VXMclNx3yF?&0Trc0 z#H-m`B$^6SBk_^GEj%B?`0le9jaLPBfrYrN~~tpYJ9w62!JuT>*#ic=9}N z`GZtik$R!A;6xRrNW?SA?`Y~cmZ+_j6va{?4x@ZKd6A;ba-umybY$xaaBX4rJjDx_ zmWIzbU(SXOL|q})l$(kh={Ey-vbIetK?v&TD)(_)Y3mu4dF}ODUO}dQO%-m&P7@ax^h1;tihXC4iA3kRbs1MLv z!f2j6go)MVi?y3pDbR&y5dSgQpaRdEf%9fFd3@`)^L4&En}v+V0GE9H(~?HdtN zb==au6=N!dh9ih#m-KV%$Oan5<;}EC>(TiIgg7+0y1a_5wad_?jxMrni!SBLc!=0` zrV=_?kFx+pv0}KrZyOX04A;2x*cEwkFk=sR;c>=2qq)F}VcaL|=-R;Io~4xhB0fF7 znsJ+UG^I?(Ss@CL5g3YkxUqi=l_TBsXqm%|5uGKXXWZ_b3sm!(C&F-C>llPysZ6z0 zPH~C55a_r?MTA13fJk2A4bcbWy`zZyy52d5cGy=tN>oH>G#VT|dX#3fiT55K@{^1; z7Hch~Qi*|q0m|iaN6B{FqN`ujszn(J48-}{{k0OE{UM-@#_Ep002ovPDHLkV1jVA BxgP)k diff --git a/Nynja/Resources/Assets.xcassets/AppIconStickers.appiconset/Icon-App-29x29@3x.png b/Nynja/Resources/Assets.xcassets/AppIconStickers.appiconset/Icon-App-29x29@3x.png deleted file mode 100644 index fc202f0686b7a1c916c1294a4b071eac31e105f2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3917 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 diff --git a/Nynja/Resources/Assets.xcassets/AppIconStickers.appiconset/Icon-App-57x57@1x.png b/Nynja/Resources/Assets.xcassets/AppIconStickers.appiconset/Icon-App-57x57@1x.png deleted file mode 100644 index a053209e804005fe0da9ec6caf7a1f1ba057b685..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2424 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

^@H!E&wnD9Fj8Wm%q z`E_EImu8jVqWSr`uT6VVMs0nrt>+xllY#W`1{(Q_?)MUuDV};nO3E1(P=0i>C`uCp zlN$IVdLMG;H>UTZPco*EG2SsSnMC4>e!5Qy>K&4ctzs+_MGb`#d#`OTdh5+|_6q#^ zY<73Lz#=ab!mm0YKeTnN*m>|V-AVxiJP{o@2BE2|?!>NM>AMj(fWxZ*IS7ReH_3b> zR(CngAupzLud$Wy0GATcn>y%6$go)jN$`46jgS1rLh9SoJ+seq;s@hb2FBy)WNl;# z?B&s&<+;nEu^{6;As@wO!u*L7an&~pw=ym%?>ghAJE5Px4GzhW=_OzK zKbt|u)J@n@>WCCC9OLQhE9zLdfT&_N3mY;KIl?!OZpds=ib)w`1rmR6WZn^O^Yplf z!@v4pv>-{wbS}uYrrSm_0zu^sTk(gS9a;uH9yTErIm?4QO&XrnNq#UFtdCWuwF z^O#zwo4bVR{UUvqB%Sydu@(9l(pnB%cya&AfDx6MkoMHI=ncb4S|skYmlBRiVn|7o z(H7C~SKdlEnlQ*v>ML)GO#4@3y;y{a@iX-tQ33E$YS9*6u0l?Ocv142)us{dnd^IF zB>fXFjg#nKcD>Hv`Rt2XUFI-z38}u&P|67<$sw^!d|N8^?fhpDux8Z5??b* z^R~nGCOV{GaybA?ykL2yp^cWRY78Uinj3F3IL2QJcmL@(Bym#X>stE$G1DF3ki@Xv zZjP`wdC;-$HFGkss<~2rOdwvwq^BSSh^$ljLQDJig!HTM0-MDODSMXfcol(9OsUmPPBsD?P0?<6%YSS`Z>$+o}{5S2z z&y|G|!wo@?{305&&LK8~o2sM1wRNp-HLGo;PCT0;(DN@`OZ2AZ)G0^JJT%A1{E5wj zhk_jObAgl%+4IEQ!L>pd0lHm|8|03)r*U}WP#P*?B;wlAm|&`N?%Iu3ks9a z7FuN@y>{`h#0&I>gtBxZBhWqUnNFYABFL(r%#_o!XJIPh5nQY;$D6e_>@6FXoh0c*>xX`Eg#+C^-_~FahTKIUOho-=rUvogjEGHGQO|l)uGv>zX}kg|QZQ*uCy2Lj z;=2d|1O0=3LF-!Tu5oGD+D9tc)lf9!*1NGdSxc3{4?rXHUdX+i%^+WxeP)bzk3G#i zhwb09faDH}|5Ls^X+s&Ru@4)WW*Kd4Kv_2+Iy9t*71)ifMD4SGcU4>ksvIg!NZVwK94ZMWkFsFV|;%=>bx$D{_8A`O}py85~r&s8LU72#3IcnsElAG^jD}t)`!~ ze!_B_GHC6k{N7W+Gca!Bc#l+L_U10j^2FR1#@sykKEFQBv4I=jmXP?qm%}?y3_&Z) zLGgK?x5mnoms4x>8V83@V1aRJ^hpN9#y-RgwZSINh_5tlViicQ<}ttSz3OFB1OIrH z!ksf%CWqES2!(z)^k}Nl5);rD(-dnmxo;@8yIL>G=nke!h!!Q0L35sy?hb-25 z<72hv<9{6DES0UYqHd*z7bQFMiErJ7`Nu3<>nOi@mcTJ zDTU>?#=b8%5PMmZP2oe|J2+ELiYGf$*<1pIs~sOT2}hc(xwko7&Qh_vn7ZL0;S~R_ z0u2^oXDr@l-tT5A&hY;Ahc`<`OC3b@VJ_`Aeq9Uw^zuoD*F8gZdNwu}2ZtDFhi?5# zF1X{mtP%jiQec?OPD&aRWh|f}onO(`mJ_D5GI#nFju&!qXpfu~@ME5c3c_%Fpp+sd znA(-Z$-N2x|Fs6?owJoJUptQklb_=^YCS_9Xs#04gPXf_usP#7%?6T=Et+!#G4a d`5$fxlR|~v^4b8QL1(^{{x4v6x#p* diff --git a/Nynja/Resources/Assets.xcassets/AppIconStickers.appiconset/Icon-App-60x60@2x.png b/Nynja/Resources/Assets.xcassets/AppIconStickers.appiconset/Icon-App-60x60@2x.png deleted file mode 100644 index 2e68bfd69244aed1f418ad8da95e8451ed886350..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5565 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 diff --git a/Nynja/Resources/Assets.xcassets/AppIconStickers.appiconset/Icon-App-60x60@3x.png b/Nynja/Resources/Assets.xcassets/AppIconStickers.appiconset/Icon-App-60x60@3x.png deleted file mode 100644 index 098a96998f645e5fedb7e8bd94cde276c18c3e11..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8840 zcmd6NRZv|`ur==PZo%E%-3boCA!u+2F2UUnt~qdUC%C)2yL)g6F86%(|IhdNKJ2}x zdRBGM%##4AW#+Lr8U6chW{P}IPhC!5{?rBf=W$6T0+Y!=OWiP zg<9X}SghOk%dP%Z_20jj<#|><#mBQ;n|GI|WN;WV5=XN=BkU8%LE6H3sYcETZEb%d z=@C%QeHQAEBVRS}U5p@m|CLt2V`3!i+{BU*^h$)E61G_vx92^_wme>vVHv!Phv7Gr)LaKY+~XS0~T&IW|cwE0e5Oa0zM!ph_@c=;HOT@`uei_$&Es5Jh9ojW5?#Xm#KzMnM$x z@I+xASr}m+NQW@a(&l9f0|I5P)hjz%0dQ94#?>{&QKzG17fC=;0bFsKqlm7Lij!*xV90qCJVB!joPB%;@>hBc_OcnS+1HaTA)VMi2xEwl z(LFTDvXrcKDJoI=S}In-MAN58)8ThHNJj#x+54xac~=K`0=hQr-RyF2&1!eVMa`J8 zHblIaqsC`ZK-|9IGPlnW#IEN)F257cfS6SaHuHyY%sl1}2x)4W(jHqR+UX1ic1+Y- zHll++Hj$?`8D%z+A~z8GO%t^Y>^rWZ^y0O9><;GB1?~Nm3IsQ^ogry@kjt2sxX}=s zSPod0lz37qR7YBI_`!@NA+UL<_+Ek;`=`$F)^5l-h>SVNb_-h2)Wtu;ClCIi-(3$n z-EzH>?QXp{-x#`~v2>2gjbN(@@3Ou%#hK+=u$vqt4U6?zf1a`-K3?^12*#6;&?aRK zLZKm7jn-;W5GnVRGO&}SX2il}#D}ktqt=TNnL7!OI3~+_rqf+<6NO=GAm6buwVBY5 z{k2r*^`0zLZ=zDM(uz9{Mk)zWuDugZ&OR2}g_?%fwh9r{kx9I4JV3?)D)eP2&Rdn~ z{mznlpS+Xa7M*-l=??w$ITOls(ovsl$GWNlx*Q;8#9o926TeRC4GXnN z3jY@Mx*}V zuD5Id6;zxh?wc|xovqBsgbA+~Q;%j%GJtnH=g&jluRC4PB^uGFQVjoidukyzZ2GnE zbT7|Jz=zT1*NVPuFxGFQC&q+;Lw^LwCH|ax_LPWmI3TcBP(CbIx%?mfw?uKuAVp(z z9GGC#9$N}$AZh7CE>JxbB~uvNC9`uDLUR^lelmZd8@+Blg^;7nlTIEc(dUuAp3~gm zRBo=(5qH$ge%>VIJ4!=x8qW;E9$@UJ6bh~_XT=X;axE`jJ$cw{bR6c`#U}})vl9jK4=BSLe=r`3JNJ6(0eW=sZLf{{n^(!m+PYD z1k-6}jSvd}C#>I`zwJWR22q=%E0940LZodI6AQ+<{wkjHLMvxe7EiLz->_?1Wr$cM zx)_*`u*i5uSWGQ8Q(IXd11TAxFp|Hc;1%}jYacck@2r~UY5K&Mgy`qZ-sPyrjKxX`ROH5z_8svK@n$;0{-BdJYEeK(`0{+iVyI<02>k9N!D z@P}8vfEq=c_2J^$Koth}FSmGCy`9~fcREqeze*>46yaJ6OTF@B#pCQbN>a)D!DBAD zckyXb3cs5wrHK5KK|N-;{Hd}J%xyn3+Cl07ivir#L%5Z*@QgnphW(;sC<&MrzKnJe zQu&dF6T_hHU4y?QB?}}amU+jPNrdGD%P{yQNq~lze1QB9*j$17O1s!2^A#@-&Pvgj`#bvx2do_LmI5z<;=*v)gF9N_P%_08u)ad)<#R z3dsDAI&jR*50pxrKtM8bCw@tAY#0WVd+6UGNTm`pc%|+(e#Mce+^k&~7ffx_M?KN; zhgJwMHn zspwyrJ^rJ*5GSk0Yh!yYxkD%f%QCB6?K|A4?vLZJfpCKK?~F}R_T*h94-9xlArPGm z04_b*q5+(3#jlD?JQhglvcK5%&CjN#4Z_a>dlj_ga zVrkomFLFI@9(kVHf#jHtB0kQAdHj>ce*So>EVL5T_DXT`-|loM1~Gc`bFTBx0y0Df z>sTHqFYFEAKgDsliu;)0<8Li=-ynvV3pmea%M#98Rqqx`DxAxq7Wr2p4GcZ-VgOj= zRi6ST@!f+vGT)$MK+FPLMz7Fa7E{0xhpI&zOE%8z=0uoypN{WrZT_VgK&UudohQEZ zPl%d~4i^JIH=%W(LB*qzK)+kZ%N)UYTV^0~YoX~n_FGHQu_V>G56ZO8POIIVYh&1~ z;3|sDJdc;>1-iUowNos?cxlS>_GHa<*--k7#+sH%7p?m@Ofk){z`Tg(hF+z{aZr`| zAY{HQzLau9kCG?p{w?lo;%FNE?g&694M662=m;OYFkOcQ|3O=xG5BgfWLEjcZwP=T zN&apbc9jYmCV9}aJ@nK97BQ89g%mRyjkl=b>z6$4X$}h5Xe>@f%b~)FS{w#>`S@QX zS;3(wN=Y0HT#S~A01ySGn87=>^@To;nVReRanhpLU)r+!+%guP_@24UT7UCXbtFhE z1aoHcmX|8!)Sh<^K;s*Gpp)ti&mFS~pEbXwb%qPBo$U{O z2}$rm8;Ew1I}(sT71boBrXRF!LsT_ogT{ zN7MYhfAM;CoZ9Ym6lU||#kMkxQPLuaS5hxBX(-<@GXdqdlZWH0n)#5xP(g zQ?8wtd|9Z0aOlhs{TONgd8KP1>@Xcv(a2(p?lPWzDy>-d*+qcgwG^j34kV(JbX;rT z7-xxEZ7n)Jo`ha18265?aJ7Dh3wRw#(EK?`x;W?|Cv77?uG2%%AHn8}?Wvo0WN!(V zie4MXf6BjbJMBPjpRl%i%Y14Q2O!l&1Dq>7e5lYG9L_%oP>3tFI;`HZ*>6AVOJ9~{ z)NAvpP#uIT7%%*Bf3TA|yq~e3q%%kt=GWpF_?v;qHx+@_8Ky@v#rm^v9nro$!cmS= z%V;2g@DLh)JLl=ta1FgpGcs4DdowgEmll7&@Q7?R&*z7XMy5l}z5d3ZGJpN)pA^)q zF<}TtSqi-Jo(pV+4zhaHQ%%V+FhmSC>}lvEeq68P*bB;Ta!*Gd9w|VxQMK+iFK98J z9UcN*8QiscGIzD(p9Xm%NKsCe(xVKXEJm7qJw9&3B3u%yP8HdQ4+UJvbPi6JINoy4 zrLJh$x=*trkGJpbP70dtLaL%dYBPyPCHpb~=F6KTzdLZhXuGp2t0g(u87Yjc-lgrV z<4N;k{&b6R>oicv@i~-TbI)6pNQ>h0-(uLB@`i6q?3Yi)hUUAyLd*G1TYa9PXi6uf zF_Y+V#vP8XCN@^xt6)#?<=fAzkCi#Eh+pliDY3;WRC?(36bA8Mo{doJg>|=8U$yBf zWDXbPlz?@x{VhcMb>o4F2b_bs#3&w>KwQbxNa)xbCHMf0bbPHY3pay4;}CjXL5M8g zx-aQ%)m2J?5e8%ZCOxvu25FP-BrxTLE=#vv?+LEJj_h*V0uSKg$V#gL91OjceONm( z-}RSyIt~d^QaFb$0d4o@$cwfKDWcAGQFtZumDV8ZpMrrFLYtw?x#Mk5Hsbh_u?tU0 zMxHLyPYyy$e9iUs&G9s1@5vxfm)A=Jb~Dnd`)Ay4`v4wt|NKh%V_#Mt!TBvFmgyti z+u2`BM9Dv#eNPd@IQ#*Gni5xEw>PHZ@wur3)(D3q9!1AiMyPqY6-1jW#M5t!$4#6J zHVEt3sPA)C+xgc)RSlhKoh9?So>5!DrpWOeB7GL~3Bg!7XWsME#m)){f;aOjtk$uQ zY9i50OetyF1@8FT)iz3nueVBZj+xPo(&tmhs$ae1=F?0z?%3g#d`mAA1Bia?0x-Y} zc*&`9b^4Iq;{C~1JR|?IX_e=-=;t1x(Jp&}TRB=Zz^OD_6m%ebwb8}wE%74`@$9W4 zN%tnpsM*C4zT~9MWfXzvk8HY3fZYkJ4%F*ufILt{#y>O@zsx z`+ejx@*%QshaCjFIV1#C$pIW7u_L>giaq~w`i8M8MEIRm3lL^FeI z&7HM&hc0*i4HQ*o{eU0M$+Bpj+=7PaC)X3XqHX`0CY0Fxm2-9^`)vYJhH1$qYA&ZCInBSA&uoO&%G>qWKIvfh`sJ~M4Ty0)xws!q_MC&3(Ifr^)4;Gn1JG25Vu zFXd4^E_N}7134T)sT)8P!K8;g#&;)44R&fNgGxdeAEHU;*BwP*Y^GL`==zDDZ$EsW z{=Hn~gQ#SMzP*FY6o41gx>=ryXw12y>8UccNJzbHjG8`xh+8@@7w5=LJB(k)=TIh* z-MvnHAJNtmgef?DpKHAhtuGzN7eQ+*CdFSfYKOArj3>{+6I`YoKZ~@8Er;(TjM<_- zNCZe-KkvHx(m$V>=(YQ3L7r{_pSEeN0%Nka-yRIx0v$>~X#YH5+4 zwK&ppz$_P@c>fy@nLN)cBWW|4f8Ksv&GP`XZfrMcQmbxtQK~gTcQ^dpcniWAy$Ln| z{>_pekRH%7O2*%Sl5u_e_N=aYlt9;s$mhn_a66Hi;25}-mOx7gTvnKHb_qpS6P5<@ z;Q5Y56SA5~NV)pd%p52RO}!SA52Dhi*fyh9M7H_|tU~rY?-}=d=w&INigYyJ3o=$q zNGs2jLm{m`O+jWwW<>wE-kLkhnDedW<{Pf@FbX^CViuunow>bhC)@e^Cd$~<^}H1j z)7sFS$1Qfj*umS9)J)a_52`qM&@#3Tb8e(R7u;%)Rj0EKie4SZd9ZW*KCEJiz2LfFtvm6kH?A~Ct48G6E0$IY?t==gmv%2uvprx z)J^J{{nPB8`mV`iRPHFY>j^jNcT*{WDCaYMzXdi1-)zCRl9#1}KkcG4kJPrG$sX-> zLjX*e2(zz>=Q<$+jt-C8*Z?77@EAoAF=F*=it68XyVJ)gpfnA(ovo-pStJ}j7Y^FZ z%7D~?*32E`Ml+xb%ypqgDiyR=!~mDiFDo7W(x^EO<|@!Xvi0T%)?_&|;g%KvqqNCx zO)P_rT}B&ukcLbbtMzev&U(82Z7)dkx0ZEPX12F^Mov4 zjlNS6P<@k1@t`)cFkz2jZec8Zbk-+NgJf|1d0lW=<)d%Msx)0>O?udqA?S%yz26nI zp3&IZj@;`ps~ZeAQ;@;M6JUP88sL6_TgBWzmM}V46{4~85%$?Q{zusFN>PNXvI` zG)mwpkjFWF>(;2oK`p~w&f{hAVC^zMz#UD=@SpG|#T$=?m|XOck!N-7qw(WkMMi$Y z7w*c=KQsuK;b$SXE8rSgkmz*y?6(|cM}-%_h;yd#)OTsoOYWbAU&n{{JsiApvtA;} zGW@(W@kHYN7k`$MMnK#zpSZ1lQJ$k-CDygfT#HFhliP2VifCJSeECu8s8#*RZk zu&u~^F!}&KSD%Epxu=1Jk*htu$%RN?9?B4_mwZcGOKU)vKG(sjQ5;}28!Fn3P}~5V zy|jJQJN=D{TezogAUq9??0ns&l_Bg|xp*XCE}U2sC)fp(4H1ol6*|W)wM((n2&D^0 z1BP9cdV7$SQcLvxK0*W4StA4mGF`$Q`4woup8w zY9C8G!^-_TY>BaV9)Yt`E;o}9RQLfg99N*uzr0i0m4n@j%iPx1Ozhw#(QM{&K(pfz zmTsgK>R2M8-J(xAO&Z^fYbxf@pW=d=uZ>!>owNPR7GS?3ObvEWcLXJ%b+k6Bh+cJY8qZWDqPFE3 zzCP~VmjkLO2|4=#>4_%v?+{-!3RTbj?_jTkjbY(&5Cklaz103`+FQENtm3I`E#m7g4HnVKgtCr7QA0NQ(~z?CN~a;G;`kOo!PcPFk|9xsWS zU%i}EG9M=Q+5r_GtvcPYRYQ=9w{LzI$-=gIAn6kWc<{>GkmISfn|A(oRJ=s#)c^As zp(jjZGM$>bnQv6XFfOo|asW5|jo9_Vi$)^s0w?$;n`Z&rOQkmd)%&eS+c&c>$Arm$ zn#q`UU^gXbkGdnfrvfVi@%%C0)A+EnjfJm!*Q}p>-J*jWL`OUrjb|LF)7$Lqsz=+) zyAxEZB~~&2jD{i4uMi$}DhRC;#!r^JkEutY%WeXfF)(DF$2~*xPw=`k+u97uoiXu8}s-h#?FyVjVbjw)#b0=<{75HUT>SBR0bToof~isS@$fVm)-m=0Of0x@M~!O|2Zx= zvTfjRNK^8K#(^#x$)KkASG4zbpO)^eJ`hxi==+NvbODw1$fBCI~fGIdyB}rb--7*gZIh4^a(~i5?D!agHZy(+z6|v zS#bf15$*wQW0`+!%O|bz?^)Q~Q*oEyx(noTtLAhJon8c8#XHp906~XyQl)I_b!$hR(qGAzTzP4tD!z$AO zWE1CX3rt>yZ%#ypuQN|rEwt| z2-x0aqP`-qh_zD@$BjF%op9XIu**ABJBrn)Xr>{hN-0}~6A{1un3mj~^R#;}_EF+# z&zJmNiexL|F|SLZnTCG00J2cI2Bd7ohU4!ix}wo?*NFMDU_17JQ9=0kL<$;SsF!1# zJsD)fFmSdqwmr@_1!)bGmTlsrs4m+ZX61snWo&+Eh#ko!`i8$D>1SQ0#PXKh$&TSIXj1m-60V9jrp0-&k7t ztpnEyTn@p%7j*f>zneejBEBox*pW2o@m+L@(eBukv338vpGyu=X5X*eaT9!$*tri^ zUbY`Z)d-IkG`LvT1{wB~UbP+`r#lFvnWNjO1-eXaX>$*?t1OQhtp+X$|> z#_Kd-IpU@`0mJd3CE z1PlWLHi|=GjGLZwOj-LNy|ATLJroIlLYgnd2y`@(gIA4X!}c z^uaF+G!JEMDm}%@8L*k`rj{_k(?p7wQ*wZ~n-lh)-sJL`ehd!)C zY$qz>l+0^z&A4c;<2#EjR;WemAZ~HN?)FgD1LuWSw;NYSoAaXbx0#mgF#LPkUFUN(ludIqRlep=h$WY4g$U2+QcKm89bw2f}tKi9j z^dnlT(^@m#FkkP%kvIh;Hn=g!bB3ZY(wnT-ADIt)@27}htFe(7LjA;^n}~uDaVmSD zMpKT^rV-+!_^)?mZFyi)kJ5)mm$`MgM}njx0Q3&9aecD~?Rc!mZ9|T~%g->4XGDNJ zUtb24dI$&FxV1~XY#zeD+Y5uIz{*9$qs(U)ycTET&RH+|rBy1U`3<*+a~GjSwU?zj zfWG2see0kI?~4JK$ZU%E2&)7Q4>Q-FF(u#x`iOv;Z$;D*aE_*xKjP&O*^XCjF7d2o z#D>`93;mL6pHg%N=DlD*2Gf-BR0`=)EHlOv{~Q-sX(R==;e?QoQnb zBDEr4i;7A7pgQRERrnE1CbBOGDtk*ECCy;MLK=@4MAYPZy&#e2^T{7S5mLGKK2J?o zac^I^HB8xS%ZK{p;5a2MU8+-| z#fZz8iuBJX*Q#CtM@CwG0n42B#4)MaXhe~rkDVGt%C(gwOftx|Rk_u$SdaN7DP<`N z9JHLLZl3lN+>RzPb?)sGnpRUf1M+`bCR|{O`Wi6|9vo21#gp7TSb$S?4K(I%PY^T; z8X9ZNlncpk{LJMxQA}|6MI)L5Ax-AAu8xE4=;t16r|Qtq5CJ*)@V6L_aQ^tH{S5l( z$bA9}l%b63CeXa2P1Wo-Y#2y00xTFv35*7qJpTym3$XfR{&<5ss=3>)&B`Ig*+v5o z(58=j!2h3fv~6)r?T*o}!M_>03EraaK=6d?Dw_G@lT Ju9h?n{vUHi7q$QZ diff --git a/Nynja/Resources/Assets.xcassets/AppIconStickers.appiconset/Icon-App-72x72@1x.png b/Nynja/Resources/Assets.xcassets/AppIconStickers.appiconset/Icon-App-72x72@1x.png deleted file mode 100644 index 0f2a4f9bfc185a564c57d5cf04494b70ca0d8197..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3196 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 diff --git a/Nynja/Resources/Assets.xcassets/AppIconStickers.appiconset/Icon-App-76x76@1x.png b/Nynja/Resources/Assets.xcassets/AppIconStickers.appiconset/Icon-App-76x76@1x.png deleted file mode 100644 index 07fe4980249f70a9c266f492741049a9eac74ad8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3397 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 Cx+Q`D diff --git a/Nynja/Resources/Assets.xcassets/AppIconStickers.appiconset/Icon-App-83.5x83.5@2x.png b/Nynja/Resources/Assets.xcassets/AppIconStickers.appiconset/Icon-App-83.5x83.5@2x.png deleted file mode 100644 index 2008709fa59ab71b591943718f074f3f5472f0c9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8159 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 Zvl9fC3;1(~ycF({l;qT8t7OcA{|7jY0S^EG diff --git a/Nynja/Resources/Assets.xcassets/AppIconStickers.appiconset/Icon-Small-50x50@1x.png b/Nynja/Resources/Assets.xcassets/AppIconStickers.appiconset/Icon-Small-50x50@1x.png deleted file mode 100644 index 392fec50044c22bf63c6fe97073a74499b2271ef..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2072 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 diff --git a/Nynja/Resources/Info.plist b/Nynja/Resources/Info.plist index dda23f384..d4fda774a 100644 --- a/Nynja/Resources/Info.plist +++ b/Nynja/Resources/Info.plist @@ -23,7 +23,7 @@ CFBundleShortVersionString 1.0 CFBundleVersion - 0.5.4.Dev + 0.5.4.Load ConfServerAddress $(ConfServerAddress) ConfServerPort diff --git a/Nynja/Resources/LoadDBConfig.xcconfig b/Nynja/Resources/LoadDBConfig.xcconfig new file mode 100644 index 000000000..c1e7b411b --- /dev/null +++ b/Nynja/Resources/LoadDBConfig.xcconfig @@ -0,0 +1,20 @@ +// +// LoadDBConfig.xcconfig +// Nynja +// +// Created by Anton Makarov on 10/24/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +BundleIdentifier = com.nynja.dev.mobile.communicator +ExtensionBundleIdentifier = com.nynja.dev.mobile.communicator.NynjaShare +ServerURL = loaddb.ci.nynja.net +AppName = NYNJALoad +ServerPort = 1883 +Config = dev +AppGroup = group.com.nynja.mobile.communicator.dev +ModelsVersion = 9 +isServerConnectionSecure = false +ConfServerAddress = 35.198.118.190 +ConfServerPort = 80 +ConfServerSecure = false diff --git a/Nynja/Resources/ThirdPartyServices.swift b/Nynja/Resources/ThirdPartyServices.swift index 1a85a5e7b..9b8721b27 100644 --- a/Nynja/Resources/ThirdPartyServices.swift +++ b/Nynja/Resources/ThirdPartyServices.swift @@ -22,6 +22,7 @@ enum AppConfig: String { case devAutoTests case prerelease case release + case loadDB } protocol ThirdPartyService { @@ -45,7 +46,7 @@ struct AmazonService: ThirdPartyService { init(config: AppConfig) { switch config { - case .dev, .devAutoTests: + case .dev, .devAutoTests, .loadDB: serviceConfig = Config(accessKey: "AKIAIVFYOPZSACBLBBSA", secretKey: "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", defaultBucketName: "nynja-defaults", @@ -74,7 +75,7 @@ struct GoogleService: ThirdPartyService { init(config: AppConfig) { switch config { - case .dev, .devAutoTests: serviceConfig = Config(apiKey: "AIzaSyCAi5Ea_zkYzbIARCqfMMrq4NOy935BltA") + case .dev, .devAutoTests, .loadDB: serviceConfig = Config(apiKey: "AIzaSyCAi5Ea_zkYzbIARCqfMMrq4NOy935BltA") case .prerelease: serviceConfig = Config(apiKey: "AIzaSyCAi5Ea_zkYzbIARCqfMMrq4NOy935BltA") case .release: serviceConfig = Config(apiKey: "AIzaSyCAi5Ea_zkYzbIARCqfMMrq4NOy935BltA") } @@ -91,7 +92,7 @@ struct IntercomService: ThirdPartyService { init(config: AppConfig) { switch config { - case .dev, .devAutoTests: serviceConfig = Config(apiKey: "ios_sdk-3f0f8a4f52e4ed08a2bf6f1a39a1e9eb8b0763d5", appId: "s3isdm0n") + case .dev, .devAutoTests, .loadDB: serviceConfig = Config(apiKey: "ios_sdk-3f0f8a4f52e4ed08a2bf6f1a39a1e9eb8b0763d5", appId: "s3isdm0n") case .prerelease: serviceConfig = Config(apiKey: "ios_sdk-3f0f8a4f52e4ed08a2bf6f1a39a1e9eb8b0763d5", appId: "s3isdm0n") case .release: serviceConfig = Config(apiKey: "ios_sdk-3f0f8a4f52e4ed08a2bf6f1a39a1e9eb8b0763d5", appId: "s3isdm0n") } @@ -111,7 +112,7 @@ struct SupportService: ThirdPartyService { init(config: AppConfig) { switch config { - case .dev, .devAutoTests: serviceConfig = Config(mailAddress: "support@nynja.biz", + case .dev, .devAutoTests, .loadDB: serviceConfig = Config(mailAddress: "support@nynja.biz", faq: URL(string: "https://landing.nynja.io/-temporary-slug-81be145c-f4aa-4787-8d71-b3ab51a1aef2?hs_preview=XOrlQzBx-6108791186")!, privacyPolicy: URL(string: "https://landing.nynja.io/privacy-policy")!, terms: URL(string:"https://landing.nynja.io/terms-of-use")!) @@ -138,7 +139,7 @@ struct TestFairyService: ThirdPartyService { init(config: AppConfig) { switch config { - case .dev, .devAutoTests: serviceConfig = Config(key: "4e58695a5ce5ee4ccddbb4b852d3927626f7da36") + case .dev, .devAutoTests, .loadDB: serviceConfig = Config(key: "4e58695a5ce5ee4ccddbb4b852d3927626f7da36") case .prerelease: serviceConfig = Config(key: "4e58695a5ce5ee4ccddbb4b852d3927626f7da36") case .release: serviceConfig = Config(key: "4e58695a5ce5ee4ccddbb4b852d3927626f7da36") } -- GitLab From eaba081d048a63c9ed1a93b9adc8536936667112 Mon Sep 17 00:00:00 2001 From: Anton Makarov Date: Thu, 25 Oct 2018 17:12:34 +0300 Subject: [PATCH 02/68] LogService update --- Nynja.xcodeproj/project.pbxproj | 4 ++++ Nynja/Library/Debug/DebugLogs.swift | 4 +++- .../Splash/Interactor/SplashInteractor.swift | 6 +++--- .../AudioSessionManager.swift | 2 +- .../LogService/LogService/LogService.swift | 18 ++++++++++++------ .../LogService/LogServiceTopic.swift | 3 +-- .../Services/Debug/LogService/LogWriter.swift | 7 ++++++- Nynja/Services/MQTT/MQTTService.swift | 4 +++- .../ServiceFactory/ServiceFactory.swift | 5 +++++ .../ServiceFactoryExtension.swift | 19 +++++++++++++++++++ Nynja/Services/StorageService.swift | 2 +- 11 files changed, 58 insertions(+), 16 deletions(-) create mode 100644 Nynja/Services/ServiceFactory/ServiceFactoryExtension.swift diff --git a/Nynja.xcodeproj/project.pbxproj b/Nynja.xcodeproj/project.pbxproj index 837d688fe..12f103637 100644 --- a/Nynja.xcodeproj/project.pbxproj +++ b/Nynja.xcodeproj/project.pbxproj @@ -147,6 +147,7 @@ 260629712056EF2800CB8F65 /* LinksCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 260629702056EF2800CB8F65 /* LinksCell.swift */; }; 2606F3BC20BFE20500CF7F15 /* MessageInteractor+Translation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2606F3BB20BFE20400CF7F15 /* MessageInteractor+Translation.swift */; }; 2610D4642076516900E6E2B2 /* Array+Feature.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26DCB25320692237001EF0AB /* Array+Feature.swift */; }; + 2611CEF52182044F00FFD4DD /* ServiceFactoryExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2611CEF32182044200FFD4DD /* ServiceFactoryExtension.swift */; }; 26131E02210399BA00BE94F9 /* TranscribeService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26131E01210399BA00BE94F9 /* TranscribeService.swift */; }; 26142B1120472ECD004E5FE4 /* MessageLinkTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26142B1020472ECD004E5FE4 /* MessageLinkTable.swift */; }; 26142B1320473BFD004E5FE4 /* DBMessageLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26142B1220473BFD004E5FE4 /* DBMessageLink.swift */; }; @@ -2510,6 +2511,7 @@ 260552A51F9E1CD100D68DE6 /* SearchHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SearchHandler.swift; path = Services/HandleServices/SearchHandler.swift; sourceTree = ""; }; 260629702056EF2800CB8F65 /* LinksCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinksCell.swift; sourceTree = ""; }; 2606F3BB20BFE20400CF7F15 /* MessageInteractor+Translation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "MessageInteractor+Translation.swift"; sourceTree = ""; }; + 2611CEF32182044200FFD4DD /* ServiceFactoryExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServiceFactoryExtension.swift; sourceTree = ""; }; 26131E01210399BA00BE94F9 /* TranscribeService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TranscribeService.swift; sourceTree = ""; }; 26142B1020472ECD004E5FE4 /* MessageLinkTable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageLinkTable.swift; sourceTree = ""; }; 26142B1220473BFD004E5FE4 /* DBMessageLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DBMessageLink.swift; sourceTree = ""; }; @@ -12842,6 +12844,7 @@ isa = PBXGroup; children = ( F11786F020AC5482007A9A1B /* ServiceFactory.swift */, + 2611CEF32182044200FFD4DD /* ServiceFactoryExtension.swift */, ); name = ServiceFactory; path = Services/ServiceFactory; @@ -14696,6 +14699,7 @@ 8503B51D20503B4C006F0593 /* BaseNynjaButton.swift in Sources */, A497F56E20EFA8B6005CC60F /* ErrorsHandler.swift in Sources */, 263D66311FE8D30200A509F8 /* TypingExtension+BERT.swift in Sources */, + 2611CEF52182044F00FFD4DD /* ServiceFactoryExtension.swift in Sources */, A42CE56220692EDB000889CC /* cur.swift in Sources */, E7A77FDB1FACC58A004AE609 /* KeychainService.swift in Sources */, 85D669E720BD959800FBD803 /* Int+AnyObject.swift in Sources */, diff --git a/Nynja/Library/Debug/DebugLogs.swift b/Nynja/Library/Debug/DebugLogs.swift index dd8ea8f2d..28c3c5c28 100644 --- a/Nynja/Library/Debug/DebugLogs.swift +++ b/Nynja/Library/Debug/DebugLogs.swift @@ -9,5 +9,7 @@ import Foundation func deinited(_ object: AnyObject) { - LogService.log(topic: .arc) { "\(type(of: object)) deinited" } + #if !RELEASE + print("\(type(of: object)) deinited") + #endif } diff --git a/Nynja/Modules/Splash/Interactor/SplashInteractor.swift b/Nynja/Modules/Splash/Interactor/SplashInteractor.swift index cec6ce8a9..81bc95880 100644 --- a/Nynja/Modules/Splash/Interactor/SplashInteractor.swift +++ b/Nynja/Modules/Splash/Interactor/SplashInteractor.swift @@ -59,9 +59,9 @@ class SplashInteractor: SplashInteractorInputProtocol { } guard storageService.isUserLogined, let phoneId = storageService.phoneId else { - LogService.log(topic: .db) { return """ - Clear storage: hasPhone = \(storageService.hasPhone), hasToken = \(storageService.hasToken), - phoneId = \(storageService.phoneId ?? "none") + LogService.log(topic: .db) { [weak self] in return """ + Clear storage: hasPhone = \(self?.storageService.hasPhone), hasToken = \(self?.storageService.hasToken), + phoneId = \(self?.storageService.phoneId ?? "none") """ } prepareToShowAuth() return diff --git a/Nynja/Services/Audio/AudioSessionManager/AudioSessionManager.swift b/Nynja/Services/Audio/AudioSessionManager/AudioSessionManager.swift index 3ec40b652..bf34a7ce3 100644 --- a/Nynja/Services/Audio/AudioSessionManager/AudioSessionManager.swift +++ b/Nynja/Services/Audio/AudioSessionManager/AudioSessionManager.swift @@ -172,7 +172,7 @@ final class AudioSessionManager { LogService.log(topic: .audioSystem) { return "audioSessionRouteChange reason oldDeviceUnavailable" } case .categoryChange: LogService.log(topic: .audioSystem) { return "audioSessionRouteChange reason categoryChange" } - LogService.log(topic: .audioSystem) { return "AVAudioSession Category: \(session.category)" } + LogService.log(topic: .audioSystem) {[weak self] in return "AVAudioSession Category: \(self?.session.category)" } case .override: LogService.log(topic: .audioSystem) { return "audioSessionRouteChange reason override" } case .wakeFromSleep: diff --git a/Nynja/Services/Debug/LogService/LogService/LogService.swift b/Nynja/Services/Debug/LogService/LogService/LogService.swift index 68538a764..ea209b8f4 100644 --- a/Nynja/Services/Debug/LogService/LogService/LogService.swift +++ b/Nynja/Services/Debug/LogService/LogService/LogService.swift @@ -11,20 +11,26 @@ import os.log class LogService { - static var enabledTopics: [LogServiceTopic] = [.userDefaults, .wallet, .keychain, .fileSystem, - .audioSystem, .MQTT, .amazon, .callSystem, .locationSystem, + private static let logWriter: LogWriterProtocol? = ServiceFactory().makeLogWriter() + + private static var enabledTopics: [LogServiceTopic] = [.userDefaults, .wallet, .keychain, .fileSystem, + .audioSystem, .MQTT, .db, .amazon, .callSystem, .locationSystem, .system, .network, .galery, .videoConverter, .QRCode, - .passphrase, .arc, .connectionState] + .passphrase, .connectionState] + + private static let backgroundQueue = DispatchQueue(label:"logQueue",qos:.background) - static func log(topic: LogServiceTopic, block: () -> String) { + static func log(topic: LogServiceTopic, block: (() -> String)?) { #if !RELEASE + backgroundQueue.async { if !enabledTopics.contains(topic) { return } - LogService.executeLogs(topic: topic, text: block(), thread: Thread.current.debugDescription) + LogService.executeLogs(topic: topic, text: block?() ?? "", thread: Thread.current.debugDescription) + } #endif } private static func executeLogs(topic: LogServiceTopic, text: String, thread: String) { - LogWriter.shared?.writeLog(topic: topic.rawValue, description: text, thread: thread) + logWriter?.writeLog(topic: topic.rawValue, description: text, thread: thread) printToLog(topic: topic, text: text, thread: thread) } diff --git a/Nynja/Services/Debug/LogService/LogService/LogServiceTopic.swift b/Nynja/Services/Debug/LogService/LogService/LogServiceTopic.swift index 96e71c637..550f7724b 100644 --- a/Nynja/Services/Debug/LogService/LogService/LogServiceTopic.swift +++ b/Nynja/Services/Debug/LogService/LogService/LogServiceTopic.swift @@ -24,13 +24,12 @@ enum LogServiceTopic: String { case passphrase = "Passphrase" case push = "Push notification" case userDefaults = "User Defaults" - case arc = "ARC" case connectionState = "Connection State" static let allValues: [LogServiceTopic] = [.userDefaults, .wallet, .keychain, .fileSystem, .db, .audioSystem, .MQTT, .amazon, .callSystem, .locationSystem, .system, .network, .galery, .videoConverter, .QRCode, - .passphrase, .arc, .connectionState] + .passphrase, .connectionState] static var allValuesStrings: String { return allValues.reduce("") { (result, topic) -> String in diff --git a/Nynja/Services/Debug/LogService/LogWriter.swift b/Nynja/Services/Debug/LogService/LogWriter.swift index 1a1453996..c2ed339a6 100644 --- a/Nynja/Services/Debug/LogService/LogWriter.swift +++ b/Nynja/Services/Debug/LogService/LogWriter.swift @@ -8,7 +8,12 @@ import Foundation -class LogWriter { +protocol LogWriterProtocol: class { + func writeLog(topic: String, description: String, thread: String) + func closeFile() +} + +class LogWriter: LogWriterProtocol { static let shared: LogWriter? = LogWriter() diff --git a/Nynja/Services/MQTT/MQTTService.swift b/Nynja/Services/MQTT/MQTTService.swift index b3639a0f7..120020895 100644 --- a/Nynja/Services/MQTT/MQTTService.swift +++ b/Nynja/Services/MQTT/MQTTService.swift @@ -158,7 +158,9 @@ final class MQTTService: NSObject, CocoaMQTTDelegate, ConnectionServiceDelegate autoReconnectTimeInterval = 15 - LogService.log(topic: .MQTT) { return "setup clientID: \(mqtt?.clientID ?? "") & password: \(mqtt?.password ?? "")" } + LogService.log(topic: .MQTT) { [weak self] in + return "setup clientID: \(self?.mqtt?.clientID ?? "") & password: \(self?.mqtt?.password ?? "")" + } } func mqtt(_ mqtt: CocoaMQTT, didConnectAck ack: CocoaMQTTConnAck) { diff --git a/Nynja/Services/ServiceFactory/ServiceFactory.swift b/Nynja/Services/ServiceFactory/ServiceFactory.swift index f581ec61b..6faf08b1e 100644 --- a/Nynja/Services/ServiceFactory/ServiceFactory.swift +++ b/Nynja/Services/ServiceFactory/ServiceFactory.swift @@ -47,6 +47,7 @@ protocol ServiceFactoryProtocol: SharedServiceFactoryProtocol { func makeUseCaseValidationServise() -> UseCaseValidationServiceProtocol func makeAudioSessionManager() -> AudioSessionManager + func makeLogWriter() -> LogWriterProtocol? } final class ServiceFactory: SharedServiceFactory, ServiceFactoryProtocol { @@ -169,4 +170,8 @@ final class ServiceFactory: SharedServiceFactory, ServiceFactoryProtocol { func makeAudioSessionManager() -> AudioSessionManager { return AudioSessionManager.shared } + + func makeLogWriter() -> LogWriterProtocol? { + return LogWriter.shared + } } diff --git a/Nynja/Services/ServiceFactory/ServiceFactoryExtension.swift b/Nynja/Services/ServiceFactory/ServiceFactoryExtension.swift new file mode 100644 index 000000000..1d781aa34 --- /dev/null +++ b/Nynja/Services/ServiceFactory/ServiceFactoryExtension.swift @@ -0,0 +1,19 @@ +// +// ServiceFactoryExtension.swift +// Nynja +// +// Created by Anton Makarov on 10/25/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + +protocol ServiceFactoryProtocol: class { + func makeLogWriter() -> LogWriterProtocol? +} + +final class ServiceFactory: ServiceFactoryProtocol { + func makeLogWriter() -> LogWriterProtocol? { + return LogWriter.shared + } +} diff --git a/Nynja/Services/StorageService.swift b/Nynja/Services/StorageService.swift index 9ef71ae31..d003ee29f 100644 --- a/Nynja/Services/StorageService.swift +++ b/Nynja/Services/StorageService.swift @@ -80,7 +80,7 @@ class StorageService { application: application) keychain.set(newPassphrase, forKey: passphraseKey) - LogService.log(topic: .passphrase) { return "Store new passphrase: \(keychain.string(forKey: passphraseKey))" } + LogService.log(topic: .passphrase) {[weak self] in return "Store new passphrase: \(self?.keychain.string(forKey: self?.passphraseKey ?? ""))" } } private func setupInsecureDatabase(with name: String, application: UIApplication) { -- GitLab From 93b2758cd91c9fc0262402a1a3d569502345ecfa Mon Sep 17 00:00:00 2001 From: Anton Makarov Date: Fri, 26 Oct 2018 16:47:59 +0300 Subject: [PATCH 03/68] something --- Nynja.xcodeproj/project.pbxproj | 20 +++++++++---------- .../LogService/LogServiceProtocol.swift | 1 + .../LogService/LogServiceStub.swift | 13 ------------ .../LogService/LogWriterProtocol.swift | 13 ++++++++++++ .../Services/Debug/LogService/LogWriter.swift | 5 ----- .../ServiceFactory/ServiceFactory.swift | 5 ----- .../ServiceFactoryExtension.swift | 19 ------------------ .../SharedServiceFactory.swift | 5 +++++ 8 files changed, 29 insertions(+), 52 deletions(-) delete mode 100644 Nynja/Services/Debug/LogService/LogService/LogServiceStub.swift create mode 100644 Nynja/Services/Debug/LogService/LogService/LogWriterProtocol.swift delete mode 100644 Nynja/Services/ServiceFactory/ServiceFactoryExtension.swift diff --git a/Nynja.xcodeproj/project.pbxproj b/Nynja.xcodeproj/project.pbxproj index 12f103637..2d5f1f0af 100644 --- a/Nynja.xcodeproj/project.pbxproj +++ b/Nynja.xcodeproj/project.pbxproj @@ -147,7 +147,10 @@ 260629712056EF2800CB8F65 /* LinksCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 260629702056EF2800CB8F65 /* LinksCell.swift */; }; 2606F3BC20BFE20500CF7F15 /* MessageInteractor+Translation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2606F3BB20BFE20400CF7F15 /* MessageInteractor+Translation.swift */; }; 2610D4642076516900E6E2B2 /* Array+Feature.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26DCB25320692237001EF0AB /* Array+Feature.swift */; }; - 2611CEF52182044F00FFD4DD /* ServiceFactoryExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2611CEF32182044200FFD4DD /* ServiceFactoryExtension.swift */; }; + 2611CEF72182090900FFD4DD /* LogWriterProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2611CEF62182090900FFD4DD /* LogWriterProtocol.swift */; }; + 2611CEF82182090900FFD4DD /* LogWriterProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2611CEF62182090900FFD4DD /* LogWriterProtocol.swift */; }; + 2611CEF92182090900FFD4DD /* LogWriterProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2611CEF62182090900FFD4DD /* LogWriterProtocol.swift */; }; + 2611CEFA2182090900FFD4DD /* LogWriterProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2611CEF62182090900FFD4DD /* LogWriterProtocol.swift */; }; 26131E02210399BA00BE94F9 /* TranscribeService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26131E01210399BA00BE94F9 /* TranscribeService.swift */; }; 26142B1120472ECD004E5FE4 /* MessageLinkTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26142B1020472ECD004E5FE4 /* MessageLinkTable.swift */; }; 26142B1320473BFD004E5FE4 /* DBMessageLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26142B1220473BFD004E5FE4 /* DBMessageLink.swift */; }; @@ -573,7 +576,6 @@ 4B1F1230203C8DDE00D61D21 /* JobTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B1F122F203C8DDE00D61D21 /* JobTable.swift */; }; 4B2D063A202DDA2000010A0C /* BackSwipable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B2D0639202DDA2000010A0C /* BackSwipable.swift */; }; 4B2D063C202E1A1500010A0C /* ContactsExpandedItemsFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B2D063B202E1A1500010A0C /* ContactsExpandedItemsFactory.swift */; }; - 4B348FD62163668500CCB0E3 /* LogServiceStub.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BF090B421635B2E00DCCA5C /* LogServiceStub.swift */; }; 4B348FD7216366A900CCB0E3 /* LogServiceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BF090B821635B4700DCCA5C /* LogServiceProtocol.swift */; }; 4B348FD8216366AE00CCB0E3 /* LogServiceTopic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BF090BA21635B6600DCCA5C /* LogServiceTopic.swift */; }; 4B348FF22163808300CCB0E3 /* DeletedIndexesCalculatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B348FF12163808300CCB0E3 /* DeletedIndexesCalculatorTests.swift */; }; @@ -693,7 +695,6 @@ 4BE2C5E72142EB5A00A73DD9 /* NynjaCommunicatorService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BE2C5E52142EB5A00A73DD9 /* NynjaCommunicatorService.swift */; }; 4BE2C5E82142EB5A00A73DD9 /* NynjaRingingService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BE2C5E62142EB5A00A73DD9 /* NynjaRingingService.swift */; }; 4BEE89D69CACB85ABEE9046F /* QRCodeGeneratorPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFAB7D8D9024C26FA51BF783 /* QRCodeGeneratorPresenter.swift */; }; - 4BF090B621635B3000DCCA5C /* LogServiceStub.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BF090B421635B2E00DCCA5C /* LogServiceStub.swift */; }; 4BF090B921635B4700DCCA5C /* LogServiceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BF090B821635B4700DCCA5C /* LogServiceProtocol.swift */; }; 4BF090BB21635B6600DCCA5C /* LogServiceTopic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BF090BA21635B6600DCCA5C /* LogServiceTopic.swift */; }; 4BF090BC21635B8000DCCA5C /* LogServiceTopic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BF090BA21635B6600DCCA5C /* LogServiceTopic.swift */; }; @@ -2511,7 +2512,7 @@ 260552A51F9E1CD100D68DE6 /* SearchHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SearchHandler.swift; path = Services/HandleServices/SearchHandler.swift; sourceTree = ""; }; 260629702056EF2800CB8F65 /* LinksCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinksCell.swift; sourceTree = ""; }; 2606F3BB20BFE20400CF7F15 /* MessageInteractor+Translation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "MessageInteractor+Translation.swift"; sourceTree = ""; }; - 2611CEF32182044200FFD4DD /* ServiceFactoryExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServiceFactoryExtension.swift; sourceTree = ""; }; + 2611CEF62182090900FFD4DD /* LogWriterProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogWriterProtocol.swift; sourceTree = ""; }; 26131E01210399BA00BE94F9 /* TranscribeService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TranscribeService.swift; sourceTree = ""; }; 26142B1020472ECD004E5FE4 /* MessageLinkTable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageLinkTable.swift; sourceTree = ""; }; 26142B1220473BFD004E5FE4 /* DBMessageLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DBMessageLink.swift; sourceTree = ""; }; @@ -2952,7 +2953,6 @@ 4BE2C5E12142EB0E00A73DD9 /* AudioManagerDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AudioManagerDelegate.swift; sourceTree = ""; }; 4BE2C5E52142EB5A00A73DD9 /* NynjaCommunicatorService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NynjaCommunicatorService.swift; sourceTree = ""; }; 4BE2C5E62142EB5A00A73DD9 /* NynjaRingingService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NynjaRingingService.swift; sourceTree = ""; }; - 4BF090B421635B2E00DCCA5C /* LogServiceStub.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogServiceStub.swift; sourceTree = ""; }; 4BF090B821635B4700DCCA5C /* LogServiceProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogServiceProtocol.swift; sourceTree = ""; }; 4BF090BA21635B6600DCCA5C /* LogServiceTopic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogServiceTopic.swift; sourceTree = ""; }; 4BF090C421635E8600DCCA5C /* Message+LinkedId.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Message+LinkedId.swift"; sourceTree = ""; }; @@ -7143,8 +7143,8 @@ isa = PBXGroup; children = ( 4B7C73EF215A5508007924DB /* LogService.swift */, - 4BF090B421635B2E00DCCA5C /* LogServiceStub.swift */, 4BF090B821635B4700DCCA5C /* LogServiceProtocol.swift */, + 2611CEF62182090900FFD4DD /* LogWriterProtocol.swift */, 4BF090BA21635B6600DCCA5C /* LogServiceTopic.swift */, ); path = LogService; @@ -12844,7 +12844,6 @@ isa = PBXGroup; children = ( F11786F020AC5482007A9A1B /* ServiceFactory.swift */, - 2611CEF32182044200FFD4DD /* ServiceFactoryExtension.swift */, ); name = ServiceFactory; path = Services/ServiceFactory; @@ -14631,6 +14630,7 @@ A42CE5B020692EDB000889CC /* TypeSpec.swift in Sources */, 26B32B971FE20BB500888A0A /* DescExtension+BERT.swift in Sources */, A42CE54220692EDA000889CC /* muc.swift in Sources */, + 2611CEF82182090900FFD4DD /* LogWriterProtocol.swift in Sources */, A45F115620B4226600F45004 /* RawRepresentable+Localized.swift in Sources */, 4BD53BF4202C8BCA00569C1A /* AVURLAsset+Duration.swift in Sources */, B767F48D215D1C2500FA9B27 /* ComingSoonProtocol.swift in Sources */, @@ -14699,7 +14699,6 @@ 8503B51D20503B4C006F0593 /* BaseNynjaButton.swift in Sources */, A497F56E20EFA8B6005CC60F /* ErrorsHandler.swift in Sources */, 263D66311FE8D30200A509F8 /* TypingExtension+BERT.swift in Sources */, - 2611CEF52182044F00FFD4DD /* ServiceFactoryExtension.swift in Sources */, A42CE56220692EDB000889CC /* cur.swift in Sources */, E7A77FDB1FACC58A004AE609 /* KeychainService.swift in Sources */, 85D669E720BD959800FBD803 /* Int+AnyObject.swift in Sources */, @@ -14827,6 +14826,7 @@ 6D6234F61F1E150600EF375F /* HistoryTableDS.swift in Sources */, FBE3885D2118849000149721 /* AlertActionWrapper.swift in Sources */, F119E66E20D24BBF0043A532 /* MultiplePreviewWireframe.swift in Sources */, + 2611CEF72182090900FFD4DD /* LogWriterProtocol.swift in Sources */, 2648C40F2069B52100863614 /* ChangeNumberStep3Presenter.swift in Sources */, A42D51A0206A361400EEB952 /* reader.swift in Sources */, A42D52BB206A53AA00EEB952 /* Vox_Spec.swift in Sources */, @@ -16654,7 +16654,6 @@ FB816EF020B5B36D00093DCD /* HistoryRequestModelTests.swift in Sources */, 85458CEA212D742300BA8814 /* muc.swift in Sources */, A4CB153B21039C1100C3B68B /* JailbreakDetectorProtocol.swift in Sources */, - 4BF090B621635B3000DCCA5C /* LogServiceStub.swift in Sources */, 4B8C05952164A9D60034D8F3 /* ChatCellModelMock.swift in Sources */, 85458D01212D7C1A00BA8814 /* StringAtomExtension.swift in Sources */, A4AB8E522105EC46005F9B0C /* TextField.swift in Sources */, @@ -16675,6 +16674,7 @@ 4BF090CE21635FEE00DCCA5C /* Message+Type.swift in Sources */, A49EE6D7210B110800B700B1 /* Link.swift in Sources */, 4B8FC31C2163CD8C00602D6B /* ChatCellModel.swift in Sources */, + 2611CEF92182090900FFD4DD /* LogWriterProtocol.swift in Sources */, 85458CE4212D731300BA8814 /* MessageIdentifiers.swift in Sources */, FB61D651214FEC8200CB2A1F /* LocalizableConstants.swift in Sources */, A4330A562109D60D0060BD93 /* QueryFactoryProtocol.swift in Sources */, @@ -16721,12 +16721,12 @@ buildActionMask = 2147483647; files = ( 4B348FD8216366AE00CCB0E3 /* LogServiceTopic.swift in Sources */, + 2611CEFA2182090900FFD4DD /* LogWriterProtocol.swift in Sources */, FE21ACB92113AB3C006010A0 /* KeychainServiceTest.swift in Sources */, FE21ACBC2113AB92006010A0 /* QueryFactoryProtocol.swift in Sources */, 4B348FD7216366A900CCB0E3 /* LogServiceProtocol.swift in Sources */, FE21ACBD2113ABBF006010A0 /* DictionaryExtension.swift in Sources */, FE21ACBA2113AB4B006010A0 /* KeychainService.swift in Sources */, - 4B348FD62163668500CCB0E3 /* LogServiceStub.swift in Sources */, 4BF2C3E92189B49500E59F6C /* Localizable.swift in Sources */, 85458CDC212D6FFF00BA8814 /* String+Split.swift in Sources */, FB61D652214FEC8300CB2A1F /* LocalizableConstants.swift in Sources */, diff --git a/Nynja/Services/Debug/LogService/LogService/LogServiceProtocol.swift b/Nynja/Services/Debug/LogService/LogService/LogServiceProtocol.swift index 5f6c98b23..94822e1a5 100644 --- a/Nynja/Services/Debug/LogService/LogService/LogServiceProtocol.swift +++ b/Nynja/Services/Debug/LogService/LogService/LogServiceProtocol.swift @@ -10,3 +10,4 @@ protocol LogServiceProtocol { static var enabledTopics: [LogServiceTopic] { get } static func log(topic: LogServiceTopic, block: () -> String) } + diff --git a/Nynja/Services/Debug/LogService/LogService/LogServiceStub.swift b/Nynja/Services/Debug/LogService/LogService/LogServiceStub.swift deleted file mode 100644 index a2155916c..000000000 --- a/Nynja/Services/Debug/LogService/LogService/LogServiceStub.swift +++ /dev/null @@ -1,13 +0,0 @@ -// -// LogServiceStub.swift -// Nynja -// -// Created by Volodymyr Hryhoriev on 10/2/18. -// Copyright © 2018 TecSynt Solutions. All rights reserved. -// - -/// In order to fix errors in test targets -class LogService: LogServiceProtocol { - static let enabledTopics: [LogServiceTopic] = [] - static func log(topic: LogServiceTopic, block: () -> String) {} -} diff --git a/Nynja/Services/Debug/LogService/LogService/LogWriterProtocol.swift b/Nynja/Services/Debug/LogService/LogService/LogWriterProtocol.swift new file mode 100644 index 000000000..d496f9b35 --- /dev/null +++ b/Nynja/Services/Debug/LogService/LogService/LogWriterProtocol.swift @@ -0,0 +1,13 @@ +// +// LogWriterProtocol.swift +// Nynja +// +// Created by Anton Makarov on 10/25/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + +protocol LogWriterProtocol: class { + func writeLog(topic: String, description: String, thread: String) +} diff --git a/Nynja/Services/Debug/LogService/LogWriter.swift b/Nynja/Services/Debug/LogService/LogWriter.swift index c2ed339a6..a254422d3 100644 --- a/Nynja/Services/Debug/LogService/LogWriter.swift +++ b/Nynja/Services/Debug/LogService/LogWriter.swift @@ -8,11 +8,6 @@ import Foundation -protocol LogWriterProtocol: class { - func writeLog(topic: String, description: String, thread: String) - func closeFile() -} - class LogWriter: LogWriterProtocol { static let shared: LogWriter? = LogWriter() diff --git a/Nynja/Services/ServiceFactory/ServiceFactory.swift b/Nynja/Services/ServiceFactory/ServiceFactory.swift index 6faf08b1e..f581ec61b 100644 --- a/Nynja/Services/ServiceFactory/ServiceFactory.swift +++ b/Nynja/Services/ServiceFactory/ServiceFactory.swift @@ -47,7 +47,6 @@ protocol ServiceFactoryProtocol: SharedServiceFactoryProtocol { func makeUseCaseValidationServise() -> UseCaseValidationServiceProtocol func makeAudioSessionManager() -> AudioSessionManager - func makeLogWriter() -> LogWriterProtocol? } final class ServiceFactory: SharedServiceFactory, ServiceFactoryProtocol { @@ -170,8 +169,4 @@ final class ServiceFactory: SharedServiceFactory, ServiceFactoryProtocol { func makeAudioSessionManager() -> AudioSessionManager { return AudioSessionManager.shared } - - func makeLogWriter() -> LogWriterProtocol? { - return LogWriter.shared - } } diff --git a/Nynja/Services/ServiceFactory/ServiceFactoryExtension.swift b/Nynja/Services/ServiceFactory/ServiceFactoryExtension.swift deleted file mode 100644 index 1d781aa34..000000000 --- a/Nynja/Services/ServiceFactory/ServiceFactoryExtension.swift +++ /dev/null @@ -1,19 +0,0 @@ -// -// ServiceFactoryExtension.swift -// Nynja -// -// Created by Anton Makarov on 10/25/18. -// Copyright © 2018 TecSynt Solutions. All rights reserved. -// - -import Foundation - -protocol ServiceFactoryProtocol: class { - func makeLogWriter() -> LogWriterProtocol? -} - -final class ServiceFactory: ServiceFactoryProtocol { - func makeLogWriter() -> LogWriterProtocol? { - return LogWriter.shared - } -} diff --git a/Shared/Services/SharedServiceFactory/SharedServiceFactory.swift b/Shared/Services/SharedServiceFactory/SharedServiceFactory.swift index 1b8274323..40e54e9d2 100644 --- a/Shared/Services/SharedServiceFactory/SharedServiceFactory.swift +++ b/Shared/Services/SharedServiceFactory/SharedServiceFactory.swift @@ -12,6 +12,7 @@ protocol SharedServiceFactoryProtocol: class { func makeAmazonManager() -> AmazonManager func makeAmazonInitializer() -> AmazonInitializer func makeTypingSenderService() -> TypingSenderServiceProtocol + func makeLogWriter() -> LogWriterProtocol? } class SharedServiceFactory: SharedServiceFactoryProtocol { @@ -38,4 +39,8 @@ class SharedServiceFactory: SharedServiceFactoryProtocol { return TypingSenderService(dependencies: dependencies) } + + func makeLogWriter() -> LogWriterProtocol? { + return LogWriter.shared + } } -- GitLab From 47282c7501e4de74f36df1cc58da76d0c7e6a8b8 Mon Sep 17 00:00:00 2001 From: Volodymyr Hryhoriev Date: Thu, 1 Nov 2018 11:48:33 +0200 Subject: [PATCH 04/68] 1. Remove `voxId`. 2. Create `FullNameable` protocol and confirm it by `Contact` and `Roster` models. --- Nynja/ContactDAO.swift | 22 ------------- Nynja/ContactDAOProtocol.swift | 1 - Nynja/Extensions/Models/RosterExtension.swift | 23 ++++--------- Nynja/FullNameable.swift | 32 +++++++++++++++++++ Nynja/StorageService+UserInfo.swift | 6 ---- Nynja/UserInfo.swift | 3 -- .../Services/UserInfo/UserInfoTest.swift | 4 --- .../Models/Contact/ContactExtension.swift | 26 +-------------- 8 files changed, 39 insertions(+), 78 deletions(-) create mode 100644 Nynja/FullNameable.swift diff --git a/Nynja/ContactDAO.swift b/Nynja/ContactDAO.swift index d7ce86b92..7343a18f1 100644 --- a/Nynja/ContactDAO.swift +++ b/Nynja/ContactDAO.swift @@ -63,28 +63,6 @@ class ContactDAO: ContactDAOProtocol { return Contact(contact: contact) } - static func findContactBy(voxId: String) -> Contact? { - let serviceIdColumn = Column(ServiceTable.Column.id.title) - let serviceTypeColumn = Column(ServiceTable.Column.type.title) - let serviceTargetTypeColumn = Column(ServiceTable.Column.targetType.title) - let contactIdColumn = Column(ContactTable.Column.phoneId.title) - let voxType = "vox" - - let predicate = serviceIdColumn == voxId && - serviceTypeColumn == voxType && - serviceTargetTypeColumn == DBService.TargetType.contact.rawValue - - guard - let service = dbManager.fetch({ db in - return try DBService.filter(predicate).fetchOne(db) - }), let contact = dbManager.fetch({ (db) in - return try DBContact.filter(contactIdColumn == service.targetId).fetchOne(db) - }) else { - return nil - } - - return Contact(contact: contact) - } // MARK: -- Contacts static func fetchContacts() -> [Contact] { diff --git a/Nynja/ContactDAOProtocol.swift b/Nynja/ContactDAOProtocol.swift index f97c190fe..9d2b23e7a 100644 --- a/Nynja/ContactDAOProtocol.swift +++ b/Nynja/ContactDAOProtocol.swift @@ -17,7 +17,6 @@ protocol ContactDAOProtocol: DAOProtocol { static func findContactBy(phoneId: String) -> Contact? static func findContactBy(username: String) -> Contact? - static func findContactBy(voxId: String) -> Contact? // MARK: -- Contacts static func fetchContacts() -> [Contact] diff --git a/Nynja/Extensions/Models/RosterExtension.swift b/Nynja/Extensions/Models/RosterExtension.swift index ddd7a130b..12880f6a5 100644 --- a/Nynja/Extensions/Models/RosterExtension.swift +++ b/Nynja/Extensions/Models/RosterExtension.swift @@ -6,31 +6,21 @@ // Copyright © 2017 TecSynt Solutions. All rights reserved. // -extension Roster { +extension Roster: FullNameable { // Returns self contact var myContact: Contact? { // Check that 'contacts' and 'phone' exist - guard let contacts = self.userlist, let phone = self.phone else { - return nil + guard let contacts = self.userlist, + let phoneId = self.phoneId, + let contact = contacts.first(where: { $0.phone_id == phoneId }) else { + return nil } - //Find index of self contact - guard let index = contacts.index(where: { contact in - // Check that 'phoneNumber' exists in contact - guard let phoneNumber = contact.phoneNumber else { - return false - } - - return phone == phoneNumber - }) else { - return nil - } - - let contact = contacts[index] contact.names = self.names contact.surnames = self.surnames contact.avatar = self.avatar + return contact } @@ -45,5 +35,4 @@ extension Roster { return "\(phone)_\(rosterId)" } - } diff --git a/Nynja/FullNameable.swift b/Nynja/FullNameable.swift new file mode 100644 index 000000000..41687f81e --- /dev/null +++ b/Nynja/FullNameable.swift @@ -0,0 +1,32 @@ +// +// FullNameable.swift +// Nynja +// +// Created by Volodymyr Hryhoriev on 11/1/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +protocol FullNameable { + var names: String? { get set } + var surnames: String? { get set } + var fullName: String? { get } +} + +extension FullNameable { + + var fullName: String? { + if let name = self.names, let surname = self.surnames { + if !surname.isEmpty { + return "\(name) \(surname)" + } else { + return name + } + } else if names != nil && surnames == nil { + return names + } else if names == nil && surnames == nil { + return surnames + } + + return nil + } +} diff --git a/Nynja/StorageService+UserInfo.swift b/Nynja/StorageService+UserInfo.swift index b520bbda0..2e7b6d7b5 100644 --- a/Nynja/StorageService+UserInfo.swift +++ b/Nynja/StorageService+UserInfo.swift @@ -46,11 +46,6 @@ extension StorageService: UserInfo { set { set(newValue, forId: .phone) } } - var voxId: String? { - get { return userDefaults?.string(forKey: UserIdentifiers.voxId.rawValue) } - set { set(newValue, forId: .voxId) } - } - var rosterId: Int64? { get { guard let id = userDefaults?.value(forKey: UserIdentifiers.rosterId.rawValue) as? Int64 else { return nil } @@ -82,7 +77,6 @@ extension StorageService: UserInfo { func setupUserInfo(from roster: Roster) { phone = roster.phone rosterId = roster.id - voxId = roster.myContact?.voxID } private func set(_ value: Any?, forId id: UserIdentifiers) { diff --git a/Nynja/UserInfo.swift b/Nynja/UserInfo.swift index abddc6a46..b54af5b57 100644 --- a/Nynja/UserInfo.swift +++ b/Nynja/UserInfo.swift @@ -11,7 +11,6 @@ import Foundation enum UserIdentifiers: String { case token case phone - case voxId case rosterId case clientId = "clientID" @@ -24,7 +23,6 @@ protocol UserInfo: class { var tokenAsNSData: NSData? { get } var phone: String? { get set } - var voxId: String? { get set } var rosterId: Int64? { get set } var clientId: String? { get set } @@ -60,7 +58,6 @@ extension UserInfo { LogService.log(topic: .userDefaults) { return "drop user info" } token = nil phone = nil - voxId = nil rosterId = nil clientId = nil } diff --git a/NynjaUnitTests/Services/UserInfo/UserInfoTest.swift b/NynjaUnitTests/Services/UserInfo/UserInfoTest.swift index 73ac1e54a..f2ba00aa2 100644 --- a/NynjaUnitTests/Services/UserInfo/UserInfoTest.swift +++ b/NynjaUnitTests/Services/UserInfo/UserInfoTest.swift @@ -93,7 +93,6 @@ class UserInfoTest: XCTestCase { userInfo.phone = phone userInfo.rosterId = rosterId userInfo.token = token - userInfo.voxId = "vox_id" userInfo.clientId = "client_id" userInfo.dropUserInfo() @@ -101,7 +100,6 @@ class UserInfoTest: XCTestCase { XCTAssertNil(userInfo.phone) XCTAssertNil(userInfo.rosterId) XCTAssertNil(userInfo.token) - XCTAssertNil(userInfo.voxId) XCTAssertNil(userInfo.clientId) } @@ -114,11 +112,9 @@ private extension UserInfoTest { var token: String? var tokenAsNSData: NSData? var phone: String? - var voxId: String? var rosterId: Int64? var clientId: String? var wasLogined: Bool = true var wasRun: Bool = true } - } diff --git a/Shared/Library/Extensions/Models/Contact/ContactExtension.swift b/Shared/Library/Extensions/Models/Contact/ContactExtension.swift index 0acc8d98f..321bdcb09 100644 --- a/Shared/Library/Extensions/Models/Contact/ContactExtension.swift +++ b/Shared/Library/Extensions/Models/Contact/ContactExtension.swift @@ -8,7 +8,7 @@ import UIKit -extension Contact { +extension Contact: FullNameable { enum Status: String { case get case request @@ -22,24 +22,6 @@ extension Contact { case ignore } - /// Returns fullname, name or surname depending on what exists. - /// If contact doesn't have name and surname, it will return nil - var fullName: String? { - if let name = self.names, let surname = self.surnames { - if !surname.isEmpty { - return "\(name) \(surname)" - } else { - return name - } - } else if names != nil && surnames == nil { - return names - } else if names == nil && surnames == nil { - return surnames - } - - return nil - } - var alias: String? { return fullName } @@ -121,12 +103,6 @@ extension Contact { return phone_id ?? "" } - /// Returns 'person_id' if exists, else - empty string - var voxID: String { - let ser = self.services?.first { ($0.type as? StringAtom)?.string == "vox" } - return ser?.id ?? "" - } - /// Parse roster id from the 'phone_id' property var rosterId: Int64 { if var phone = self.phone_id, let range = phone.range(of: "_") { -- GitLab From fe2581e188fa499c46e5a6afad5b9e24e9eea135 Mon Sep 17 00:00:00 2001 From: Volodymyr Hryhoriev Date: Thu, 1 Nov 2018 11:54:42 +0200 Subject: [PATCH 05/68] Update `NotificationManager`. --- Nynja/NotificationManager.swift | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/Nynja/NotificationManager.swift b/Nynja/NotificationManager.swift index 7b86fdf0a..4059601d6 100644 --- a/Nynja/NotificationManager.swift +++ b/Nynja/NotificationManager.swift @@ -261,32 +261,29 @@ final class NotificationManager { } private func getContainer(isFromPush: Bool) -> Container? { - guard let message = getMessage(), message.messageStatus != .delete else { + guard let message = getMessage(), + message.messageStatus != .delete, + let phoneId = StorageService.sharedInstance.phoneId else { return nil } let sender = message.from - guard let id = StorageService.sharedInstance.rosterId, let roster = RosterDAO.findRosterBy(id: id) else { return nil } - guard let contacts = roster.userlist else { return nil } - guard let myContactID = roster.myContact?.phone_id else { return nil } - let isCursorInOwn = message.isInOwnChat && message.isCursor - if sender == myContactID && !isCursorInOwn { + if sender == phoneId, !isCursorInOwn { return nil } - var senderContact = contacts.first { (contact) -> Bool in - return contact.phone_id == sender - } - + var senderContact = sender.flatMap { ContactDAO.findContactBy(phoneId: $0) } var room_s: Room? = nil - if let room = roster.roomlist?.first(where: { $0.id == message.to }), - let member = room.allMembersWithoutFilter?.first(where: { $0.phone_id == StorageService.sharedInstance.phoneId }) { + if let room = message.to.flatMap({ RoomDAO.findRoom(by: $0) }), + let member = room.allMembersWithoutFilter?.first(where: { $0.phone_id == phoneId }) { + senderContact = nil room_s = room - if !member.shouldNotify(for: message) { + + guard member.shouldNotify(for: message) else { return nil } } -- GitLab From 089dac9505ab10ceec0ecfb82fc1a1a79b31a499 Mon Sep 17 00:00:00 2001 From: Volodymyr Hryhoriev Date: Thu, 1 Nov 2018 11:55:12 +0200 Subject: [PATCH 06/68] Correct prev commit. --- Nynja.xcodeproj/project.pbxproj | 6 ++++++ Nynja.xcodeproj/xcshareddata/xcschemes/LoadDB.xcscheme | 2 +- Nynja/RosterDAO.swift | 1 - Nynja/RosterDAOProtocol.swift | 1 - 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Nynja.xcodeproj/project.pbxproj b/Nynja.xcodeproj/project.pbxproj index 2d5f1f0af..11f215ba3 100644 --- a/Nynja.xcodeproj/project.pbxproj +++ b/Nynja.xcodeproj/project.pbxproj @@ -717,6 +717,8 @@ 4BF2C3E42188BABC00E59F6C /* UIDeviceExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = E7A77FD71FACC360004AE609 /* UIDeviceExtension.swift */; }; 4BF2C3E92189B49500E59F6C /* Localizable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D485DE41F0AD96D00E12FB1 /* Localizable.swift */; }; 4BF2C3F02189F58F00E59F6C /* ServerSignal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BF2C3EF2189F58F00E59F6C /* ServerSignal.swift */; }; + 4BF2C3FC218AFE9D00E59F6C /* FullNameable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BF2C3FB218AFE9D00E59F6C /* FullNameable.swift */; }; + 4BF2C3FD218AFF6300E59F6C /* FullNameable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BF2C3FB218AFE9D00E59F6C /* FullNameable.swift */; }; 4C5EEA13EBC6A8398F08DCD1 /* MainWireframe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65AAB8F770774CE3AE3FD6E1 /* MainWireframe.swift */; }; 4D53FE7454959323B1CCFD96 /* ProfileViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D270F638DBB2D8FC1BDEB633 /* ProfileViewController.swift */; }; 4DAEBCF361B86B0AD3C98749 /* EditUsernameInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBE3BAC9B7EA418FB463EF04 /* EditUsernameInteractor.swift */; }; @@ -2960,6 +2962,7 @@ 4BF090CB21635FDC00DCCA5C /* Message+Type.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Message+Type.swift"; sourceTree = ""; }; 4BF2C3DF2188B27500E59F6C /* Injectable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Injectable.swift; sourceTree = ""; }; 4BF2C3EF2189F58F00E59F6C /* ServerSignal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerSignal.swift; sourceTree = ""; }; + 4BF2C3FB218AFE9D00E59F6C /* FullNameable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FullNameable.swift; sourceTree = ""; }; 4CDA2BE900351F21464CE687 /* DateTimePickerInteractor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DateTimePickerInteractor.swift; sourceTree = ""; }; 4D247CBC45C1C1267BBBB289 /* QRCodeReaderInteractor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = QRCodeReaderInteractor.swift; sourceTree = ""; }; 4F7C039B61A0663D43BE5AE5 /* SelectCountryProtocols.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = SelectCountryProtocols.swift; sourceTree = ""; }; @@ -5960,6 +5963,7 @@ 266AE8C2203496B60096A12C /* AsyncOperation.swift */, F112B18F20E0FBE800B06E3E /* AsyncBlockOperation.swift */, 263C04E82132E2FF00B8F0BE /* WrappedTaskOperation.swift */, + 4BF2C3FB218AFE9D00E59F6C /* FullNameable.swift */, ); name = Library; sourceTree = ""; @@ -14758,6 +14762,7 @@ A4330A6F2109EBA70060BD93 /* CountriesProvider.swift in Sources */, A42CE54820692EDB000889CC /* reader.swift in Sources */, A42CE60A20692EDB000889CC /* Index_Spec.swift in Sources */, + 4BF2C3FD218AFF6300E59F6C /* FullNameable.swift in Sources */, 26352916207572AA00DC6FBD /* JobExtension.swift in Sources */, 8566BB12215BC39D00320E15 /* FetchType.swift in Sources */, 8520040120D4672E007C0036 /* StickerPack.swift in Sources */, @@ -16027,6 +16032,7 @@ 8524C4D6217772C8003BF374 /* Member+Construct.swift in Sources */, A44B4D5A20CE9BDF00CA700A /* SwitchCell.swift in Sources */, F1607B3020B2FD5A00BDF60A /* QRNotificationVIew.swift in Sources */, + 4BF2C3FC218AFE9D00E59F6C /* FullNameable.swift in Sources */, FEA655CB2167777E00B44029 /* SeedVerificationWalletPresenter.swift in Sources */, 260313A420A0A4BA009AC66D /* DirectableActionCellViewModel.swift in Sources */, 859773232087965700B03B4A /* NynjaControlContainerView.swift in Sources */, diff --git a/Nynja.xcodeproj/xcshareddata/xcschemes/LoadDB.xcscheme b/Nynja.xcodeproj/xcshareddata/xcschemes/LoadDB.xcscheme index 10f32b01c..2243a6445 100644 --- a/Nynja.xcodeproj/xcshareddata/xcschemes/LoadDB.xcscheme +++ b/Nynja.xcodeproj/xcshareddata/xcschemes/LoadDB.xcscheme @@ -76,7 +76,7 @@ DBRoster? static func findRosterBy(id: Int64) -> Roster? - } -- GitLab From e8d7574df088c489878c1352a9d2e7d5cbf51ac1 Mon Sep 17 00:00:00 2001 From: Volodymyr Hryhoriev Date: Thu, 1 Nov 2018 12:00:14 +0200 Subject: [PATCH 07/68] Replace `RosterDAO.currentRoster` with `ContactDAO.currentContact` in `AddParticipantsInteractor`, `NynjaCommunicatorService`, `MainInteractor`. --- .../Interactor/AddParticipantsInteractor.swift | 3 +-- Nynja/Modules/Main/Interactor/MainInteractor.swift | 11 ++++++----- .../NynjaCalls/NynjaCommunicatorService.swift | 3 +-- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/Nynja/Modules/AddParticipants/Interactor/AddParticipantsInteractor.swift b/Nynja/Modules/AddParticipants/Interactor/AddParticipantsInteractor.swift index 25f312b69..7c4c2b1a7 100644 --- a/Nynja/Modules/AddParticipants/Interactor/AddParticipantsInteractor.swift +++ b/Nynja/Modules/AddParticipants/Interactor/AddParticipantsInteractor.swift @@ -47,8 +47,7 @@ class AddParticipantsInteractor: BaseInteractor, AddParticipantsInteractorInputP } func getMySelf() -> Contact? { - guard let id = storageService.rosterId, let roster = RosterDAO.findRosterBy(id: id) else { return nil } - return roster.myContact + return ContactDAO.currentContact } func allocateConference() { diff --git a/Nynja/Modules/Main/Interactor/MainInteractor.swift b/Nynja/Modules/Main/Interactor/MainInteractor.swift index a2ad1f2ff..707e9e478 100644 --- a/Nynja/Modules/Main/Interactor/MainInteractor.swift +++ b/Nynja/Modules/Main/Interactor/MainInteractor.swift @@ -143,16 +143,17 @@ class MainInteractor: MainInteractorInputProtocol, EditPhotoDelegate, MQTTServic private func setupIntercom() { Intercom.logout() - guard let roster = RosterDAO.currentRoster else { + + guard let contact = ContactDAO.currentContact else { Intercom.registerUnidentifiedUser() return } - let rand = "\(UIDevice.current.persistentIdentifier)" + let userAttributes = ICMUserAttributes() - let id = roster.id != nil ? "\(roster.phoneId!)" : rand + let id = contact.phone_id ?? "\(UIDevice.current.persistentIdentifier)" Intercom.registerUser(withUserId: id) - userAttributes.name = roster.myContact?.fullName - userAttributes.phone = roster.phone + userAttributes.name = contact.fullName + userAttributes.phone = contact.phoneNumber Intercom.updateUser(userAttributes) } diff --git a/Nynja/Services/NynjaCalls/NynjaCommunicatorService.swift b/Nynja/Services/NynjaCalls/NynjaCommunicatorService.swift index 39a8fb5cf..b04c64d7f 100644 --- a/Nynja/Services/NynjaCalls/NynjaCommunicatorService.swift +++ b/Nynja/Services/NynjaCalls/NynjaCommunicatorService.swift @@ -464,8 +464,7 @@ class NynjaCommunicatorService: NSObject, NynjaCommunicatorDelegate, NYNCallDele //MARK: Helpers func getMySelf() -> Contact? { - guard let id = StorageService.sharedInstance.rosterId, let roster = RosterDAO.findRosterBy(id: id) else { return nil } - return roster.myContact + return ContactDAO.currentContact } func makeMembers(contacts: [Contact], room: Room?) -> [Member] { -- GitLab From 214dba77c6c8fefafe33c7053c74ae4c50b697fc Mon Sep 17 00:00:00 2001 From: Volodymyr Hryhoriev Date: Thu, 1 Nov 2018 12:01:24 +0200 Subject: [PATCH 08/68] Remove usage of `myContact` in `ProfileHandler`. --- Nynja/Services/HandleServices/ProfileHandler.swift | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Nynja/Services/HandleServices/ProfileHandler.swift b/Nynja/Services/HandleServices/ProfileHandler.swift index d2d68a05c..d3a900ce3 100644 --- a/Nynja/Services/HandleServices/ProfileHandler.swift +++ b/Nynja/Services/HandleServices/ProfileHandler.swift @@ -74,7 +74,7 @@ class ProfileHandler: BaseHandler { return } configureTestFairy(with: roster) - configureNynjaCommunicatorService(profile) + configureNynjaCommunicatorService(with: roster) requestJobs(with: phoneId) requestStickerPacks(with: phoneId) @@ -147,11 +147,14 @@ class ProfileHandler: BaseHandler { } private static func configureTestFairy(with roster: Roster) { - TestFairy.setUserId("\(roster.myContact?.phone_id ?? "")_\(roster.myContact?.fullName ?? "")") + let phoneId = roster.phoneId ?? "" + let fullName = roster.fullName ?? "" + let userId = "\(phoneId)_\(fullName)" + TestFairy.setUserId(userId) } - private static func configureNynjaCommunicatorService(_ profile: Profile) { - guard let rosterId = (profile.rosters?.first as? Roster)?.myContact?.phone_id else { + private static func configureNynjaCommunicatorService(with roster: Roster) { + guard let _ = roster.phoneId else { return } -- GitLab From 261c4f20ca41bb91d85a2f8f39ebc2543e03a2d2 Mon Sep 17 00:00:00 2001 From: Volodymyr Hryhoriev Date: Thu, 1 Nov 2018 16:49:40 +0200 Subject: [PATCH 09/68] Remove usage of `currentRoster` from the `MainInteractor`. --- .../Main/Interactor/MainInteractor.swift | 48 +++++++++++-------- Nynja/RosterDAO.swift | 2 +- Nynja/RosterDAOProtocol.swift | 1 - 3 files changed, 30 insertions(+), 21 deletions(-) diff --git a/Nynja/Modules/Main/Interactor/MainInteractor.swift b/Nynja/Modules/Main/Interactor/MainInteractor.swift index 707e9e478..6952408d4 100644 --- a/Nynja/Modules/Main/Interactor/MainInteractor.swift +++ b/Nynja/Modules/Main/Interactor/MainInteractor.swift @@ -10,15 +10,15 @@ import SDWebImage import Intercom class MainInteractor: MainInteractorInputProtocol, EditPhotoDelegate, MQTTServiceDelegate { - func startRinging() { - - } - weak var presenter: MainInteractorOutputProtocol! var contact: Contact? { return ContactDAO.currentContact } + + private var storageService: StorageService { + return StorageService.sharedInstance + } init() { let notificationsSettings = UIUserNotificationSettings(types: [.sound, .alert, .badge], categories: nil) @@ -80,7 +80,7 @@ class MainInteractor: MainInteractorInputProtocol, EditPhotoDelegate, MQTTServic } func deleteAccount() { - if let phone = StorageService.sharedInstance.phone, phone != "" { + if let phone = storageService.phone, phone != "" { MQTTService.sharedInstance.deleteUser(number: phone) } LogService.log(topic: .db) { return "Clear storage: delete account" } @@ -102,23 +102,33 @@ class MainInteractor: MainInteractorInputProtocol, EditPhotoDelegate, MQTTServic } func updateAvatar(url: URL) { - guard let roster = RosterDAO.currentRoster else { return } - + guard let rosterId = storageService.rosterId else { + return + } + + do { + try updateAvatar(url: url, rosterId: rosterId) + uploadAvatar(with: url, rosterId: rosterId) + } catch { + assertionFailure("Failed to save avatar url for roster with id \(rosterId)") + } + } + + private func updateAvatar(url: URL, rosterId: Int64) throws { + let roster = Roster() + roster.id = rosterId + roster.avatar = url.absoluteString.getShortPath() + try storageService.perform(action: .updateColumns([RosterTable.Column.avatar.title]), with: roster) + } + + private func uploadAvatar(with url: URL, rosterId: Int64) { let sync = SyncFileManager.sharedInstance sync.downloader = AmazonManager.shared - - roster.avatar = url.absoluteString.getShortPath() - do { - try StorageService.sharedInstance.perform(action: .updateColumns([RosterTable.Column.avatar.title]), with: roster) - - SyncFileManager.sharedInstance.saveExternalFileLink(localUrl: url.path) { (ext, progress,request) in - if ext != nil { - if let id = roster.id { - MQTTService.sharedInstance.updateAvatar(id: id, link: String(describing: ext!)) - } - } + SyncFileManager.sharedInstance.saveExternalFileLink(localUrl: url.path) { (ext, progress, request) in + if ext != nil { + MQTTService.sharedInstance.updateAvatar(id: rosterId, link: String(describing: ext!)) } - } catch {} + } } // MARK: MQTT subscribing diff --git a/Nynja/RosterDAO.swift b/Nynja/RosterDAO.swift index 1c881d08d..60dea2322 100644 --- a/Nynja/RosterDAO.swift +++ b/Nynja/RosterDAO.swift @@ -16,7 +16,7 @@ class RosterDAO: RosterDAOProtocol { return Roster(roster: roster) } - static var currentDBRoster: DBRoster? { + private static var currentDBRoster: DBRoster? { guard let id = StorageService.sharedInstance.rosterId else { return nil } return fetchRosterBy(id: id) } diff --git a/Nynja/RosterDAOProtocol.swift b/Nynja/RosterDAOProtocol.swift index 190692bc4..6615f93cc 100644 --- a/Nynja/RosterDAOProtocol.swift +++ b/Nynja/RosterDAOProtocol.swift @@ -10,7 +10,6 @@ protocol RosterDAOProtocol: DAOProtocol { // MARK: - Fetch static var currentRoster: Roster? { get } - static var currentDBRoster: DBRoster? { get } static func fetchRosterBy(rowId: Int64) -> DBRoster? static func fetchRosterBy(id: Int64) -> DBRoster? -- GitLab From d85e0245d21496ff5c09ff04edcba3c146bc1b6d Mon Sep 17 00:00:00 2001 From: Volodymyr Hryhoriev Date: Thu, 1 Nov 2018 17:52:24 +0200 Subject: [PATCH 10/68] Make `DBObserver` to do work on the background queue. --- Nynja/DBObserver.swift | 244 +++++++++++++++++++++-------------------- 1 file changed, 126 insertions(+), 118 deletions(-) diff --git a/Nynja/DBObserver.swift b/Nynja/DBObserver.swift index e5ceec7a7..24c9ee755 100644 --- a/Nynja/DBObserver.swift +++ b/Nynja/DBObserver.swift @@ -14,6 +14,8 @@ class DBObserver: StorageObserver, TransactionObserver { private init() {} + private var dispatchQueue = DispatchQueue(label: "com.nynja.mobile.communicator.db_observer.queue", qos: .userInitiated) + // MARK: - Properties var subscribers: [SubscribeType: [StorageSubscriberReference]] = [:] @@ -54,145 +56,151 @@ class DBObserver: StorageObserver, TransactionObserver { } func databaseDidChange(with event: DatabaseEvent) { - var temp = allChanges[event.tableName] ?? [] - temp.append(ChangeInfo(event: event)) - allChanges[event.tableName] = temp + dispatchQueue.async { + var temp = self.allChanges[event.tableName] ?? [] + temp.append(ChangeInfo(event: event)) + self.allChanges[event.tableName] = temp + } } func databaseWillCommit() throws { - allChanges.forEach { (tableName, changes) in - changes.forEach { info in - guard info.kind != .insert else { return } - info.oldValue = fetchValue(for: tableName, event: info.event) + dispatchQueue.async { + self.allChanges.forEach { (tableName, changes) in + changes.forEach { info in + guard info.kind != .insert else { return } + info.oldValue = self.fetchValue(for: tableName, event: info.event) + } } } } func databaseDidCommit(_ db: Database) { - allChanges.forEach { (tableName, changes) in - var storageChanges: [StorageChange] = [] - - switch tableName { - case JobTable.name: - changes.forEach { info in - let change = storageChange(from: info) - storageChanges.append(change) - - let job = change.entity as? Job - notify(with: [change], type: .job(job?.id)) - } + dispatchQueue.async { + self.allChanges.forEach { (tableName, changes) in + var storageChanges: [StorageChange] = [] - notify(with: storageChanges, type: .job(nil)) - case MessageTable.name: - func handle(changes: [ChangeInfo]) { + switch tableName { + case JobTable.name: changes.forEach { info in - let message = changedValue(from: info) as? Message + let change = self.storageChange(from: info) + storageChanges.append(change) - if let receiver = message?.p2pFeed?.opponentId { - notify(with: info.event, entity: message, type: .chat(receiver)) - } else if let roomId = message?.mucFeed?.name { - notify(with: info.event, entity: message, type: .chat(roomId)) - } - - notify(with: info.event, entity: message, type: .reply(message?.id)) + let job = change.entity as? Job + self.notify(with: [change], type: .job(job?.id)) } - } - - if changes.count == 2, - case let firstInfo = changes[0], - case let secondInfo = changes[1], - let firstMessage = changedValue(from: firstInfo) as? Message, - let secondMessage = changedValue(from: secondInfo) as? Message, - case let isFirstReply = firstMessage.isReply && firstMessage.linkedId == secondMessage.id, - case let isSecondReply = secondMessage.isReply && secondMessage.linkedId == firstMessage.id, - isFirstReply || isSecondReply { - handle(changes: [firstInfo]) - handle(changes: [secondInfo]) - } else if changes.count > 1, let info = changes.first, let message = changedValue(from: info) as? Message { - if let receiver = message.p2pFeed?.opponentId { - notify(with: [], type: .chat(receiver)) - } else if let roomId = message.mucFeed?.name { - notify(with: [], type: .chat(roomId)) + + self.notify(with: storageChanges, type: .job(nil)) + case MessageTable.name: + func handle(changes: [ChangeInfo]) { + changes.forEach { info in + let message = self.changedValue(from: info) as? Message + + if let receiver = message?.p2pFeed?.opponentId { + self.notify(with: info.event, entity: message, type: .chat(receiver)) + } else if let roomId = message?.mucFeed?.name { + self.notify(with: info.event, entity: message, type: .chat(roomId)) + } + + self.notify(with: info.event, entity: message, type: .reply(message?.id)) + } } - } else { - handle(changes: changes) - } - case ContactTable.name: - if changes.count == 1, let info = changes.first { - let contact = changedValue(from: info) as? DBContact - notify(with: info.event, entity: contact, type: .contact(nil)) - } else { - notify(with: [], type: .contact(nil)) - } - - changes.forEach { info in - let contact = changedValue(from: info) as? DBContact - notify(with: info.event, entity: contact, type: .contact(contact?.phoneId)) - } - case RoomTable.name: - changes.forEach { info in - let change = storageChange(from: info) - storageChanges.append(change) - let room = change.entity as? DBRoom - notify(with: [change], type: .room(room?.id)) - } - - notify(with: storageChanges, type: .room(nil)) - case MemberTable.name: - changes.forEach { info in - let member = changedValue(from: info) as? DBMember - notify(with: info.event, entity: member, type: .member((member?.feed as? DBMuc)?.name)) - } - case RosterTable.name: - changes.forEach { info in - let change = storageChange(from: info) - storageChanges.append(change) + if changes.count == 2, + case let firstInfo = changes[0], + case let secondInfo = changes[1], + let firstMessage = self.changedValue(from: firstInfo) as? Message, + let secondMessage = self.changedValue(from: secondInfo) as? Message, + case let isFirstReply = firstMessage.isReply && firstMessage.linkedId == secondMessage.id, + case let isSecondReply = secondMessage.isReply && secondMessage.linkedId == firstMessage.id, + isFirstReply || isSecondReply { + handle(changes: [firstInfo]) + handle(changes: [secondInfo]) + } else if changes.count > 1, let info = changes.first, let message = self.changedValue(from: info) as? Message { + if let receiver = message.p2pFeed?.opponentId { + self.notify(with: [], type: .chat(receiver)) + } else if let roomId = message.mucFeed?.name { + self.notify(with: [], type: .chat(roomId)) + } + } else { + handle(changes: changes) + } + case ContactTable.name: + if changes.count == 1, let info = changes.first { + let contact = self.changedValue(from: info) as? DBContact + self.notify(with: info.event, entity: contact, type: .contact(nil)) + } else { + self.notify(with: [], type: .contact(nil)) + } - let roster = changedValue(from: info) as? DBRoster - notify(with: [change], type: .roster(roster?.id)) - } - - notify(with: storageChanges, type: .roster(nil)) - case StarTable.name: - changes.forEach { info in - let change = storageChange(from: info) - storageChanges.append(change) + changes.forEach { info in + let contact = self.changedValue(from: info) as? DBContact + self.notify(with: info.event, entity: contact, type: .contact(contact?.phoneId)) + } + case RoomTable.name: + changes.forEach { info in + let change = self.storageChange(from: info) + storageChanges.append(change) + + let room = change.entity as? DBRoom + self.notify(with: [change], type: .room(room?.id)) + } - let star = changedValue(from: info) as? DBStar - notify(with: [change], type: .star(star?.clientId)) - } - - notify(with: storageChanges, type: .star(nil)) - case ProfileTable.name: - changes.forEach { info in - let profile = changedValue(from: info) as? DBProfile - notify(with: info.event, entity: profile, type: .profile) - } - case RecentStickerTable.name: - changes.forEach { info in - let change = storageChange(from: info) - storageChanges.append(change) + self.notify(with: storageChanges, type: .room(nil)) + case MemberTable.name: + changes.forEach { info in + let member = self.changedValue(from: info) as? DBMember + self.notify(with: info.event, entity: member, type: .member((member?.feed as? DBMuc)?.name)) + } + case RosterTable.name: + changes.forEach { info in + let change = self.storageChange(from: info) + storageChanges.append(change) + + let roster = self.changedValue(from: info) as? DBRoster + self.notify(with: [change], type: .roster(roster?.id)) + } - let recentSticker = changedValue(from: info) as? DBRecentSticker - notify(with: [change], type: .recentSticker(recentSticker?.id)) - } - notify(with: storageChanges, type: .recentSticker(nil)) - case StickerPackTable.name: - changes.forEach { info in - let change = storageChange(from: info) - storageChanges.append(change) + self.notify(with: storageChanges, type: .roster(nil)) + case StarTable.name: + changes.forEach { info in + let change = self.storageChange(from: info) + storageChanges.append(change) + + let star = self.changedValue(from: info) as? DBStar + self.notify(with: [change], type: .star(star?.clientId)) + } - let stickerPack = changedValue(from: info) as? StickerPack - notify(with: [change], type: .stickerPack(stickerPack?.id)) + self.notify(with: storageChanges, type: .star(nil)) + case ProfileTable.name: + changes.forEach { info in + let profile = self.changedValue(from: info) as? DBProfile + self.notify(with: info.event, entity: profile, type: .profile) + } + case RecentStickerTable.name: + changes.forEach { info in + let change = self.storageChange(from: info) + storageChanges.append(change) + + let recentSticker = self.changedValue(from: info) as? DBRecentSticker + self.notify(with: [change], type: .recentSticker(recentSticker?.id)) + } + self.notify(with: storageChanges, type: .recentSticker(nil)) + case StickerPackTable.name: + changes.forEach { info in + let change = self.storageChange(from: info) + storageChanges.append(change) + + let stickerPack = self.changedValue(from: info) as? StickerPack + self.notify(with: [change], type: .stickerPack(stickerPack?.id)) + } + self.notify(with: storageChanges, type: .stickerPack(nil)) + default: + break } - notify(with: storageChanges, type: .stickerPack(nil)) - default: - break } + + self.clear() } - - clear() } func databaseDidRollback(_ db: Database) { -- GitLab From d02ff3d752d30602767fe217623dd08574ee77ba Mon Sep 17 00:00:00 2001 From: Volodymyr Hryhoriev Date: Fri, 2 Nov 2018 17:26:10 +0200 Subject: [PATCH 11/68] Update models --- Nynja/DB/Models/DBContact.swift | 5 ----- Nynja/DB/Models/DBJob.swift | 2 +- Nynja/DB/Models/DBJobMessage.swift | 2 +- Nynja/DB/Models/DBMember.swift | 2 +- Nynja/DB/Models/DBRoom.swift | 2 +- Nynja/DB/Models/DBStar.swift | 2 +- Nynja/DB/Models/DBStarMessage.swift | 2 +- Nynja/DB/Models/DBStickerPack.swift | 2 +- 8 files changed, 7 insertions(+), 12 deletions(-) diff --git a/Nynja/DB/Models/DBContact.swift b/Nynja/DB/Models/DBContact.swift index f58a43005..d2ce29079 100644 --- a/Nynja/DB/Models/DBContact.swift +++ b/Nynja/DB/Models/DBContact.swift @@ -179,11 +179,6 @@ class DBContact: Record, DBModelProtocol { return try contacts(db, predicate: predicate) } - static func contacts(_ db: Database, ids: [String]) throws -> [DBContact] { - let phoneIdColumn = Column(ContactTable.Column.phoneId.title) - return try contacts(db, predicate: ids.contains(phoneIdColumn)) - } - private static func contacts(_ db: Database, predicate: SQLExpressible) throws -> [DBContact] { let contacts = try DBContact.filter(predicate).fetchAll(db) try contacts.forEach { try $0.construct(db) } diff --git a/Nynja/DB/Models/DBJob.swift b/Nynja/DB/Models/DBJob.swift index 80586f4a4..90c79a496 100644 --- a/Nynja/DB/Models/DBJob.swift +++ b/Nynja/DB/Models/DBJob.swift @@ -112,7 +112,7 @@ class DBJob: Record, DBModelProtocol { return try SQLRequest(sql).asRequest(of: DBJob.self).fetchAll(db) } - private func construct(_ db: Database) throws { + func construct(_ db: Database) throws { if let jobId = self.id { self.messages = try DBJobMessage.messages(db, jobId: jobId) self.features = try DBJob.requestFeature(jobId: String(describing: jobId)).fetchAll(db) diff --git a/Nynja/DB/Models/DBJobMessage.swift b/Nynja/DB/Models/DBJobMessage.swift index 705505d31..c779dc881 100644 --- a/Nynja/DB/Models/DBJobMessage.swift +++ b/Nynja/DB/Models/DBJobMessage.swift @@ -119,7 +119,7 @@ final class DBJobMessage: Record, DBModelProtocol { return messages } - private func construct(_ db: Database) throws { + func construct(_ db: Database) throws { if let id = self.id, case let targetId = String(id) { self.files = try DBDesc.descs(targetId: targetId, targetType: .job, db: db) } diff --git a/Nynja/DB/Models/DBMember.swift b/Nynja/DB/Models/DBMember.swift index 2b456cbcb..10cc75857 100644 --- a/Nynja/DB/Models/DBMember.swift +++ b/Nynja/DB/Models/DBMember.swift @@ -176,7 +176,7 @@ class DBMember: Record, DBModelProtocol { return member } - private func construct(_ db: Database) throws { + func construct(_ db: Database) throws { let memberId = "\(self.id)" self.features = (try? DBMember.requestFeature(targetId: memberId).fetchAll(db)) ?? [] self.services = (try? DBService.request(targetId: memberId, targetType: .member).fetchAll(db)) ?? [] diff --git a/Nynja/DB/Models/DBRoom.swift b/Nynja/DB/Models/DBRoom.swift index a41b3696d..5d048c8a4 100644 --- a/Nynja/DB/Models/DBRoom.swift +++ b/Nynja/DB/Models/DBRoom.swift @@ -230,7 +230,7 @@ class DBRoom: Record, DBModelProtocol { return rooms } - private func construct(_ db: Database) throws { + func construct(_ db: Database) throws { if let messageId = self.messageId { self.message = try DBMessage.message(db, localId: messageId) } diff --git a/Nynja/DB/Models/DBStar.swift b/Nynja/DB/Models/DBStar.swift index 6f21437a7..126eac0b5 100644 --- a/Nynja/DB/Models/DBStar.swift +++ b/Nynja/DB/Models/DBStar.swift @@ -108,7 +108,7 @@ final class DBStar: Record, DBModelProtocol { return stars } - private func construct(_ db: Database) throws { + func construct(_ db: Database) throws { if let messageId = messageID { message = try DBStarMessage.message(db, localId: messageId) } diff --git a/Nynja/DB/Models/DBStarMessage.swift b/Nynja/DB/Models/DBStarMessage.swift index 00e7da110..3ec6dfd44 100644 --- a/Nynja/DB/Models/DBStarMessage.swift +++ b/Nynja/DB/Models/DBStarMessage.swift @@ -235,7 +235,7 @@ final class DBStarMessage: Record, DBModelProtocol { return nil } - private func construct(_ db: Database) throws { + func construct(_ db: Database) throws { files = try DBDesc.descs(targetId: localId, targetType: .star, db: db) guard let feedId = feedId, let feedType = feedType, let type = FeedType(rawValue: feedType) else { diff --git a/Nynja/DB/Models/DBStickerPack.swift b/Nynja/DB/Models/DBStickerPack.swift index a0818e949..393c303bb 100644 --- a/Nynja/DB/Models/DBStickerPack.swift +++ b/Nynja/DB/Models/DBStickerPack.swift @@ -124,7 +124,7 @@ final class DBStickerPack: Record, DBModelProtocol { // MARK: Foreign Constraints - private func construct(_ db: Database) throws { + func construct(_ db: Database) throws { stickers = try DBStickerPack.stickers(from: db, targetId: String(id)) } -- GitLab From d9dba96bed8cd2923c0a7f623b08728fe2b9d4f9 Mon Sep 17 00:00:00 2001 From: Volodymyr Hryhoriev Date: Fri, 2 Nov 2018 18:09:31 +0200 Subject: [PATCH 12/68] temo --- Nynja.xcodeproj/project.pbxproj | 16 +++++ Nynja/ContactDAO.swift | 23 ++++--- .../Extensions/TableDefinitionExtension.swift | 5 ++ Nynja/DB/Models/Base/DBModelProtocol.swift | 52 +++++++++++++++ Nynja/DB/Models/DBContact.swift | 6 +- Nynja/DB/Models/DBRoom.swift | 6 +- Nynja/DB/Tables/ContactTable.swift | 9 +++ .../HomeDataProvider/HomeDataProvider.swift | 20 ++++++ .../HomeDataProviderImpl.swift | 63 +++++++++++++++++++ 9 files changed, 186 insertions(+), 14 deletions(-) create mode 100644 Nynja/Modules/Profile/Interactor/HomeDataProvider/HomeDataProvider.swift create mode 100644 Nynja/Modules/Profile/Interactor/HomeDataProvider/HomeDataProviderImpl.swift diff --git a/Nynja.xcodeproj/project.pbxproj b/Nynja.xcodeproj/project.pbxproj index 11f215ba3..e3ac74542 100644 --- a/Nynja.xcodeproj/project.pbxproj +++ b/Nynja.xcodeproj/project.pbxproj @@ -684,6 +684,8 @@ 4BD53BF4202C8BCA00569C1A /* AVURLAsset+Duration.swift in Sources */ = {isa = PBXBuildFile; fileRef = E77FBDDC1FFE828400BDB255 /* AVURLAsset+Duration.swift */; }; 4BDC7E61203492CA00BCD381 /* TopSwipable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BDC7E60203492CA00BCD381 /* TopSwipable.swift */; }; 4BDC7E63203494C000BCD381 /* ScheduleButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BDC7E62203494C000BCD381 /* ScheduleButton.swift */; }; + 4BDDAAE9218CA28300F775A7 /* HomeDataProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BDDAAE8218CA28300F775A7 /* HomeDataProvider.swift */; }; + 4BDDAAEB218CA2C700F775A7 /* HomeDataProviderImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BDDAAEA218CA2C700F775A7 /* HomeDataProviderImpl.swift */; }; 4BE2C5D92142EAC500A73DD9 /* AudioPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BE2C5D02142EAC500A73DD9 /* AudioPlayer.swift */; }; 4BE2C5DA2142EAC500A73DD9 /* AudioSessionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BE2C5D22142EAC500A73DD9 /* AudioSessionManager.swift */; }; 4BE2C5DB2142EAC500A73DD9 /* AudioRecorder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BE2C5D42142EAC500A73DD9 /* AudioRecorder.swift */; }; @@ -2945,6 +2947,8 @@ 4BB0EFBA2151347900704136 /* AlertManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlertManager.swift; sourceTree = ""; }; 4BDC7E60203492CA00BCD381 /* TopSwipable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TopSwipable.swift; sourceTree = ""; }; 4BDC7E62203494C000BCD381 /* ScheduleButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScheduleButton.swift; sourceTree = ""; }; + 4BDDAAE8218CA28300F775A7 /* HomeDataProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeDataProvider.swift; sourceTree = ""; }; + 4BDDAAEA218CA2C700F775A7 /* HomeDataProviderImpl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeDataProviderImpl.swift; sourceTree = ""; }; 4BE2C5D02142EAC500A73DD9 /* AudioPlayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AudioPlayer.swift; sourceTree = ""; }; 4BE2C5D22142EAC500A73DD9 /* AudioSessionManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AudioSessionManager.swift; sourceTree = ""; }; 4BE2C5D42142EAC500A73DD9 /* AudioRecorder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AudioRecorder.swift; sourceTree = ""; }; @@ -7075,6 +7079,15 @@ path = AlertImageViewController; sourceTree = ""; }; + 4BDDAAE7218CA27200F775A7 /* HomeDataProvider */ = { + isa = PBXGroup; + children = ( + 4BDDAAE8218CA28300F775A7 /* HomeDataProvider.swift */, + 4BDDAAEA218CA2C700F775A7 /* HomeDataProviderImpl.swift */, + ); + path = HomeDataProvider; + sourceTree = ""; + }; 4BE2C5CE2142EAC500A73DD9 /* Audio */ = { isa = PBXGroup; children = ( @@ -7210,6 +7223,7 @@ 50D9FF79FEBFA4AFE9A2791A /* Interactor */ = { isa = PBXGroup; children = ( + 4BDDAAE7218CA27200F775A7 /* HomeDataProvider */, EFF6A30BDE89BF7887B67DA0 /* ProfileInteractor.swift */, ); path = Interactor; @@ -15707,6 +15721,7 @@ A45F112220B4218D00F45004 /* MessageVideoView.swift in Sources */, A42D51A8206A361400EEB952 /* Index.swift in Sources */, 859B862D204820DC003272B2 /* ThemePickerViewController.swift in Sources */, + 4BDDAAE9218CA28300F775A7 /* HomeDataProvider.swift in Sources */, 855AC533208E441500DC2335 /* StickersInputViewController.swift in Sources */, 00F7B34A202B350A00E443E1 /* TimeZoneManager.swift in Sources */, A94B03A70E016BDA759B0703 /* EditProfileViewController.swift in Sources */, @@ -16260,6 +16275,7 @@ 85D77807211D9B980044E72F /* ScrollPosition.swift in Sources */, FEA655F22167777E00B44029 /* PaymentWireFrame.swift in Sources */, DF55CCC682DAB5392F2A763D /* FavoritesPresenter.swift in Sources */, + 4BDDAAEB218CA2C700F775A7 /* HomeDataProviderImpl.swift in Sources */, 002FFCE1202DE8BE003CCB26 /* Date+Extension.swift in Sources */, B723C626204D86AF00884FFD /* SettingsDataAndStorageViewController.swift in Sources */, A4494C7E2080F26600223B06 /* ChannelsItemsFactory.swift in Sources */, diff --git a/Nynja/ContactDAO.swift b/Nynja/ContactDAO.swift index 7343a18f1..96e9440c1 100644 --- a/Nynja/ContactDAO.swift +++ b/Nynja/ContactDAO.swift @@ -66,10 +66,14 @@ class ContactDAO: ContactDAOProtocol { // MARK: -- Contacts static func fetchContacts() -> [Contact] { - guard let rosterId = StorageService.sharedInstance.rosterId else { return [] } + guard let rosterId = StorageService.sharedInstance.rosterId else { + return [] + } let contacts = dbManager.fetch { db -> [DBContact] in - return try DBContact.contacts(from: db, rosterId: rosterId) + return try DBContact + .filter(Column.rosterId == rosterId) + .fetchAll(from: db) } return contacts.map { Contact(contact: $0) } @@ -108,16 +112,11 @@ class ContactDAO: ContactDAOProtocol { static func fetchContacts(with statuses: [Contact.Status]) -> [Contact] { let statuses = statuses.map { $0.rawValue } - let statusColumn = Column(ContactTable.Column.status.title) - - let contacts = dbManager.fetch { db -> [DBContact] in - // TODO: need to think - let contacts = try DBContact.filter(statuses.contains(statusColumn)).fetchAll(db) - try contacts.forEach { try $0.construct(db) } - return contacts - } - - return contacts.map { Contact(contact: $0) } + return dbManager.fetch { db -> [DBContact] in + return try DBContact + .filter(statuses.contains(Column.status)) + .fetchAll(from: db) + }.serverModels } // MARK: -- Reader diff --git a/Nynja/DB/Extensions/TableDefinitionExtension.swift b/Nynja/DB/Extensions/TableDefinitionExtension.swift index 7a82d6255..3bcb30cd2 100644 --- a/Nynja/DB/Extensions/TableDefinitionExtension.swift +++ b/Nynja/DB/Extensions/TableDefinitionExtension.swift @@ -14,4 +14,9 @@ extension TableDefinition { func column(_ desc: Describable, _ type: Database.ColumnType? = nil) -> ColumnDefinition { return column(desc.title, type) } + + @discardableResult + func column(_ column: Column, _ type: Database.ColumnType? = nil) -> ColumnDefinition { + return self.column(column.name, type) + } } diff --git a/Nynja/DB/Models/Base/DBModelProtocol.swift b/Nynja/DB/Models/Base/DBModelProtocol.swift index 10f019eb5..1b880d246 100644 --- a/Nynja/DB/Models/Base/DBModelProtocol.swift +++ b/Nynja/DB/Models/Base/DBModelProtocol.swift @@ -14,6 +14,8 @@ protocol DBModelProtocol: Persistable, RowConvertible { @discardableResult func deleteAggregate(_ db: Database) throws -> Bool + + func construct(_ db: Database) throws } extension DBModelProtocol { @@ -27,4 +29,54 @@ extension DBModelProtocol { try delete(db) return false } + + func construct(_ db: Database) throws {} +} + +extension Array where Element: DBModelProtocol { + + func construct(_ db: Database) throws { + try self.forEach { try $0.construct(db) } + } +} + + +// MARK: - Construct + +extension QueryInterfaceRequest where T: DBModelProtocol { + + enum ModelKind { + case plain + case full + } + + func fetchAll(kind: ModelKind = .full, from db: Database) throws -> [T] { + let models = try self.fetchAll(db) + if kind == .full { + try models.construct(db) + } + return models + } + + func fetchOne(kind: ModelKind = .full, from db: Database) throws -> T? { + let model = try self.fetchOne(db) + if kind == .full { + try model?.construct(db) + } + return model + } +} + +protocol ServerModelConvertible { + associatedtype ServerModel + var serverModel: ServerModel { get } +} + +protocol DBExtendedModelProtocol: DBModelProtocol, ServerModelConvertible {} + +extension Array where Element: ServerModelConvertible { + + var serverModels: [Element.ServerModel] { + return self.map { $0.serverModel } + } } diff --git a/Nynja/DB/Models/DBContact.swift b/Nynja/DB/Models/DBContact.swift index d2ce29079..4da6d897d 100644 --- a/Nynja/DB/Models/DBContact.swift +++ b/Nynja/DB/Models/DBContact.swift @@ -8,7 +8,7 @@ import GRDBCipher -class DBContact: Record, DBModelProtocol { +class DBContact: Record, DBExtendedModelProtocol { var phoneId: String var avatar: String? @@ -29,6 +29,10 @@ class DBContact: Record, DBModelProtocol { var features: [DBFeature] = [] var services: [DBService] = [] + var serverModel: Contact { + return Contact(contact: self) + } + init?(contact: Contact, rosterId: Int64?) { guard let phoneId = contact.phone_id else { return nil } diff --git a/Nynja/DB/Models/DBRoom.swift b/Nynja/DB/Models/DBRoom.swift index 5d048c8a4..e791375ba 100644 --- a/Nynja/DB/Models/DBRoom.swift +++ b/Nynja/DB/Models/DBRoom.swift @@ -8,7 +8,7 @@ import GRDBCipher -class DBRoom: Record, DBModelProtocol { +class DBRoom: Record, DBExtendedModelProtocol { var id: String var name: String @@ -40,6 +40,10 @@ class DBRoom: Record, DBModelProtocol { var files: [DBDesc] = [] var links: [DBLink] = [] + var serverModel: Room { + return Room(room: self) + } + init?(room: Room, rosterId: Int64?) { guard let id = room.id else { return nil } diff --git a/Nynja/DB/Tables/ContactTable.swift b/Nynja/DB/Tables/ContactTable.swift index 3ac2717c6..a9db6b8e1 100644 --- a/Nynja/DB/Tables/ContactTable.swift +++ b/Nynja/DB/Tables/ContactTable.swift @@ -52,3 +52,12 @@ extension ContactTable { case rosterId } } + + +extension Column { + static let phoneId = Column("phoneId") + static let created = Column("created") + static let unread = Column("unread") + static let status = Column("status") + static let rosterId = Column("rosterId") +} diff --git a/Nynja/Modules/Profile/Interactor/HomeDataProvider/HomeDataProvider.swift b/Nynja/Modules/Profile/Interactor/HomeDataProvider/HomeDataProvider.swift new file mode 100644 index 000000000..058a37e46 --- /dev/null +++ b/Nynja/Modules/Profile/Interactor/HomeDataProvider/HomeDataProvider.swift @@ -0,0 +1,20 @@ +// +// HomeDataProvider.swift +// Nynja +// +// Created by Volodymyr Hryhoriev on 11/2/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +struct HomeDataQuery { + let rosterId: String + let phoneId: String +} + +protocol HomeDataProvider { + var dbManager: DBManagerProtocol { get } + + func fetchChats(for query: HomeDataQuery, limit: Int) -> [Contact] + func fetchGroups(for query: HomeDataQuery, limit: Int) -> [Room] + func fetchHistory(for query: HomeDataQuery, limit: Int) -> [Contact] +} diff --git a/Nynja/Modules/Profile/Interactor/HomeDataProvider/HomeDataProviderImpl.swift b/Nynja/Modules/Profile/Interactor/HomeDataProvider/HomeDataProviderImpl.swift new file mode 100644 index 000000000..6ce6470ec --- /dev/null +++ b/Nynja/Modules/Profile/Interactor/HomeDataProvider/HomeDataProviderImpl.swift @@ -0,0 +1,63 @@ +// +// HomeDataProviderImpl.swift +// Nynja +// +// Created by Volodymyr Hryhoriev on 11/2/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import GRDBCipher + +class HomeDataProviderImpl: HomeDataProvider, InitializeInjectable { + typealias Dependencies = DBManagerProtocol + + let dbManager: DBManagerProtocol + + required init(dependencies: Dependencies) { + dbManager = dependencies + } + + // MARK: - HomeDataProvider + + func fetchChats(for query: HomeDataQuery, limit: Int) -> [Contact] { + let statuses: [Contact.Status] = [.friend, .banned, .ban] + let statusStrings = statuses.map { $0.rawValue } + + return dbManager.fetch { db in + return try DBContact + .filter(Column.rosterId == query.rosterId) + .filter(Column.unread > 0) + .filter(statusStrings.contains(Column.status)) + .order(Column.created.desc) + .limit(limit) + .fetchAll(from: db) + }.serverModels + } + + func fetchGroups(for query: HomeDataQuery, limit: Int) -> [Room] { // TOOD: the same + return dbManager.fetch { db in + return try DBRoom + .filter(Column.rosterId == query.rosterId) + .filter(Column.unread > 0) + .order(Column.created.desc) + .limit(limit) + .fetchAll(from: db) + }.serverModels + } + + func fetchHistory(for query: HomeDataQuery, limit: Int) -> [Contact] { // SHOULD work + let statuses: [Contact.Status] = [.request, .ignore] + let statusStrings = statuses.map { $0.rawValue } + + return dbManager.fetch { db in + return try DBContact + .filter(Column.rosterId == query.rosterId) + .filter(Column.phoneId != query.phoneId) + .filter(Column.unread > 0) + .filter(!statusStrings.contains(Column.status)) + .order(Column.created.desc) + .limit(limit) + .fetchAll(from: db) + }.serverModels + } +} -- GitLab From 92249fc6dd3a94b41f5c0bf8b75cda04fcc478b6 Mon Sep 17 00:00:00 2001 From: Volodymyr Hryhoriev Date: Sun, 4 Nov 2018 20:51:16 +0200 Subject: [PATCH 13/68] Create request with limit and offset for chats and groups. --- Nynja.xcodeproj/project.pbxproj | 8 +++ Nynja/CPFetchArgs.swift | 21 +++++++ Nynja/ContactDAO.swift | 8 +++ Nynja/ContactDAOProtocol.swift | 12 +++- Nynja/ConversationsProvider.swift | 25 +++++--- Nynja/ConversationsProviding.swift | 6 +- Nynja/DB/Models/Base/DBModelProtocol.swift | 16 +++--- Nynja/DB/Models/DBContact.swift | 36 ++++++++++++ Nynja/DB/Models/DBRoom.swift | 57 +++++++++++++++++++ Nynja/DB/Tables/Base/SharedColumn.swift | 20 +++++++ Nynja/DB/Tables/ContactTable.swift | 9 --- .../Interactor/ChatsListInteractor.swift | 15 ++++- .../Interactor/GroupsListInteractor.swift | 17 +++++- .../View/MainViewController+Recents.swift | 17 +++++- .../HomeDataProvider/HomeDataProvider.swift | 14 ++--- .../HomeDataProviderImpl.swift | 54 +++++++++--------- .../Interactor/ProfileInteractor.swift | 28 +++++---- .../Profile/WireFrame/ProfileWireframe.swift | 30 +++++----- Nynja/RoomDAO.swift | 30 ++++++---- Nynja/RoomDAOProtocol.swift | 14 ++++- .../ServiceFactory/ServiceFactory.swift | 13 +++++ 21 files changed, 340 insertions(+), 110 deletions(-) create mode 100644 Nynja/CPFetchArgs.swift create mode 100644 Nynja/DB/Tables/Base/SharedColumn.swift diff --git a/Nynja.xcodeproj/project.pbxproj b/Nynja.xcodeproj/project.pbxproj index e3ac74542..e06331598 100644 --- a/Nynja.xcodeproj/project.pbxproj +++ b/Nynja.xcodeproj/project.pbxproj @@ -574,6 +574,8 @@ 4B1D7E112029FF5000703228 /* Array+WheelItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B1D7E102029FF5000703228 /* Array+WheelItemModel.swift */; }; 4B1D7E14202A0A0200703228 /* GroupMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B1D7E13202A0A0200703228 /* GroupMode.swift */; }; 4B1F1230203C8DDE00D61D21 /* JobTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B1F122F203C8DDE00D61D21 /* JobTable.swift */; }; + 4B2B1558218F5423000E4916 /* SharedColumn.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B2B1557218F5423000E4916 /* SharedColumn.swift */; }; + 4B2B155A218F6982000E4916 /* CPFetchArgs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B2B1559218F6982000E4916 /* CPFetchArgs.swift */; }; 4B2D063A202DDA2000010A0C /* BackSwipable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B2D0639202DDA2000010A0C /* BackSwipable.swift */; }; 4B2D063C202E1A1500010A0C /* ContactsExpandedItemsFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B2D063B202E1A1500010A0C /* ContactsExpandedItemsFactory.swift */; }; 4B348FD7216366A900CCB0E3 /* LogServiceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BF090B821635B4700DCCA5C /* LogServiceProtocol.swift */; }; @@ -2858,6 +2860,8 @@ 4B1D7E102029FF5000703228 /* Array+WheelItemModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Array+WheelItemModel.swift"; sourceTree = ""; }; 4B1D7E13202A0A0200703228 /* GroupMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupMode.swift; sourceTree = ""; }; 4B1F122F203C8DDE00D61D21 /* JobTable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JobTable.swift; sourceTree = ""; }; + 4B2B1557218F5423000E4916 /* SharedColumn.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedColumn.swift; sourceTree = ""; }; + 4B2B1559218F6982000E4916 /* CPFetchArgs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CPFetchArgs.swift; sourceTree = ""; }; 4B2D0639202DDA2000010A0C /* BackSwipable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackSwipable.swift; sourceTree = ""; }; 4B2D063B202E1A1500010A0C /* ContactsExpandedItemsFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactsExpandedItemsFactory.swift; sourceTree = ""; }; 4B348FF12163808300CCB0E3 /* DeletedIndexesCalculatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeletedIndexesCalculatorTests.swift; sourceTree = ""; }; @@ -9706,6 +9710,7 @@ children = ( A411D95920AC39C6009D107C /* ConversationsProviding.swift */, A411D95B20AC3A5A009D107C /* ConversationsProvider.swift */, + 4B2B1559218F6982000E4916 /* CPFetchArgs.swift */, ); name = ConversationsProvider; sourceTree = ""; @@ -12277,6 +12282,7 @@ E7417E981FBED91100E5C124 /* Table.swift */, 85B0013121270DEC000C89FE /* TableOrder.swift */, E709383E1FBEE41D006CCDC6 /* Describable.swift */, + 4B2B1557218F5423000E4916 /* SharedColumn.swift */, ); path = Base; sourceTree = ""; @@ -16037,6 +16043,7 @@ 8502DB542061030100613C8C /* WheelPositionPickerViewController.swift in Sources */, 4B752B612163A5C900E852B9 /* DescExtension.swift in Sources */, A45F116120B422AF00F45004 /* Message+System.swift in Sources */, + 4B2B155A218F6982000E4916 /* CPFetchArgs.swift in Sources */, 4B06D30C2028A25D003B275B /* HomeItemsFactory.swift in Sources */, 266AE8C3203496B60096A12C /* AsyncOperation.swift in Sources */, FEA655DE2167777E00B44029 /* WalletDetailsInteractor.swift in Sources */, @@ -16267,6 +16274,7 @@ 4B1D7E092029D86600703228 /* CreateGroupItemsFactory.swift in Sources */, 85052E5720D1A90D00BCC386 /* StickerImagePreviewView.swift in Sources */, 8580BACC20BD984500239D9D /* MessageEditInfo.swift in Sources */, + 4B2B1558218F5423000E4916 /* SharedColumn.swift in Sources */, 850FC5FA2032F64100832D87 /* ForwardSelectorWireFrame.swift in Sources */, 3AA4E6ACDBCB060172A7A279 /* FavoritesProtocols.swift in Sources */, 9B81AD92215A5EEA00993A8C /* ActiveSpeakerView.swift in Sources */, diff --git a/Nynja/CPFetchArgs.swift b/Nynja/CPFetchArgs.swift new file mode 100644 index 000000000..8727e8cae --- /dev/null +++ b/Nynja/CPFetchArgs.swift @@ -0,0 +1,21 @@ +// +// CPFetchArgs.swift +// Nynja +// +// Created by Volodymyr Hryhoriev on 11/4/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + + +struct CPFetchArgs { + + let rosterId: Int64 + let limit: Int? + let offset: Int? + + init(rosterId: Int64, limit: Int? = nil, offset: Int? = nil) { + self.rosterId = rosterId + self.limit = limit + self.offset = offset + } +} diff --git a/Nynja/ContactDAO.swift b/Nynja/ContactDAO.swift index 96e9440c1..5c713f42b 100644 --- a/Nynja/ContactDAO.swift +++ b/Nynja/ContactDAO.swift @@ -119,6 +119,14 @@ class ContactDAO: ContactDAOProtocol { }.serverModels } + static func fetchChats(rosterId: Int64, limit: Int? = nil, offset: Int? = nil) -> [Contact] { + return dbManager.fetch { db in + return try DBContact + .request(rosterId: rosterId, limit: limit, offset: offset) + .fetchAll(from: db) + }.serverModels + } + // MARK: -- Reader static func fetchReader(for phoneId: String, kind: ReaderKind) -> Int64? { let readersString = dbManager.fetch{ db in diff --git a/Nynja/ContactDAOProtocol.swift b/Nynja/ContactDAOProtocol.swift index 9d2b23e7a..0e1cda1f5 100644 --- a/Nynja/ContactDAOProtocol.swift +++ b/Nynja/ContactDAOProtocol.swift @@ -10,6 +10,7 @@ protocol ContactDAOProtocol: DAOProtocol { // MARK: - Fetch // MARK: -- Contact + static var currentContact: Contact? { get } static func fetchContact(by id: String) -> DBContact? @@ -18,19 +19,28 @@ protocol ContactDAOProtocol: DAOProtocol { static func findContactBy(phoneId: String) -> Contact? static func findContactBy(username: String) -> Contact? + // MARK: -- Contacts + static func fetchContacts() -> [Contact] static func fetchContactWithoutSelf() -> [Contact] static func fetchContacts(with phoneIds: [String], isExcluded: Bool) -> [Contact] static func fetchContacts(with statuses: [Contact.Status]) -> [Contact] + static func fetchChats(rosterId: Int64, limit: Int?, offset: Int?) -> [Contact] + + // MARK: -- Reader + static func fetchReader(for phoneId: String, kind: ReaderKind) -> Int64? + // MARK: - Update + static func updateColumns(_ columns: Set, contact: Contact) + // MARK: -- Fields - static func updateReader(_ reader: Int64, phoneId: String, kind: ReaderKind) + static func updateReader(_ reader: Int64, phoneId: String, kind: ReaderKind) } diff --git a/Nynja/ConversationsProvider.swift b/Nynja/ConversationsProvider.swift index c7b497524..e41832b32 100644 --- a/Nynja/ConversationsProvider.swift +++ b/Nynja/ConversationsProvider.swift @@ -8,22 +8,29 @@ class ConversationsProvider: ConversationsProviding { - // MARK: - Chats - func fetchChats() -> [Contact] { - return ContactDAO - .fetchContacts(with: [.friend, .banned, .ban]) - .sorted(by: comparator) + func fetchChats(with args: CPFetchArgs) -> [Contact] { + return ContactDAO.fetchChats( + rosterId: args.rosterId, + limit: args.limit, + offset: args.offset) } // MARK: - Groups - func fetchGroups() -> [Room] { - return RoomDAO - .fetchRooms(kind: .group) - .sorted(by: comparator) + func fetchGroups(with args: CPFetchArgs) -> [Room] { + let groupRequestArgs = makeGroupsRequestArgs(from: args, kind: .group) + return RoomDAO.fetchRooms(with: groupRequestArgs) + } + + private func makeGroupsRequestArgs(from args: CPFetchArgs, kind: Room.Kind) -> DBRoom.RequestArgs { + return .init( + rosterId: args.rosterId, + type: kind.rawValue, + limit: args.limit, + offset: args.offset) } diff --git a/Nynja/ConversationsProviding.swift b/Nynja/ConversationsProviding.swift index a4f130a74..70699b47b 100644 --- a/Nynja/ConversationsProviding.swift +++ b/Nynja/ConversationsProviding.swift @@ -6,10 +6,12 @@ // Copyright © 2018 TecSynt Solutions. All rights reserved. // + protocol ConversationsProviding { - func fetchChats() -> [Contact] - func fetchGroups() -> [Room] + func fetchChats(with args: CPFetchArgs) -> [Contact] + + func fetchGroups(with args: CPFetchArgs) -> [Room] func fetchChannels() -> [Room] func fetchMyChannels() -> [Room] diff --git a/Nynja/DB/Models/Base/DBModelProtocol.swift b/Nynja/DB/Models/Base/DBModelProtocol.swift index 1b880d246..a5ca9cdf7 100644 --- a/Nynja/DB/Models/Base/DBModelProtocol.swift +++ b/Nynja/DB/Models/Base/DBModelProtocol.swift @@ -43,14 +43,16 @@ extension Array where Element: DBModelProtocol { // MARK: - Construct -extension QueryInterfaceRequest where T: DBModelProtocol { +enum DBModelFetchingKind { + case plain + case full +} - enum ModelKind { - case plain - case full - } +extension TypedRequest where RowDecoder: DBModelProtocol { + + - func fetchAll(kind: ModelKind = .full, from db: Database) throws -> [T] { + func fetchAll(kind: DBModelFetchingKind = .full, from db: Database) throws -> [RowDecoder] { let models = try self.fetchAll(db) if kind == .full { try models.construct(db) @@ -58,7 +60,7 @@ extension QueryInterfaceRequest where T: DBModelProtocol { return models } - func fetchOne(kind: ModelKind = .full, from db: Database) throws -> T? { + func fetchOne(kind: DBModelFetchingKind = .full, from db: Database) throws -> RowDecoder? { let model = try self.fetchOne(db) if kind == .full { try model?.construct(db) diff --git a/Nynja/DB/Models/DBContact.swift b/Nynja/DB/Models/DBContact.swift index 4da6d897d..daf5b6cd0 100644 --- a/Nynja/DB/Models/DBContact.swift +++ b/Nynja/DB/Models/DBContact.swift @@ -199,6 +199,7 @@ class DBContact: Record, DBExtendedModelProtocol { } // MARK: - Request + static private func request(targetId: String) -> QueryInterfaceRequest { return DBFeature.request(targetId: targetId, targetType: DBFeature.TargetType.contact) } @@ -206,5 +207,40 @@ class DBContact: Record, DBExtendedModelProtocol { static func request(phoneId: String) -> QueryInterfaceRequest { return DBContact.filter(Column(ContactTable.Column.phoneId.title) == phoneId) } + + static func request(rosterId: Int64, limit: Int?, offset: Int?) -> AnyTypedRequest { + let contactTable = ContactTable.name + let messageTable = MessageTable.name + + let messageIdColumn = "\(contactTable).\(Column.messageId.name)" + let localIdColumn = "\(messageTable).\(Column.localId.name)" + + let rosterIdColumn = "\(contactTable).\(Column.rosterId.name)" + let unreadColumn = "\(contactTable).\(Column.unread.name)" + let statusColumn = "\(contactTable).\(Column.status.name)" + + let createdColumn = "\(messageTable).\(Column.created.name)" + + var sql = """ + select \(contactTable).* + from \(contactTable) left join \(messageTable) + on \(messageIdColumn) == \(localIdColumn) + where \(rosterIdColumn) == \(rosterId) + and \(unreadColumn) > 0 + and \(statusColumn) in ('friend', 'ban', 'banned') + order by \(createdColumn) desc + limit \(limit) offset \(offset) + """ + + if let limit = limit { + sql.append(" limit \(limit)") + } + + if let offset = offset { + sql.append(" offset \(offset)") + } + + return SQLRequest(sql).asRequest(of: DBContact.self) + } } diff --git a/Nynja/DB/Models/DBRoom.swift b/Nynja/DB/Models/DBRoom.swift index e791375ba..3e3b0f2f6 100644 --- a/Nynja/DB/Models/DBRoom.swift +++ b/Nynja/DB/Models/DBRoom.swift @@ -285,7 +285,9 @@ class DBRoom: Record, DBExtendedModelProtocol { return sql } + // MARK: - Make Requests + static private func requestFeature(targetId: String) -> QueryInterfaceRequest { return DBFeature.request(targetId: targetId, targetType: .room) } @@ -293,4 +295,59 @@ class DBRoom: Record, DBExtendedModelProtocol { static private func descs(targetId: String, db: Database) throws -> [DBDesc] { return try DBDesc.descs(targetId: targetId, targetType: .room, db: db) } + + static func request(with args: RequestArgs) -> AnyTypedRequest { + + let roomTable = RoomTable.name + let messageTable = MessageTable.name + + let messageIdColumn = "\(roomTable).\(Column.messageId.name)" + let localIdColumn = "\(messageTable).\(Column.localId.name)" + + let rosterIdColumn = "\(roomTable).\(Column.rosterId.name)" + let typeColumn = "\(roomTable).\(Column.type.name)" + let unreadColumn = "\(roomTable).\(Column.unread.name)" + + let createdColumn = "\(messageTable).\(Column.created.name)" + + var sql = """ + select \(roomTable).* + from \(roomTable) left join \(messageTable) + on \(messageIdColumn) == \(localIdColumn) + where \(rosterIdColumn) == \(args.rosterId) + and \(typeColumn) == '\(args.type)' + and \(unreadColumn) > 0 + order by \(createdColumn) desc + """ + + if let limit = args.limit { + sql.append(" limit \(limit)") + } + + if let offset = args.offset { + sql.append(" offset \(offset)") + } + + return SQLRequest(sql).asRequest(of: DBRoom.self) + } +} + + +// MARK: - + +extension DBRoom { + + struct RequestArgs { + let rosterId: Int64 + let type: String + let limit: Int? + let offset: Int? + + init(rosterId: Int64, type: String, limit: Int? = nil, offset: Int? = nil) { + self.rosterId = rosterId + self.type = type + self.limit = limit + self.offset = offset + } + } } diff --git a/Nynja/DB/Tables/Base/SharedColumn.swift b/Nynja/DB/Tables/Base/SharedColumn.swift new file mode 100644 index 000000000..253f5a171 --- /dev/null +++ b/Nynja/DB/Tables/Base/SharedColumn.swift @@ -0,0 +1,20 @@ +// +// Column.swift +// Nynja +// +// Created by Volodymyr Hryhoriev on 11/4/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import GRDBCipher + +extension Column { + static let phoneId = Column("phoneId") + static let created = Column("created") + static let unread = Column("unread") + static let status = Column("status") + static let messageId = Column("messageId") + static let rosterId = Column("rosterId") + static let localId = Column("localId") + static let type = Column("type") +} diff --git a/Nynja/DB/Tables/ContactTable.swift b/Nynja/DB/Tables/ContactTable.swift index a9db6b8e1..3ac2717c6 100644 --- a/Nynja/DB/Tables/ContactTable.swift +++ b/Nynja/DB/Tables/ContactTable.swift @@ -52,12 +52,3 @@ extension ContactTable { case rosterId } } - - -extension Column { - static let phoneId = Column("phoneId") - static let created = Column("created") - static let unread = Column("unread") - static let status = Column("status") - static let rosterId = Column("rosterId") -} diff --git a/Nynja/Modules/ChatsList/Interactor/ChatsListInteractor.swift b/Nynja/Modules/ChatsList/Interactor/ChatsListInteractor.swift index 02aa17311..3a511cb6f 100644 --- a/Nynja/Modules/ChatsList/Interactor/ChatsListInteractor.swift +++ b/Nynja/Modules/ChatsList/Interactor/ChatsListInteractor.swift @@ -16,6 +16,9 @@ class ChatsListInteractor: BaseInteractor, ChatsListInteractorInputProtocol, Ini private var chats: [Contact] = [] private var searchText: String = "" + private var rosterId: Int64? { + return storageService.rosterId + } // MARK: - InitializeInjectable @@ -61,7 +64,11 @@ class ChatsListInteractor: BaseInteractor, ChatsListInteractorInputProtocol, Ini let contact = Contact(contact: dbContact) chats = updateChatsList(with: contact) } else { - chats = conversationsProvider.fetchChats() + guard let args = rosterId.map({ CPFetchArgs(rosterId: $0) }) else { + return + } + + chats = conversationsProvider.fetchChats(with: args) } applyFilter(with: searchText) @@ -71,7 +78,11 @@ class ChatsListInteractor: BaseInteractor, ChatsListInteractorInputProtocol, Ini //MARK: - Private private func fetchChats() { - chats = conversationsProvider.fetchChats() + guard let args = rosterId.map({ CPFetchArgs(rosterId: $0) }) else { + return + } + + chats = conversationsProvider.fetchChats(with: args) presenter.didFetch(chats: chats) } diff --git a/Nynja/Modules/GroupsList/Interactor/GroupsListInteractor.swift b/Nynja/Modules/GroupsList/Interactor/GroupsListInteractor.swift index c16ee18b5..781d60b80 100644 --- a/Nynja/Modules/GroupsList/Interactor/GroupsListInteractor.swift +++ b/Nynja/Modules/GroupsList/Interactor/GroupsListInteractor.swift @@ -16,6 +16,9 @@ class GroupsListInteractor: BaseInteractor, GroupsListInteractorInputProtocol, I private var chats: [Room] = [] private var searchText: String = "" + private var rosterId: Int64? { + return storageService.rosterId + } // MARK: - InitializeInjectable @@ -67,7 +70,11 @@ class GroupsListInteractor: BaseInteractor, GroupsListInteractorInputProtocol, I applyFilter(with: searchText) } } else { - chats = conversationsProvider.fetchGroups() + guard let args = rosterId.map({ CPFetchArgs(rosterId: $0) }) else { + return + } + + chats = conversationsProvider.fetchGroups(with: args) applyFilter(with: searchText) } } @@ -75,8 +82,12 @@ class GroupsListInteractor: BaseInteractor, GroupsListInteractorInputProtocol, I //MARK: - Private - private func fetchGroups(_ filter: String? = nil) { - chats = conversationsProvider.fetchGroups() + private func fetchGroups() { + guard let args = rosterId.map({ CPFetchArgs(rosterId: $0) }) else { + return + } + + chats = conversationsProvider.fetchGroups(with: args) presenter.didFetch(groups: chats) } diff --git a/Nynja/Modules/Main/View/MainViewController+Recents.swift b/Nynja/Modules/Main/View/MainViewController+Recents.swift index 8963fb3d5..2a1e68370 100644 --- a/Nynja/Modules/Main/View/MainViewController+Recents.swift +++ b/Nynja/Modules/Main/View/MainViewController+Recents.swift @@ -43,11 +43,24 @@ extension MainViewController { // MARK: - Chats func showRecentP2PChats(indexPath: IndexPath?) { - showRecentConversation(indexPath: indexPath, conversations: ConversationsProvider().fetchChats()) + let conversations = fetchConversation( + using: ConversationsProvider().fetchChats(with:)) + showRecentConversation(indexPath: indexPath, conversations: conversations) } func showRecentGroupChats(indexPath: IndexPath?) { - showRecentConversation(indexPath: indexPath, conversations: ConversationsProvider().fetchGroups()) + let conversations = fetchConversation( + using: ConversationsProvider().fetchGroups(with:)) + showRecentConversation(indexPath: indexPath, conversations: conversations) + } + + private func fetchConversation(using fetcher: (CPFetchArgs) -> [ChatModel]) -> [ChatModel] { + guard let rosterId = StorageService.sharedInstance.rosterId else { + return [] + } + + let args = CPFetchArgs(rosterId: rosterId) + return fetcher(args) } func showRecentChannels(indexPath: IndexPath?) { diff --git a/Nynja/Modules/Profile/Interactor/HomeDataProvider/HomeDataProvider.swift b/Nynja/Modules/Profile/Interactor/HomeDataProvider/HomeDataProvider.swift index 058a37e46..f8a09da31 100644 --- a/Nynja/Modules/Profile/Interactor/HomeDataProvider/HomeDataProvider.swift +++ b/Nynja/Modules/Profile/Interactor/HomeDataProvider/HomeDataProvider.swift @@ -6,15 +6,13 @@ // Copyright © 2018 TecSynt Solutions. All rights reserved. // -struct HomeDataQuery { - let rosterId: String - let phoneId: String -} - protocol HomeDataProvider { var dbManager: DBManagerProtocol { get } + var conversationsProvider: ConversationsProviding { get } + + var limit: Int { get } - func fetchChats(for query: HomeDataQuery, limit: Int) -> [Contact] - func fetchGroups(for query: HomeDataQuery, limit: Int) -> [Room] - func fetchHistory(for query: HomeDataQuery, limit: Int) -> [Contact] + func fetchChats(rosterId: Int64) -> [Contact] + func fetchGroups(rosterId: Int64) -> [Room] + func fetchHistory(rosterId: Int64, phoneId: String) -> [Contact] } diff --git a/Nynja/Modules/Profile/Interactor/HomeDataProvider/HomeDataProviderImpl.swift b/Nynja/Modules/Profile/Interactor/HomeDataProvider/HomeDataProviderImpl.swift index 6ce6470ec..a92e9a349 100644 --- a/Nynja/Modules/Profile/Interactor/HomeDataProvider/HomeDataProviderImpl.swift +++ b/Nynja/Modules/Profile/Interactor/HomeDataProvider/HomeDataProviderImpl.swift @@ -9,50 +9,52 @@ import GRDBCipher class HomeDataProviderImpl: HomeDataProvider, InitializeInjectable { - typealias Dependencies = DBManagerProtocol + typealias Dependencies = (services: Services, fields: Fields) + typealias Services = (dbManager: DBManagerProtocol, conversationsProvider: ConversationsProviding) + struct Fields { let limit: Int } let dbManager: DBManagerProtocol + let conversationsProvider: ConversationsProviding + let limit: Int required init(dependencies: Dependencies) { - dbManager = dependencies + dbManager = dependencies.services.dbManager + conversationsProvider = dependencies.services.conversationsProvider + limit = dependencies.fields.limit } // MARK: - HomeDataProvider - func fetchChats(for query: HomeDataQuery, limit: Int) -> [Contact] { - let statuses: [Contact.Status] = [.friend, .banned, .ban] - let statusStrings = statuses.map { $0.rawValue } + func fetchChats(rosterId: Int64) -> [Contact] { + let fetcher = conversationsProvider.fetchChats(with:) + return fetchConversations(rosterId: rosterId, fetcher: fetcher) + } + + func fetchGroups(rosterId: Int64) -> [Room] { + let fetcher = conversationsProvider.fetchGroups(with:) + return fetchConversations(rosterId: rosterId, fetcher: fetcher) + } + + private func fetchConversations( + rosterId: Int64, + fetcher: (CPFetchArgs) -> [T]) -> [T] { - return dbManager.fetch { db in - return try DBContact - .filter(Column.rosterId == query.rosterId) - .filter(Column.unread > 0) - .filter(statusStrings.contains(Column.status)) - .order(Column.created.desc) - .limit(limit) - .fetchAll(from: db) - }.serverModels + let args = makeCPFetchArgs(rosterId: rosterId) + return fetcher(args) } - func fetchGroups(for query: HomeDataQuery, limit: Int) -> [Room] { // TOOD: the same - return dbManager.fetch { db in - return try DBRoom - .filter(Column.rosterId == query.rosterId) - .filter(Column.unread > 0) - .order(Column.created.desc) - .limit(limit) - .fetchAll(from: db) - }.serverModels + private func makeCPFetchArgs(rosterId: Int64) -> CPFetchArgs { + return CPFetchArgs(rosterId: rosterId, limit: limit) } - func fetchHistory(for query: HomeDataQuery, limit: Int) -> [Contact] { // SHOULD work + func fetchHistory(rosterId: Int64, phoneId: String) -> [Contact] { let statuses: [Contact.Status] = [.request, .ignore] let statusStrings = statuses.map { $0.rawValue } return dbManager.fetch { db in return try DBContact - .filter(Column.rosterId == query.rosterId) - .filter(Column.phoneId != query.phoneId) + .filter(Column.rosterId == rosterId) + .filter(Column.phoneId != phoneId) .filter(Column.unread > 0) .filter(!statusStrings.contains(Column.status)) .order(Column.created.desc) diff --git a/Nynja/Modules/Profile/Interactor/ProfileInteractor.swift b/Nynja/Modules/Profile/Interactor/ProfileInteractor.swift index 0b73d2bbf..ae7d14d55 100644 --- a/Nynja/Modules/Profile/Interactor/ProfileInteractor.swift +++ b/Nynja/Modules/Profile/Interactor/ProfileInteractor.swift @@ -20,8 +20,8 @@ class ProfileInteractor: BaseInteractor, ProfileInteractorInputProtocol { var starred: CellModels = [] var scheduled: CellModels = [] + private var homeDataProvider: HomeDataProvider! private var contactsProvider: ContactsProviding! - private var conversationsProvider: ConversationsProviding! private var mqttService: MQTTService! //MARK: - BaseInteractor @@ -101,19 +101,23 @@ fileprivate extension ProfileInteractor { } func fetchChats() { - let models = conversationsProvider - .fetchChats() - .filter { $0.unreadCount > 0 } - - chats = Array(models.prefix(presenter.elemsInSection)) + performIfRosterIdExists { rosterId in + chats = homeDataProvider.fetchChats(rosterId: rosterId) + } } func fetchRooms() { - let models = conversationsProvider - .fetchGroups() - .filter { $0.unreadCount > 0 } + performIfRosterIdExists { rosterId in + chats = homeDataProvider.fetchGroups(rosterId: rosterId) + } + } + + private func performIfRosterIdExists(_ closure: (Int64) -> Void) { + guard let rosterId = StorageService.sharedInstance.rosterId else { + return + } - rooms = Array(models.prefix(presenter.elemsInSection)) + closure(rosterId) } func fetchHistory() { @@ -159,15 +163,15 @@ fileprivate extension ProfileInteractor { extension ProfileInteractor: SetInjectable { func inject(dependencies: ProfileInteractor.Dependencies) { presenter = dependencies.presenter + homeDataProvider = dependencies.homeDataProvider contactsProvider = dependencies.contactsProvider - conversationsProvider = dependencies.conversationsProvider mqttService = dependencies.mqttService } struct Dependencies { let presenter: ProfileInteractorOutputProtocol + let homeDataProvider: HomeDataProvider let contactsProvider: ContactsProviding - let conversationsProvider: ConversationsProviding let mqttService: MQTTService } } diff --git a/Nynja/Modules/Profile/WireFrame/ProfileWireframe.swift b/Nynja/Modules/Profile/WireFrame/ProfileWireframe.swift index 119210b33..3e001cd9d 100644 --- a/Nynja/Modules/Profile/WireFrame/ProfileWireframe.swift +++ b/Nynja/Modules/Profile/WireFrame/ProfileWireframe.swift @@ -15,27 +15,25 @@ class ProfileWireFrame: ProfileWireFrameProtocol { func presentProfile(navigation: UINavigationController, main: MainWireFrame?) { - // Dependencies - let contactsProvider = ContactsProvider() - let conversationsProvider = ConversationsProvider() - let mqttService = MQTTService.sharedInstance + let serviceFactory = ServiceFactory() // Components let view = ProfileViewController() let presenter = ProfilePresenter() let interactor = ProfileInteractor() - - let interactorDependencies = ProfileInteractor.Dependencies(presenter: presenter, - contactsProvider: contactsProvider, - conversationsProvider: conversationsProvider, - mqttService: mqttService) - let presenterDependencies = ProfilePresenter.Dependencies(view: view, wireFrame: self, interactor: interactor) - let viewDependencies = ProfileViewController.Dependencies(presenter: presenter) - - - interactor.inject(dependencies: interactorDependencies) - presenter.inject(dependencies: presenterDependencies) - view.inject(dependencies: viewDependencies) + + interactor.inject( + dependencies: .init( + presenter: presenter, + homeDataProvider: serviceFactory.makeHomeDataProvider(limit: 5), + contactsProvider: serviceFactory.makeContactsProvider(), + mqttService: serviceFactory.makeMQTTService())) + presenter.inject( + dependencies: .init( + view: view, + wireFrame: self, + interactor: interactor)) + view.inject(dependencies: .init(presenter: presenter)) self.navigation = navigation self.main = main diff --git a/Nynja/RoomDAO.swift b/Nynja/RoomDAO.swift index 7ceca3d9f..118794be0 100644 --- a/Nynja/RoomDAO.swift +++ b/Nynja/RoomDAO.swift @@ -16,6 +16,7 @@ class RoomDAO: RoomDAOProtocol { // MARK: - Fetch // MARK: -- Room + static func fetchRoom(by rowId: Int64) -> DBRoom? { return dbManager.fetch { db in return try DBRoom.room(from: db, rowId: rowId) @@ -33,6 +34,7 @@ class RoomDAO: RoomDAOProtocol { return Room(room: dbRoom) } + // MARK: -- Rooms static func fetchRooms(kind: Room.Kind? = nil) -> [Room] { @@ -40,30 +42,34 @@ class RoomDAO: RoomDAOProtocol { return [] } - let rooms = dbManager.fetch { db -> [DBRoom] in + return dbManager.fetch { db -> [DBRoom] in return try DBRoom.rooms(db, rosterId: rosterId, type: kind?.rawValue) - } - - return rooms.map { Room(room: $0) } + }.serverModels } static func fetchRooms(with ids: [String], kind: Room.Kind? = nil) -> [Room] { - let rooms = dbManager.fetch { db -> [DBRoom] in + return dbManager.fetch { db -> [DBRoom] in return try DBRoom.rooms(db, ids: ids, type: kind?.rawValue) - } - - return rooms.map { Room(room: $0) } + }.serverModels } static func fetchUserRooms(with contactId: String, isAdmin: Bool, kind: Room.Kind?) -> [Room] { - let rooms = dbManager.fetch { db in + return dbManager.fetch { db in return try DBRoom.rooms(db, contactId: contactId, isAdmin: isAdmin, type: kind?.rawValue) - } - - return rooms.map { Room(room: $0) } + }.serverModels + } + + + static func fetchRooms(with args: DBRoom.RequestArgs) -> [Room] { + return dbManager.fetch { db in + return try DBRoom + .request(with: args) + .fetchAll(from: db) + }.serverModels } // MARK: -- Reader + static func fetchReader(for roomId: String, kind: ReaderKind) -> Int64? { if kind == .other { return dbManager.fetch { db in diff --git a/Nynja/RoomDAOProtocol.swift b/Nynja/RoomDAOProtocol.swift index 8256c6e3b..b08c3b38d 100644 --- a/Nynja/RoomDAOProtocol.swift +++ b/Nynja/RoomDAOProtocol.swift @@ -10,30 +10,42 @@ protocol RoomDAOProtocol: DAOProtocol { // MARK: - Fetch // MARK: -- Room + static func fetchRoom(by rowId: Int64) -> DBRoom? static func fetchRoom(by id: String) -> DBRoom? static func findRoom(by id: String) -> Room? + // MARK: -- Rooms + static func fetchRooms(kind: Room.Kind?) -> [Room] static func fetchRooms(with ids: [String], kind: Room.Kind?) -> [Room] static func fetchUserRooms(with contactId: String, isAdmin: Bool, kind: Room.Kind?) -> [Room] + static func fetchRooms(with args: DBRoom.RequestArgs) -> [Room] + + // MARK: -- Reader + static func fetchReader(for roomId: String, kind: ReaderKind) -> Int64? + // MARK: - Update // MARK: -- Room + static func patchRoom(_ room: Room) static func updateColumns(_ columns: Set, room: Room) + // MARK: -- Fields + static func updateReader(_ reader: Int64, roomId: String, phoneId: String, kind: ReaderKind) static func updatedMentions(with message: Message, roomId: String) -> UpdateResult<[MessageServerId]> static func isCurrentUserMentioned(in message: Message, phoneId: String) -> Bool + // MARK: - Contains Members - static func containsMembers(with ids: [Int64], roomId: String) -> Bool? + static func containsMembers(with ids: [Int64], roomId: String) -> Bool? } diff --git a/Nynja/Services/ServiceFactory/ServiceFactory.swift b/Nynja/Services/ServiceFactory/ServiceFactory.swift index f581ec61b..7dd47ea15 100644 --- a/Nynja/Services/ServiceFactory/ServiceFactory.swift +++ b/Nynja/Services/ServiceFactory/ServiceFactory.swift @@ -47,6 +47,8 @@ protocol ServiceFactoryProtocol: SharedServiceFactoryProtocol { func makeUseCaseValidationServise() -> UseCaseValidationServiceProtocol func makeAudioSessionManager() -> AudioSessionManager + + func makeHomeDataProvider(limit: Int) -> HomeDataProvider } final class ServiceFactory: SharedServiceFactory, ServiceFactoryProtocol { @@ -169,4 +171,15 @@ final class ServiceFactory: SharedServiceFactory, ServiceFactoryProtocol { func makeAudioSessionManager() -> AudioSessionManager { return AudioSessionManager.shared } + + func makeHomeDataProvider(limit: Int) -> HomeDataProvider { + return HomeDataProviderImpl( + dependencies: ( + services: ( + dbManager: makeStorageService(), + conversationsProvider: makeConversationsProvider() + ), + fields: .init( + limit: limit))) + } } -- GitLab From ddb520f169ab4bfc17645cbbf581a4d51c2fa1a3 Mon Sep 17 00:00:00 2001 From: Volodymyr Hryhoriev Date: Tue, 6 Nov 2018 14:21:20 +0200 Subject: [PATCH 14/68] 1. Rename `CPFetchArgs` to `ConversationProvidingFetchingArgs`. 2. Implemented paginated query for contacts history. --- Nynja.xcodeproj/project.pbxproj | 40 ++++++++- Nynja/ContactDAO.swift | 82 ++++++++++--------- Nynja/ContactDAOFetchingArgs.swift | 27 ++++++ Nynja/ContactDAOProtocol.swift | 5 ++ Nynja/ContactsProvider.swift | 29 +++---- Nynja/ContactsProviding.swift | 3 +- Nynja/ContactsProvidingFetchingArgs.swift | 25 ++++++ Nynja/ConversationsProvider.swift | 7 +- Nynja/ConversationsProviding.swift | 4 +- ... ConversationsProvidingFetchingArgs.swift} | 4 +- Nynja/DB/Models/Base/DBModelProtocol.swift | 20 ++++- Nynja/DB/Tables/Base/SharedColumn.swift | 1 + Nynja/DB/Tables/ContactTable.swift | 5 ++ .../SwiftLibrary/Array/Array+Contact.swift | 7 ++ .../Interactor/ChatsListInteractor.swift | 4 +- .../Interactor/GroupsListInteractor.swift | 4 +- .../Interactor/HistoryInteractor.swift | 14 +++- .../View/MainViewController+Recents.swift | 4 +- .../HomeDataProvider/HomeDataProvider.swift | 3 +- .../HomeDataProviderImpl.swift | 43 +++++----- .../Interactor/ProfileInteractor.swift | 10 ++- .../ServiceFactory/ServiceFactory.swift | 3 +- 22 files changed, 245 insertions(+), 99 deletions(-) create mode 100644 Nynja/ContactDAOFetchingArgs.swift create mode 100644 Nynja/ContactsProvidingFetchingArgs.swift rename Nynja/{CPFetchArgs.swift => ConversationsProvidingFetchingArgs.swift} (80%) diff --git a/Nynja.xcodeproj/project.pbxproj b/Nynja.xcodeproj/project.pbxproj index e06331598..cb7e83ad7 100644 --- a/Nynja.xcodeproj/project.pbxproj +++ b/Nynja.xcodeproj/project.pbxproj @@ -575,7 +575,7 @@ 4B1D7E14202A0A0200703228 /* GroupMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B1D7E13202A0A0200703228 /* GroupMode.swift */; }; 4B1F1230203C8DDE00D61D21 /* JobTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B1F122F203C8DDE00D61D21 /* JobTable.swift */; }; 4B2B1558218F5423000E4916 /* SharedColumn.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B2B1557218F5423000E4916 /* SharedColumn.swift */; }; - 4B2B155A218F6982000E4916 /* CPFetchArgs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B2B1559218F6982000E4916 /* CPFetchArgs.swift */; }; + 4B2B155A218F6982000E4916 /* ConversationsProvidingFetchingArgs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B2B1559218F6982000E4916 /* ConversationsProvidingFetchingArgs.swift */; }; 4B2D063A202DDA2000010A0C /* BackSwipable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B2D0639202DDA2000010A0C /* BackSwipable.swift */; }; 4B2D063C202E1A1500010A0C /* ContactsExpandedItemsFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B2D063B202E1A1500010A0C /* ContactsExpandedItemsFactory.swift */; }; 4B348FD7216366A900CCB0E3 /* LogServiceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BF090B821635B4700DCCA5C /* LogServiceProtocol.swift */; }; @@ -683,6 +683,8 @@ 4BB0EFBB2151347900704136 /* AlertImageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB0EFB82151347900704136 /* AlertImageViewController.swift */; }; 4BB0EFBC2151347900704136 /* AlertImageViewControllerConstraints.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB0EFB92151347900704136 /* AlertImageViewControllerConstraints.swift */; }; 4BB0EFBD2151347900704136 /* AlertManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB0EFBA2151347900704136 /* AlertManager.swift */; }; + 4BC8B38A2191A1120086DC6C /* ContactDAOFetchingArgs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BC8B3892191A1120086DC6C /* ContactDAOFetchingArgs.swift */; }; + 4BC8B38D2191AC360086DC6C /* ContactsProvidingFetchingArgs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BC8B38C2191AC360086DC6C /* ContactsProvidingFetchingArgs.swift */; }; 4BD53BF4202C8BCA00569C1A /* AVURLAsset+Duration.swift in Sources */ = {isa = PBXBuildFile; fileRef = E77FBDDC1FFE828400BDB255 /* AVURLAsset+Duration.swift */; }; 4BDC7E61203492CA00BCD381 /* TopSwipable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BDC7E60203492CA00BCD381 /* TopSwipable.swift */; }; 4BDC7E63203494C000BCD381 /* ScheduleButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BDC7E62203494C000BCD381 /* ScheduleButton.swift */; }; @@ -2861,7 +2863,7 @@ 4B1D7E13202A0A0200703228 /* GroupMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupMode.swift; sourceTree = ""; }; 4B1F122F203C8DDE00D61D21 /* JobTable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JobTable.swift; sourceTree = ""; }; 4B2B1557218F5423000E4916 /* SharedColumn.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedColumn.swift; sourceTree = ""; }; - 4B2B1559218F6982000E4916 /* CPFetchArgs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CPFetchArgs.swift; sourceTree = ""; }; + 4B2B1559218F6982000E4916 /* ConversationsProvidingFetchingArgs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationsProvidingFetchingArgs.swift; sourceTree = ""; }; 4B2D0639202DDA2000010A0C /* BackSwipable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackSwipable.swift; sourceTree = ""; }; 4B2D063B202E1A1500010A0C /* ContactsExpandedItemsFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactsExpandedItemsFactory.swift; sourceTree = ""; }; 4B348FF12163808300CCB0E3 /* DeletedIndexesCalculatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeletedIndexesCalculatorTests.swift; sourceTree = ""; }; @@ -2949,6 +2951,8 @@ 4BB0EFB82151347900704136 /* AlertImageViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlertImageViewController.swift; sourceTree = ""; }; 4BB0EFB92151347900704136 /* AlertImageViewControllerConstraints.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlertImageViewControllerConstraints.swift; sourceTree = ""; }; 4BB0EFBA2151347900704136 /* AlertManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlertManager.swift; sourceTree = ""; }; + 4BC8B3892191A1120086DC6C /* ContactDAOFetchingArgs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactDAOFetchingArgs.swift; sourceTree = ""; }; + 4BC8B38C2191AC360086DC6C /* ContactsProvidingFetchingArgs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactsProvidingFetchingArgs.swift; sourceTree = ""; }; 4BDC7E60203492CA00BCD381 /* TopSwipable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TopSwipable.swift; sourceTree = ""; }; 4BDC7E62203494C000BCD381 /* ScheduleButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScheduleButton.swift; sourceTree = ""; }; 4BDDAAE8218CA28300F775A7 /* HomeDataProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeDataProvider.swift; sourceTree = ""; }; @@ -6958,6 +6962,7 @@ 4B8996C6204ECE8500DCB183 /* Contact */ = { isa = PBXGroup; children = ( + 4BC8B3882191A0FE0086DC6C /* Entity */, 4B8996C9204ECEA700DCB183 /* ContactDAOProtocol.swift */, 4B8996C7204ECE9B00DCB183 /* ContactDAO.swift */, ); @@ -7083,6 +7088,30 @@ path = AlertImageViewController; sourceTree = ""; }; + 4BC8B3882191A0FE0086DC6C /* Entity */ = { + isa = PBXGroup; + children = ( + 4BC8B3892191A1120086DC6C /* ContactDAOFetchingArgs.swift */, + ); + name = Entity; + sourceTree = ""; + }; + 4BC8B38B2191AC200086DC6C /* Entity */ = { + isa = PBXGroup; + children = ( + 4BC8B38C2191AC360086DC6C /* ContactsProvidingFetchingArgs.swift */, + ); + name = Entity; + sourceTree = ""; + }; + 4BC8B38E2191ADB50086DC6C /* Entity */ = { + isa = PBXGroup; + children = ( + 4B2B1559218F6982000E4916 /* ConversationsProvidingFetchingArgs.swift */, + ); + name = Entity; + sourceTree = ""; + }; 4BDDAAE7218CA27200F775A7 /* HomeDataProvider */ = { isa = PBXGroup; children = ( @@ -9708,9 +9737,9 @@ A411D95820AC3916009D107C /* ConversationsProvider */ = { isa = PBXGroup; children = ( + 4BC8B38E2191ADB50086DC6C /* Entity */, A411D95920AC39C6009D107C /* ConversationsProviding.swift */, A411D95B20AC3A5A009D107C /* ConversationsProvider.swift */, - 4B2B1559218F6982000E4916 /* CPFetchArgs.swift */, ); name = ConversationsProvider; sourceTree = ""; @@ -10016,6 +10045,7 @@ A433D99F20A5C17600C946F9 /* ContactsProvider */ = { isa = PBXGroup; children = ( + 4BC8B38B2191AC200086DC6C /* Entity */, A433D9A220A5C19600C946F9 /* ContactsProviding.swift */, A433D9A020A5C18C00C946F9 /* ContactsProvider.swift */, ); @@ -14935,6 +14965,7 @@ 0008E92420347A8E003E316E /* DBJobMessage.swift in Sources */, 850FC5EC2032F21E00832D87 /* ForwardSelectorProtocols.swift in Sources */, 4BAB9CE62035CB3800385520 /* ScheduleDisplayInfo.swift in Sources */, + 4BC8B38A2191A1120086DC6C /* ContactDAOFetchingArgs.swift in Sources */, E77764C11FBDA9BD0042541D /* ImageFullWheelItemModel.swift in Sources */, 3A8045D21F60C8E200AED866 /* MQTTServiceFriend.swift in Sources */, F117872420ACF2DB007A9A1B /* PhotoQuality.swift in Sources */, @@ -15769,6 +15800,7 @@ 85458CED212D74B400BA8814 /* P2P+Opponent.swift in Sources */, E79061B41FBF10AA009FD83A /* MessageTable.swift in Sources */, 00EB7872206E286A00E3FB03 /* WCReusableViews.swift in Sources */, + 4BC8B38D2191AC360086DC6C /* ContactsProvidingFetchingArgs.swift in Sources */, 8ED0F3C11FBC5CB1004916AB /* Contact+DialogCellModel.swift in Sources */, 8EE9BC1A1FFE79FF00ECBBC7 /* GroupStorageCell.swift in Sources */, FEA655E02167777E00B44029 /* SeedBackupWalletPresenter.swift in Sources */, @@ -16043,7 +16075,7 @@ 8502DB542061030100613C8C /* WheelPositionPickerViewController.swift in Sources */, 4B752B612163A5C900E852B9 /* DescExtension.swift in Sources */, A45F116120B422AF00F45004 /* Message+System.swift in Sources */, - 4B2B155A218F6982000E4916 /* CPFetchArgs.swift in Sources */, + 4B2B155A218F6982000E4916 /* ConversationsProvidingFetchingArgs.swift in Sources */, 4B06D30C2028A25D003B275B /* HomeItemsFactory.swift in Sources */, 266AE8C3203496B60096A12C /* AsyncOperation.swift in Sources */, FEA655DE2167777E00B44029 /* WalletDetailsInteractor.swift in Sources */, diff --git a/Nynja/ContactDAO.swift b/Nynja/ContactDAO.swift index 5c713f42b..8e272ac35 100644 --- a/Nynja/ContactDAO.swift +++ b/Nynja/ContactDAO.swift @@ -35,32 +35,21 @@ class ContactDAO: ContactDAOProtocol { } static func findContactBy(phone: String) -> Contact? { - let phoneIdColumn = Column(ContactTable.Column.phoneId.title) - - guard let contact = dbManager.fetch({ db in - return try DBContact.filter(phoneIdColumn.like("\(phone)%")).fetchOne(db) - }) else { - return nil - } - - return Contact(contact: contact) + return dbManager.fetch { db in + return try DBContact + .filter(Column.phoneId.like("\(phone)%")) + .fetchOne(db) + }?.serverModel } static func findContactBy(phoneId: String) -> Contact? { - guard let dbContact = fetchContact(by: phoneId) else { return nil } - return Contact(contact: dbContact) + return fetchContact(by: phoneId)?.serverModel } static func findContactBy(username: String) -> Contact? { - let nickColumn = Column(ContactTable.Column.nick.title) - - guard let contact = dbManager.fetch({ db in - return try DBContact.filter(nickColumn == username).fetchOne(db) - }) else { - return nil - } - - return Contact(contact: contact) + return dbManager.fetch { db in + return try DBContact.filter(Column.nick == username).fetchOne(db) + }?.serverModel } @@ -70,13 +59,11 @@ class ContactDAO: ContactDAOProtocol { return [] } - let contacts = dbManager.fetch { db -> [DBContact] in + return dbManager.fetch { db -> [DBContact] in return try DBContact .filter(Column.rosterId == rosterId) .fetchAll(from: db) - } - - return contacts.map { Contact(contact: $0) } + }.serverModels } static func fetchContactWithoutSelf() -> [Contact] { @@ -92,21 +79,14 @@ class ContactDAO: ContactDAOProtocol { } static func fetchContacts(with phoneIds: [String], isExcluded: Bool = false) -> [Contact] { - let contacts = dbManager.fetch { db -> [DBContact] in - let phoneIdColumn = Column(ContactTable.Column.phoneId.title) - - var predicate = phoneIds.contains(phoneIdColumn) + return dbManager.fetch { db -> [DBContact] in + var predicate = phoneIds.contains(Column.phoneId) if isExcluded { predicate = !predicate } - // TODO: need to think - let contacts = try DBContact.filter(predicate).fetchAll(db) - try contacts.forEach { try $0.construct(db) } - return contacts - } - - return contacts.map { Contact(contact: $0) } + return try DBContact.filter(predicate).fetchAll(from: db) + }.serverModels } static func fetchContacts(with statuses: [Contact.Status]) -> [Contact] { @@ -119,6 +99,34 @@ class ContactDAO: ContactDAOProtocol { }.serverModels } + static func fetchContacts(with args: ContactDAOFetchingArgs) -> [Contact] { + return dbManager.fetch { db in + return try makeQuery(with: args).fetchAll(db) + }.serverModels + } + + static func fetchContacts( + with args: ContactDAOFetchingArgs, + excludingIds ids: [String], + excludingStatuses statuses: [Contact.Status]?) -> [Contact] { + + return dbManager.fetch { db in + return try makeQuery(with: args) + .filter(!ids.contains(Column.phoneId)) + .performIfValueExists(statuses) { $0.filter(!$1.strings.contains(Column.status)) } + .fetchAll(from: db) + }.serverModels + } + + private static func makeQuery( + with args: ContactDAOFetchingArgs) -> QueryInterfaceRequest { + + return DBContact + .filter(Column.rosterId == args.rosterId) + .performIfValueExists(args.limit) { $0.limit($1, offset: args.offset) } + .performIfValueExists(args.orderColumns) { $0.order($1) } + } + static func fetchChats(rosterId: Int64, limit: Int? = nil, offset: Int? = nil) -> [Contact] { return dbManager.fetch { db in return try DBContact @@ -131,8 +139,8 @@ class ContactDAO: ContactDAOProtocol { static func fetchReader(for phoneId: String, kind: ReaderKind) -> Int64? { let readersString = dbManager.fetch{ db in return try DBContact - .filter(Column(ContactTable.Column.phoneId.title) == phoneId) - .select([Column(ContactTable.Column.reader.title)]) + .filter(Column.phoneId == phoneId) + .select([Column.reader]) .asRequest(of: String.self) .fetchOne(db) } diff --git a/Nynja/ContactDAOFetchingArgs.swift b/Nynja/ContactDAOFetchingArgs.swift new file mode 100644 index 000000000..a6949b548 --- /dev/null +++ b/Nynja/ContactDAOFetchingArgs.swift @@ -0,0 +1,27 @@ +// +// ContactDAOFetchingArgs.swift +// Nynja +// +// Created by Volodymyr Hryhoriev on 11/6/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import GRDBCipher + +struct ContactDAOFetchingArgs { + let rosterId: Int64 + let limit: Int? + let offset: Int? + let orderColumns: [SQLOrderingTerm]? + + init(rosterId: Int64, + limit: Int? = nil, + offset: Int? = nil, + orderColumns: [SQLOrderingTerm]? = nil) { + + self.rosterId = rosterId + self.limit = limit + self.offset = offset + self.orderColumns = orderColumns + } +} diff --git a/Nynja/ContactDAOProtocol.swift b/Nynja/ContactDAOProtocol.swift index 0e1cda1f5..813726df1 100644 --- a/Nynja/ContactDAOProtocol.swift +++ b/Nynja/ContactDAOProtocol.swift @@ -27,6 +27,11 @@ protocol ContactDAOProtocol: DAOProtocol { static func fetchContacts(with phoneIds: [String], isExcluded: Bool) -> [Contact] static func fetchContacts(with statuses: [Contact.Status]) -> [Contact] + static func fetchContacts(with args: ContactDAOFetchingArgs) -> [Contact] + static func fetchContacts(with args: ContactDAOFetchingArgs, + excludingIds ids: [String], + excludingStatuses statuses: [Contact.Status]?) -> [Contact] + static func fetchChats(rosterId: Int64, limit: Int?, offset: Int?) -> [Contact] diff --git a/Nynja/ContactsProvider.swift b/Nynja/ContactsProvider.swift index 84ffbbafd..d2d32e5f6 100644 --- a/Nynja/ContactsProvider.swift +++ b/Nynja/ContactsProvider.swift @@ -6,24 +6,25 @@ // Copyright © 2018 TecSynt Solutions. All rights reserved. // +import GRDBCipher + class ContactsProvider: ContactsProviding { + typealias FetchingArgs = ContactsProvidingFetchingArgs - func fetchHistory() -> [Contact] { - return ContactDAO - .fetchContactWithoutSelf() - .sorted { - $0.create > $1.create - } + private let orderColumns = [Column.created.desc] + + func fetchHistory(with args: FetchingArgs) -> [Contact] { + let daoArgs = makeDAOFetchingArgs(from: args) + return ContactDAO.fetchContacts(with: daoArgs, + excludingIds: [args.phoneId], + excludingStatuses: args.statuses) } - func fetchHistory(without statuses: [Contact.Status]) -> [Contact] { - return fetchHistory().filter { contact in - if let status = contact.originalStatus { - return !statuses.contains(status) - } - - return true - } + private func makeDAOFetchingArgs(from args: FetchingArgs) -> ContactDAOFetchingArgs { + return ContactDAOFetchingArgs( + rosterId: args.rosterId, + limit: args.limit, + orderColumns: orderColumns) } func fetchFriends() -> [Contact] { diff --git a/Nynja/ContactsProviding.swift b/Nynja/ContactsProviding.swift index 5fedf5cc2..0d44e9650 100644 --- a/Nynja/ContactsProviding.swift +++ b/Nynja/ContactsProviding.swift @@ -8,8 +8,7 @@ protocol ContactsProviding { - func fetchHistory() -> [Contact] - func fetchHistory(without statuses: [Contact.Status]) -> [Contact] + func fetchHistory(with args: ContactsProvidingFetchingArgs) -> [Contact] func fetchFriends() -> [Contact] func fetchFriendsWithoutBlocked() -> [Contact] diff --git a/Nynja/ContactsProvidingFetchingArgs.swift b/Nynja/ContactsProvidingFetchingArgs.swift new file mode 100644 index 000000000..3e49f1db5 --- /dev/null +++ b/Nynja/ContactsProvidingFetchingArgs.swift @@ -0,0 +1,25 @@ +// +// ContactsProvidingFetchingArgs.swift +// Nynja +// +// Created by Volodymyr Hryhoriev on 11/6/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + + +struct ContactsProvidingFetchingArgs { + let rosterId: Int64 + let phoneId: String + let statuses: [Contact.Status]? + let limit: Int? + + init(rosterId: Int64, + phoneId: String, + statuses: [Contact.Status]? = nil, + limit: Int? = nil) { + self.rosterId = rosterId + self.phoneId = phoneId + self.statuses = statuses + self.limit = limit + } +} diff --git a/Nynja/ConversationsProvider.swift b/Nynja/ConversationsProvider.swift index e41832b32..730bcbe81 100644 --- a/Nynja/ConversationsProvider.swift +++ b/Nynja/ConversationsProvider.swift @@ -7,10 +7,11 @@ // class ConversationsProvider: ConversationsProviding { + typealias FetchingArgs = ConversationsProvidingFetchingArgs // MARK: - Chats - func fetchChats(with args: CPFetchArgs) -> [Contact] { + func fetchChats(with args: FetchingArgs) -> [Contact] { return ContactDAO.fetchChats( rosterId: args.rosterId, limit: args.limit, @@ -20,12 +21,12 @@ class ConversationsProvider: ConversationsProviding { // MARK: - Groups - func fetchGroups(with args: CPFetchArgs) -> [Room] { + func fetchGroups(with args: FetchingArgs) -> [Room] { let groupRequestArgs = makeGroupsRequestArgs(from: args, kind: .group) return RoomDAO.fetchRooms(with: groupRequestArgs) } - private func makeGroupsRequestArgs(from args: CPFetchArgs, kind: Room.Kind) -> DBRoom.RequestArgs { + private func makeGroupsRequestArgs(from args: FetchingArgs, kind: Room.Kind) -> DBRoom.RequestArgs { return .init( rosterId: args.rosterId, type: kind.rawValue, diff --git a/Nynja/ConversationsProviding.swift b/Nynja/ConversationsProviding.swift index 70699b47b..14e4df45f 100644 --- a/Nynja/ConversationsProviding.swift +++ b/Nynja/ConversationsProviding.swift @@ -9,9 +9,9 @@ protocol ConversationsProviding { - func fetchChats(with args: CPFetchArgs) -> [Contact] + func fetchChats(with args: ConversationsProvidingFetchingArgs) -> [Contact] - func fetchGroups(with args: CPFetchArgs) -> [Room] + func fetchGroups(with args: ConversationsProvidingFetchingArgs) -> [Room] func fetchChannels() -> [Room] func fetchMyChannels() -> [Room] diff --git a/Nynja/CPFetchArgs.swift b/Nynja/ConversationsProvidingFetchingArgs.swift similarity index 80% rename from Nynja/CPFetchArgs.swift rename to Nynja/ConversationsProvidingFetchingArgs.swift index 8727e8cae..d7329f1cb 100644 --- a/Nynja/CPFetchArgs.swift +++ b/Nynja/ConversationsProvidingFetchingArgs.swift @@ -1,5 +1,5 @@ // -// CPFetchArgs.swift +// ConversationsProvidingFetchingArgs.swift // Nynja // // Created by Volodymyr Hryhoriev on 11/4/18. @@ -7,7 +7,7 @@ // -struct CPFetchArgs { +struct ConversationsProvidingFetchingArgs { let rosterId: Int64 let limit: Int? diff --git a/Nynja/DB/Models/Base/DBModelProtocol.swift b/Nynja/DB/Models/Base/DBModelProtocol.swift index a5ca9cdf7..466c19a00 100644 --- a/Nynja/DB/Models/Base/DBModelProtocol.swift +++ b/Nynja/DB/Models/Base/DBModelProtocol.swift @@ -50,8 +50,6 @@ enum DBModelFetchingKind { extension TypedRequest where RowDecoder: DBModelProtocol { - - func fetchAll(kind: DBModelFetchingKind = .full, from db: Database) throws -> [RowDecoder] { let models = try self.fetchAll(db) if kind == .full { @@ -82,3 +80,21 @@ extension Array where Element: ServerModelConvertible { return self.map { $0.serverModel } } } + + +// MARK: - QueryInterfaceRequest + +extension QueryInterfaceRequest { + + func performIfValueExists( + _ value: V?, + closure: (QueryInterfaceRequest, V) -> QueryInterfaceRequest) -> QueryInterfaceRequest { + + guard let value = value else { + return self + } + + return closure(self, value) + } + +} diff --git a/Nynja/DB/Tables/Base/SharedColumn.swift b/Nynja/DB/Tables/Base/SharedColumn.swift index 253f5a171..35621f8b0 100644 --- a/Nynja/DB/Tables/Base/SharedColumn.swift +++ b/Nynja/DB/Tables/Base/SharedColumn.swift @@ -17,4 +17,5 @@ extension Column { static let rosterId = Column("rosterId") static let localId = Column("localId") static let type = Column("type") + static let reader = Column("reader") } diff --git a/Nynja/DB/Tables/ContactTable.swift b/Nynja/DB/Tables/ContactTable.swift index 3ac2717c6..f8a4e4560 100644 --- a/Nynja/DB/Tables/ContactTable.swift +++ b/Nynja/DB/Tables/ContactTable.swift @@ -52,3 +52,8 @@ extension ContactTable { case rosterId } } + + +extension Column { + static let nick = Column("nick") +} diff --git a/Nynja/Extensions/SwiftLibrary/Array/Array+Contact.swift b/Nynja/Extensions/SwiftLibrary/Array/Array+Contact.swift index 6ff167dc8..056a4d9a7 100644 --- a/Nynja/Extensions/SwiftLibrary/Array/Array+Contact.swift +++ b/Nynja/Extensions/SwiftLibrary/Array/Array+Contact.swift @@ -30,3 +30,10 @@ extension Array where Element == Contact { } } + + +extension Array where Element == Contact.Status { + var strings: [String] { + return self.map { $0.rawValue } + } +} diff --git a/Nynja/Modules/ChatsList/Interactor/ChatsListInteractor.swift b/Nynja/Modules/ChatsList/Interactor/ChatsListInteractor.swift index 3a511cb6f..3f92bbe83 100644 --- a/Nynja/Modules/ChatsList/Interactor/ChatsListInteractor.swift +++ b/Nynja/Modules/ChatsList/Interactor/ChatsListInteractor.swift @@ -64,7 +64,7 @@ class ChatsListInteractor: BaseInteractor, ChatsListInteractorInputProtocol, Ini let contact = Contact(contact: dbContact) chats = updateChatsList(with: contact) } else { - guard let args = rosterId.map({ CPFetchArgs(rosterId: $0) }) else { + guard let args = rosterId.map({ ConversationsProvidingFetchingArgs(rosterId: $0) }) else { return } @@ -78,7 +78,7 @@ class ChatsListInteractor: BaseInteractor, ChatsListInteractorInputProtocol, Ini //MARK: - Private private func fetchChats() { - guard let args = rosterId.map({ CPFetchArgs(rosterId: $0) }) else { + guard let args = rosterId.map({ ConversationsProvidingFetchingArgs(rosterId: $0) }) else { return } diff --git a/Nynja/Modules/GroupsList/Interactor/GroupsListInteractor.swift b/Nynja/Modules/GroupsList/Interactor/GroupsListInteractor.swift index 781d60b80..e2a7ad016 100644 --- a/Nynja/Modules/GroupsList/Interactor/GroupsListInteractor.swift +++ b/Nynja/Modules/GroupsList/Interactor/GroupsListInteractor.swift @@ -70,7 +70,7 @@ class GroupsListInteractor: BaseInteractor, GroupsListInteractorInputProtocol, I applyFilter(with: searchText) } } else { - guard let args = rosterId.map({ CPFetchArgs(rosterId: $0) }) else { + guard let args = rosterId.map({ ConversationsProvidingFetchingArgs(rosterId: $0) }) else { return } @@ -83,7 +83,7 @@ class GroupsListInteractor: BaseInteractor, GroupsListInteractorInputProtocol, I //MARK: - Private private func fetchGroups() { - guard let args = rosterId.map({ CPFetchArgs(rosterId: $0) }) else { + guard let args = rosterId.map({ ConversationsProvidingFetchingArgs(rosterId: $0) }) else { return } diff --git a/Nynja/Modules/History/Interactor/HistoryInteractor.swift b/Nynja/Modules/History/Interactor/HistoryInteractor.swift index f8cbf74de..efff84e8e 100644 --- a/Nynja/Modules/History/Interactor/HistoryInteractor.swift +++ b/Nynja/Modules/History/Interactor/HistoryInteractor.swift @@ -15,6 +15,9 @@ class HistoryInteractor: BaseInteractor, HistoryInteractorInputProtocol, SetInje private var contacts: [Contact] = [] + private var storageService: StorageService { + return StorageService.sharedInstance + } //MARK: - BaseInteractor @@ -67,7 +70,16 @@ class HistoryInteractor: BaseInteractor, HistoryInteractorInputProtocol, SetInje //MARK: - Private private func _fetchHistory() { - contacts = contactsProvider.fetchHistory() + guard let rosterId = storageService.rosterId, + let phoneId = storageService.phoneId else { + return + } + + let args = ContactsProvidingFetchingArgs( + rosterId: rosterId, + phoneId: phoneId) + + contacts = contactsProvider.fetchHistory(with: args) presenter.fetched(contacts: contacts) } } diff --git a/Nynja/Modules/Main/View/MainViewController+Recents.swift b/Nynja/Modules/Main/View/MainViewController+Recents.swift index 2a1e68370..3e952a30a 100644 --- a/Nynja/Modules/Main/View/MainViewController+Recents.swift +++ b/Nynja/Modules/Main/View/MainViewController+Recents.swift @@ -54,12 +54,12 @@ extension MainViewController { showRecentConversation(indexPath: indexPath, conversations: conversations) } - private func fetchConversation(using fetcher: (CPFetchArgs) -> [ChatModel]) -> [ChatModel] { + private func fetchConversation(using fetcher: (ConversationsProvidingFetchingArgs) -> [ChatModel]) -> [ChatModel] { guard let rosterId = StorageService.sharedInstance.rosterId else { return [] } - let args = CPFetchArgs(rosterId: rosterId) + let args = ConversationsProvidingFetchingArgs(rosterId: rosterId) return fetcher(args) } diff --git a/Nynja/Modules/Profile/Interactor/HomeDataProvider/HomeDataProvider.swift b/Nynja/Modules/Profile/Interactor/HomeDataProvider/HomeDataProvider.swift index f8a09da31..485eed8e3 100644 --- a/Nynja/Modules/Profile/Interactor/HomeDataProvider/HomeDataProvider.swift +++ b/Nynja/Modules/Profile/Interactor/HomeDataProvider/HomeDataProvider.swift @@ -8,7 +8,8 @@ protocol HomeDataProvider { var dbManager: DBManagerProtocol { get } - var conversationsProvider: ConversationsProviding { get } + var conversationsProviding: ConversationsProviding { get } + var contactsProviding: ContactsProviding { get } var limit: Int { get } diff --git a/Nynja/Modules/Profile/Interactor/HomeDataProvider/HomeDataProviderImpl.swift b/Nynja/Modules/Profile/Interactor/HomeDataProvider/HomeDataProviderImpl.swift index a92e9a349..44ed0319b 100644 --- a/Nynja/Modules/Profile/Interactor/HomeDataProvider/HomeDataProviderImpl.swift +++ b/Nynja/Modules/Profile/Interactor/HomeDataProvider/HomeDataProviderImpl.swift @@ -10,56 +10,55 @@ import GRDBCipher class HomeDataProviderImpl: HomeDataProvider, InitializeInjectable { typealias Dependencies = (services: Services, fields: Fields) - typealias Services = (dbManager: DBManagerProtocol, conversationsProvider: ConversationsProviding) + typealias Services = (dbManager: DBManagerProtocol, conversationsProviding: ConversationsProviding, contactsProviding: ContactsProviding) struct Fields { let limit: Int } let dbManager: DBManagerProtocol - let conversationsProvider: ConversationsProviding + let conversationsProviding: ConversationsProviding + let contactsProviding: ContactsProviding let limit: Int required init(dependencies: Dependencies) { dbManager = dependencies.services.dbManager - conversationsProvider = dependencies.services.conversationsProvider + conversationsProviding = dependencies.services.conversationsProviding + contactsProviding = dependencies.services.contactsProviding limit = dependencies.fields.limit } // MARK: - HomeDataProvider func fetchChats(rosterId: Int64) -> [Contact] { - let fetcher = conversationsProvider.fetchChats(with:) + let fetcher = conversationsProviding.fetchChats(with:) return fetchConversations(rosterId: rosterId, fetcher: fetcher) } func fetchGroups(rosterId: Int64) -> [Room] { - let fetcher = conversationsProvider.fetchGroups(with:) + let fetcher = conversationsProviding.fetchGroups(with:) return fetchConversations(rosterId: rosterId, fetcher: fetcher) } private func fetchConversations( rosterId: Int64, - fetcher: (CPFetchArgs) -> [T]) -> [T] { + fetcher: (ConversationsProvidingFetchingArgs) -> [T]) -> [T] { - let args = makeCPFetchArgs(rosterId: rosterId) + let args = makeConversationsProvidingFetchingArgs(rosterId: rosterId) return fetcher(args) } - private func makeCPFetchArgs(rosterId: Int64) -> CPFetchArgs { - return CPFetchArgs(rosterId: rosterId, limit: limit) + private func makeConversationsProvidingFetchingArgs(rosterId: Int64) -> ConversationsProvidingFetchingArgs { + return ConversationsProvidingFetchingArgs(rosterId: rosterId, limit: limit) } func fetchHistory(rosterId: Int64, phoneId: String) -> [Contact] { - let statuses: [Contact.Status] = [.request, .ignore] - let statusStrings = statuses.map { $0.rawValue } - - return dbManager.fetch { db in - return try DBContact - .filter(Column.rosterId == rosterId) - .filter(Column.phoneId != phoneId) - .filter(Column.unread > 0) - .filter(!statusStrings.contains(Column.status)) - .order(Column.created.desc) - .limit(limit) - .fetchAll(from: db) - }.serverModels + let args = makeContactsProvidingFetchingArgs(rosterId: rosterId, phoneId: phoneId) + return contactsProviding.fetchHistory(with: args) + } + + private func makeContactsProvidingFetchingArgs(rosterId: Int64, phoneId: String) -> ContactsProvidingFetchingArgs { + return ContactsProvidingFetchingArgs( + rosterId: rosterId, + phoneId: phoneId, + statuses: [.request, .ignore], + limit: limit) } } diff --git a/Nynja/Modules/Profile/Interactor/ProfileInteractor.swift b/Nynja/Modules/Profile/Interactor/ProfileInteractor.swift index ae7d14d55..09e113f84 100644 --- a/Nynja/Modules/Profile/Interactor/ProfileInteractor.swift +++ b/Nynja/Modules/Profile/Interactor/ProfileInteractor.swift @@ -121,8 +121,13 @@ fileprivate extension ProfileInteractor { } func fetchHistory() { - let models = contactsProvider.fetchHistory(without: [.request, .ignore]) - history = Array(models.prefix(presenter.elemsInSection)) + performIfRosterIdExists { rosterId in + guard let phoneId = StorageService.sharedInstance.phoneId else { + return + } + + history = homeDataProvider.fetchHistory(rosterId: rosterId, phoneId: phoneId) + } } func fetchStar() { @@ -130,6 +135,7 @@ fileprivate extension ProfileInteractor { starred = [Star]() return } + let stars = StarDAO.fetchStars(rosterId: id).values.sorted { $0.timestamp > $1.timestamp } starred = Array(stars.prefix(presenter.elemsInSection)) } diff --git a/Nynja/Services/ServiceFactory/ServiceFactory.swift b/Nynja/Services/ServiceFactory/ServiceFactory.swift index 7dd47ea15..ca751bde1 100644 --- a/Nynja/Services/ServiceFactory/ServiceFactory.swift +++ b/Nynja/Services/ServiceFactory/ServiceFactory.swift @@ -177,7 +177,8 @@ final class ServiceFactory: SharedServiceFactory, ServiceFactoryProtocol { dependencies: ( services: ( dbManager: makeStorageService(), - conversationsProvider: makeConversationsProvider() + conversationsProviding: makeConversationsProvider(), + contactsProviding: makeContactsProvider() ), fields: .init( limit: limit))) -- GitLab From b88c6a773c91ea060e20c88df9969abeecb12e7a Mon Sep 17 00:00:00 2001 From: Volodymyr Hryhoriev Date: Wed, 7 Nov 2018 12:45:12 +0200 Subject: [PATCH 15/68] Implement paginated fetching of stars. --- Nynja.xcodeproj/project.pbxproj | 16 ++++ .../ColumnDefinitionExtension.swift | 22 ++++++ .../Extensions/TableDefinitionExtension.swift | 1 + .../TransactionObserverExtension.swift | 1 - Nynja/DB/Models/DBStar.swift | 78 ++++++++----------- Nynja/DB/Models/DBStarAction.swift | 14 ++-- Nynja/DB/Tables/Base/SharedColumn.swift | 11 ++- Nynja/DB/Tables/StarActionTable.swift | 11 +-- Nynja/DB/Tables/StarTable.swift | 15 ++-- .../Interactor/MapSearchInteractor.swift | 1 + .../HomeDataProvider/HomeDataProvider.swift | 2 +- .../HomeDataProviderImpl.swift | 42 ++++++++-- .../Interactor/ProfileInteractor.swift | 8 +- .../ServiceFactory/ServiceFactory.swift | 5 +- Nynja/StarActionDAO.swift | 2 +- Nynja/StarDAO.swift | 41 ++++++++-- Nynja/StarDAOFetchingArgs.swift | 28 +++++++ 17 files changed, 196 insertions(+), 102 deletions(-) create mode 100644 Nynja/DB/Extensions/ColumnDefinitionExtension.swift create mode 100644 Nynja/StarDAOFetchingArgs.swift diff --git a/Nynja.xcodeproj/project.pbxproj b/Nynja.xcodeproj/project.pbxproj index cb7e83ad7..06531497f 100644 --- a/Nynja.xcodeproj/project.pbxproj +++ b/Nynja.xcodeproj/project.pbxproj @@ -648,6 +648,8 @@ 4B7E93382170D1BC001558CF /* RootNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B7E93372170D1BC001558CF /* RootNavigationController.swift */; }; 4B7E933B2170D410001558CF /* ForwardSelectorWireFrame.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B7E933A2170D410001558CF /* ForwardSelectorWireFrame.swift */; }; 4B7E933E2170D4FF001558CF /* ServiceFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B7E933D2170D4FF001558CF /* ServiceFactory.swift */; }; + 4B87712D2192F2920014AD09 /* StarDAOFetchingArgs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B87712C2192F2920014AD09 /* StarDAOFetchingArgs.swift */; }; + 4B87712F2192F6940014AD09 /* ColumnDefinitionExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B87712E2192F6940014AD09 /* ColumnDefinitionExtension.swift */; }; 4B8996C8204ECE9B00DCB183 /* ContactDAO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8996C7204ECE9B00DCB183 /* ContactDAO.swift */; }; 4B8996CA204ECEA700DCB183 /* ContactDAOProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8996C9204ECEA700DCB183 /* ContactDAOProtocol.swift */; }; 4B8996CD204ED33400DCB183 /* StarDAOProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8996CC204ED33400DCB183 /* StarDAOProtocol.swift */; }; @@ -2922,6 +2924,8 @@ 4B7E93372170D1BC001558CF /* RootNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootNavigationController.swift; sourceTree = ""; }; 4B7E933A2170D410001558CF /* ForwardSelectorWireFrame.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ForwardSelectorWireFrame.swift; sourceTree = ""; }; 4B7E933D2170D4FF001558CF /* ServiceFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServiceFactory.swift; sourceTree = ""; }; + 4B87712C2192F2920014AD09 /* StarDAOFetchingArgs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StarDAOFetchingArgs.swift; sourceTree = ""; }; + 4B87712E2192F6940014AD09 /* ColumnDefinitionExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColumnDefinitionExtension.swift; sourceTree = ""; }; 4B8996C7204ECE9B00DCB183 /* ContactDAO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactDAO.swift; sourceTree = ""; }; 4B8996C9204ECEA700DCB183 /* ContactDAOProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactDAOProtocol.swift; sourceTree = ""; }; 4B8996CC204ED33400DCB183 /* StarDAOProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StarDAOProtocol.swift; sourceTree = ""; }; @@ -6959,6 +6963,14 @@ path = ServiceFactory; sourceTree = ""; }; + 4B87712B2192F27C0014AD09 /* StarDAO */ = { + isa = PBXGroup; + children = ( + 4B87712C2192F2920014AD09 /* StarDAOFetchingArgs.swift */, + ); + name = StarDAO; + sourceTree = ""; + }; 4B8996C6204ECE8500DCB183 /* Contact */ = { isa = PBXGroup; children = ( @@ -6972,6 +6984,7 @@ 4B8996CB204ED30B00DCB183 /* Star */ = { isa = PBXGroup; children = ( + 4B87712B2192F27C0014AD09 /* StarDAO */, 4B8996CC204ED33400DCB183 /* StarDAOProtocol.swift */, 4B8996CE204ED33D00DCB183 /* StarDAO.swift */, ); @@ -12072,6 +12085,7 @@ children = ( E745A24A20061AD400D7EF42 /* DatabaseExtension.swift */, E70938401FBEE488006CCDC6 /* TableDefinitionExtension.swift */, + 4B87712E2192F6940014AD09 /* ColumnDefinitionExtension.swift */, 8509FC802158E11000734D93 /* TableAlternationExtension.swift */, E74FD69C1FC5D06200656611 /* TransactionObserverExtension.swift */, A48BF1C120A1CA390076D892 /* Array+Table.swift */, @@ -16129,6 +16143,7 @@ 5BC1D38120D3B54B002A44B3 /* CallInfoView.swift in Sources */, 264638291FFFE835002590E6 /* RepliesInteractor.swift in Sources */, 85D66A2220BD970400FBD803 /* BBCodeElement.swift in Sources */, + 4B87712F2192F6940014AD09 /* ColumnDefinitionExtension.swift in Sources */, B723C634204DA54200884FFD /* SettingsDataAndStorageTableDataSource.swift in Sources */, FEA655E72167777E00B44029 /* SeedBackupWalletOutputParams.swift in Sources */, 851EBD7F20B418890065C644 /* StickersInputView.swift in Sources */, @@ -16571,6 +16586,7 @@ C921738220BADAFC00519A2D /* TextInputValidationService.swift in Sources */, 9763CCDFE5AF7B58C21CDED9 /* GroupStoragePresenter.swift in Sources */, 16A903BE16E0899FD3E5D232 /* GroupStorageInteractor.swift in Sources */, + 4B87712D2192F2920014AD09 /* StarDAOFetchingArgs.swift in Sources */, 85D66A0820BD963C00FBD803 /* MentionInputFilter.swift in Sources */, 8514F17A20EA219F00883513 /* ContextMenuArrowView.swift in Sources */, B3D0F59E1E7BDB7E485AE662 /* GroupStorageWireframe.swift in Sources */, diff --git a/Nynja/DB/Extensions/ColumnDefinitionExtension.swift b/Nynja/DB/Extensions/ColumnDefinitionExtension.swift new file mode 100644 index 000000000..6d6ce2399 --- /dev/null +++ b/Nynja/DB/Extensions/ColumnDefinitionExtension.swift @@ -0,0 +1,22 @@ +// +// ColumnDefinitionExtension.swift +// Nynja +// +// Created by Volodymyr Hryhoriev on 11/7/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import GRDBCipher + +extension ColumnDefinition { + + @discardableResult + func references(_ table: String, column: Column, onDelete deleteAction: Database.ForeignKeyAction? = nil, onUpdate updateAction: Database.ForeignKeyAction? = nil, deferred: Bool = false) -> ColumnDefinition { + return self.references( + table, + column: column.name, + onDelete: deleteAction, + onUpdate: updateAction, + deferred: deferred) + } +} diff --git a/Nynja/DB/Extensions/TableDefinitionExtension.swift b/Nynja/DB/Extensions/TableDefinitionExtension.swift index 3bcb30cd2..9fc91a0ba 100644 --- a/Nynja/DB/Extensions/TableDefinitionExtension.swift +++ b/Nynja/DB/Extensions/TableDefinitionExtension.swift @@ -10,6 +10,7 @@ import GRDBCipher extension TableDefinition { + @available(*, deprecated, message: "Use `func column(_ column: Column, _ type: Database.ColumnType? = nil) -> ColumnDefinition` instead") @discardableResult func column(_ desc: Describable, _ type: Database.ColumnType? = nil) -> ColumnDefinition { return column(desc.title, type) diff --git a/Nynja/DB/Extensions/TransactionObserverExtension.swift b/Nynja/DB/Extensions/TransactionObserverExtension.swift index 56355cff3..8fb5bf20d 100644 --- a/Nynja/DB/Extensions/TransactionObserverExtension.swift +++ b/Nynja/DB/Extensions/TransactionObserverExtension.swift @@ -13,5 +13,4 @@ extension TransactionObserver { func databaseWillCommit() throws {} func databaseDidCommit(_ db: Database) {} func databaseDidRollback(_ db: Database) {} - } diff --git a/Nynja/DB/Models/DBStar.swift b/Nynja/DB/Models/DBStar.swift index 126eac0b5..e833f1856 100644 --- a/Nynja/DB/Models/DBStar.swift +++ b/Nynja/DB/Models/DBStar.swift @@ -9,7 +9,7 @@ import Foundation import GRDBCipher -final class DBStar: Record, DBModelProtocol { +final class DBStar: Record, DBExtendedModelProtocol { var id: Int64? var clientId: String? var rosterId: Int64? @@ -18,7 +18,13 @@ final class DBStar: Record, DBModelProtocol { var status: String? + var serverModel: Star { + return Star(star: self) + } + + // MARK: - Mapping + init?(star: Star) { self.id = star.id self.clientId = star.client_id ?? IdBuilder(format: .starClientId) @@ -44,70 +50,39 @@ final class DBStar: Record, DBModelProtocol { } } + // MARK: - Record + override static var databaseTableName: String { return StarTable.name } required init(row: Row) { - id = row[StarTable.Column.id.title] - clientId = row[StarTable.Column.clientId.title] - rosterId = row[StarTable.Column.rosterId.title] - status = row[StarTable.Column.status.title] - messageID = row[StarTable.Column.messageId.title] + id = row[Column.id] + clientId = row[Column.clientId] + rosterId = row[Column.rosterId] + status = row[Column.status] + messageID = row[Column.messageId] super.init() } override func encode(to container: inout PersistenceContainer) { - container[StarTable.Column.id.title] = id - container[StarTable.Column.clientId.title] = clientId - container[StarTable.Column.rosterId.title] = rosterId - container[StarTable.Column.status.title] = status - container[StarTable.Column.messageId.title] = messageID + container[Column.id] = id + container[Column.clientId] = clientId + container[Column.rosterId] = rosterId + container[Column.status] = status + container[Column.messageId] = messageID } + // MARK: - DBModelProtocol + func saveAggregate(_ db: Database) throws { try message?.saveAggregate(db) messageID = message?.localId try self.save(db) } - static func star(_ db: Database, rowId: Int64) throws -> DBStar? { - let star = try DBStar.filter(Column.rowID == rowId).fetchOne(db) - try star?.construct(db) - return star - } - - static func star(_ db: Database, clientId: String) throws -> DBStar? { - let clientIdColumn = Column(StarTable.Column.clientId.title) - let star = try DBStar.filter(clientIdColumn == clientId).fetchOne(db) - try star?.construct(db) - return star - } - - static func stars(from db: Database, rosterId: Int64) throws -> [DBStar] { - let rosterIdColumn = Column(StarTable.Column.rosterId.title) - let statusColumn = Column(StarTable.Column.status.title) - - let filter: SQLExpressible = rosterIdColumn == rosterId && statusColumn !== Star.Status.remove.rawValue - let stars = try DBStar.filter(filter).fetchAll(db) - - try stars.forEach { try $0.construct(db) } - return stars - } - - static func undeliveredStars(from db: Database, rosterId: Int64) throws -> [DBStar] { - let rosterIdColumn = Column(StarTable.Column.rosterId.title) - let starIdColumn = Column(StarTable.Column.id.title) - - let filter: SQLExpressible = rosterIdColumn == rosterId && starIdColumn == nil - let stars = try DBStar.filter(filter).fetchAll(db) - - try stars.forEach { try $0.construct(db) } - return stars - } - func construct(_ db: Database) throws { if let messageId = messageID { message = try DBStarMessage.message(db, localId: messageId) @@ -119,4 +94,15 @@ final class DBStar: Record, DBModelProtocol { try self.message?.deleteAggregate(db) return try self.delete(db) } + + + // MARK: - Fetching + + static func stars(from db: Database, rosterId: Int64) throws -> [DBStar] { + return try DBStar + .filter(Column.rosterId == rosterId) + .filter(Column.status !== Star.Status.remove.rawValue) + .fetchAll(from: db) + } + } diff --git a/Nynja/DB/Models/DBStarAction.swift b/Nynja/DB/Models/DBStarAction.swift index 9e669edbb..db9561f7a 100644 --- a/Nynja/DB/Models/DBStarAction.swift +++ b/Nynja/DB/Models/DBStarAction.swift @@ -23,14 +23,14 @@ final class DBStarAction: Record, DBModelProtocol { } required init(row: Row) { - starLocalId = row[StarActionTable.Column.starId.title] - action = Action(rawValue: row[StarActionTable.Column.action.title])! + starLocalId = row[Column.starId] + action = Action(rawValue: row[Column.action])! super.init() } override func encode(to container: inout PersistenceContainer) { - container[StarActionTable.Column.starId.title] = starLocalId - container[StarActionTable.Column.action.title] = action.rawValue + container[Column.starId] = starLocalId + container[Column.action] = action.rawValue } override static var databaseTableName: String { @@ -38,8 +38,8 @@ final class DBStarAction: Record, DBModelProtocol { } static func starAction(_ db: Database, starLocalId: String) throws -> DBStarAction? { - let starIdColumn = Column(StarActionTable.Column.starId.title) - let filter: SQLExpressible = starIdColumn == starLocalId - return try DBStarAction.filter(filter).fetchOne(db) + return try DBStarAction + .filter(Column.starId == starLocalId) + .fetchOne(db) } } diff --git a/Nynja/DB/Tables/Base/SharedColumn.swift b/Nynja/DB/Tables/Base/SharedColumn.swift index 35621f8b0..8e5a83d75 100644 --- a/Nynja/DB/Tables/Base/SharedColumn.swift +++ b/Nynja/DB/Tables/Base/SharedColumn.swift @@ -9,13 +9,16 @@ import GRDBCipher extension Column { + static let id = Column("id") static let phoneId = Column("phoneId") + static let rosterId = Column("rosterId") + static let messageId = Column("messageId") + static let localId = Column("localId") + static let starId = Column("starId") + static let action = Column("action") + static let reader = Column("reader") static let created = Column("created") static let unread = Column("unread") static let status = Column("status") - static let messageId = Column("messageId") - static let rosterId = Column("rosterId") - static let localId = Column("localId") static let type = Column("type") - static let reader = Column("reader") } diff --git a/Nynja/DB/Tables/StarActionTable.swift b/Nynja/DB/Tables/StarActionTable.swift index e90649168..183fe3a8f 100644 --- a/Nynja/DB/Tables/StarActionTable.swift +++ b/Nynja/DB/Tables/StarActionTable.swift @@ -19,18 +19,9 @@ final class StarActionTable: Table { t.column(Column.starId, .text) .notNull() .primaryKey(onConflict: .replace, autoincrement: false) - .references(StarTable.name, column: StarTable.Column.clientId.title, onDelete: .cascade) + .references(StarTable.name, column: Column.clientId, onDelete: .cascade) t.column(Column.action, .text).notNull() } } } - -// MARK: Column -extension StarActionTable { - - enum Column: Int, Describable { - case starId - case action - } -} diff --git a/Nynja/DB/Tables/StarTable.swift b/Nynja/DB/Tables/StarTable.swift index 1768cacb6..6676cd175 100644 --- a/Nynja/DB/Tables/StarTable.swift +++ b/Nynja/DB/Tables/StarTable.swift @@ -25,14 +25,9 @@ final class StarTable: Table { } } -// MARK: - Column -extension StarTable { - - enum Column: Int, Describable { - case id - case clientId - case status - case rosterId - case messageId - } + +extension Column { + static let clientId = Column("clientId") } + + diff --git a/Nynja/Modules/MapSearch/Interactor/MapSearchInteractor.swift b/Nynja/Modules/MapSearch/Interactor/MapSearchInteractor.swift index d49dc2aaf..e0ccb32f7 100644 --- a/Nynja/Modules/MapSearch/Interactor/MapSearchInteractor.swift +++ b/Nynja/Modules/MapSearch/Interactor/MapSearchInteractor.swift @@ -49,6 +49,7 @@ class MapSearchInteractor: MapSearchInteractorInputProtocol { guard let id = StorageService.sharedInstance.rosterId else { return [] } + // TODO: rewrite as SQL script return StarDAO.fetchStars(rosterId: id).values.compactMap { $0.message?.files?.first?.placeId } } diff --git a/Nynja/Modules/Profile/Interactor/HomeDataProvider/HomeDataProvider.swift b/Nynja/Modules/Profile/Interactor/HomeDataProvider/HomeDataProvider.swift index 485eed8e3..6332d5fa0 100644 --- a/Nynja/Modules/Profile/Interactor/HomeDataProvider/HomeDataProvider.swift +++ b/Nynja/Modules/Profile/Interactor/HomeDataProvider/HomeDataProvider.swift @@ -7,7 +7,6 @@ // protocol HomeDataProvider { - var dbManager: DBManagerProtocol { get } var conversationsProviding: ConversationsProviding { get } var contactsProviding: ContactsProviding { get } @@ -16,4 +15,5 @@ protocol HomeDataProvider { func fetchChats(rosterId: Int64) -> [Contact] func fetchGroups(rosterId: Int64) -> [Room] func fetchHistory(rosterId: Int64, phoneId: String) -> [Contact] + func fetchStars(rosterId: Int64) -> [Star] } diff --git a/Nynja/Modules/Profile/Interactor/HomeDataProvider/HomeDataProviderImpl.swift b/Nynja/Modules/Profile/Interactor/HomeDataProvider/HomeDataProviderImpl.swift index 44ed0319b..33b8fb09d 100644 --- a/Nynja/Modules/Profile/Interactor/HomeDataProvider/HomeDataProviderImpl.swift +++ b/Nynja/Modules/Profile/Interactor/HomeDataProvider/HomeDataProviderImpl.swift @@ -9,23 +9,18 @@ import GRDBCipher class HomeDataProviderImpl: HomeDataProvider, InitializeInjectable { - typealias Dependencies = (services: Services, fields: Fields) - typealias Services = (dbManager: DBManagerProtocol, conversationsProviding: ConversationsProviding, contactsProviding: ContactsProviding) - struct Fields { let limit: Int } - - let dbManager: DBManagerProtocol let conversationsProviding: ConversationsProviding let contactsProviding: ContactsProviding let limit: Int required init(dependencies: Dependencies) { - dbManager = dependencies.services.dbManager conversationsProviding = dependencies.services.conversationsProviding contactsProviding = dependencies.services.contactsProviding limit = dependencies.fields.limit } // MARK: - HomeDataProvider + // MARK: - Conversations func fetchChats(rosterId: Int64) -> [Contact] { let fetcher = conversationsProviding.fetchChats(with:) @@ -49,6 +44,9 @@ class HomeDataProviderImpl: HomeDataProvider, InitializeInjectable { return ConversationsProvidingFetchingArgs(rosterId: rosterId, limit: limit) } + + // MARK: - History + func fetchHistory(rosterId: Int64, phoneId: String) -> [Contact] { let args = makeContactsProvidingFetchingArgs(rosterId: rosterId, phoneId: phoneId) return contactsProviding.fetchHistory(with: args) @@ -61,4 +59,36 @@ class HomeDataProviderImpl: HomeDataProvider, InitializeInjectable { statuses: [.request, .ignore], limit: limit) } + + + // MARK: - Stars + + func fetchStars(rosterId: Int64) -> [Star] { + let args = makeStarDAOFetchingArgs(rosterId: rosterId) + return StarDAO.fetchStars(with: args) + } + + private func makeStarDAOFetchingArgs(rosterId: Int64) -> StarDAOFetchingArgs { + return StarDAOFetchingArgs(rosterId: rosterId, + limit: limit, + orderColumns: [Column.created.desc]) + } +} + + +extension HomeDataProviderImpl { + + struct Dependencies { + let services: Services + let fields: Fields + } + + struct Services { + let conversationsProviding: ConversationsProviding + let contactsProviding: ContactsProviding + } + + struct Fields { + let limit: Int + } } diff --git a/Nynja/Modules/Profile/Interactor/ProfileInteractor.swift b/Nynja/Modules/Profile/Interactor/ProfileInteractor.swift index 09e113f84..f156b6a40 100644 --- a/Nynja/Modules/Profile/Interactor/ProfileInteractor.swift +++ b/Nynja/Modules/Profile/Interactor/ProfileInteractor.swift @@ -131,13 +131,9 @@ fileprivate extension ProfileInteractor { } func fetchStar() { - guard let id = StorageService.sharedInstance.rosterId else { - starred = [Star]() - return + performIfRosterIdExists { rosterId in + starred = homeDataProvider.fetchStars(rosterId: rosterId) } - - let stars = StarDAO.fetchStars(rosterId: id).values.sorted { $0.timestamp > $1.timestamp } - starred = Array(stars.prefix(presenter.elemsInSection)) } func fetchScheduledMessages() { diff --git a/Nynja/Services/ServiceFactory/ServiceFactory.swift b/Nynja/Services/ServiceFactory/ServiceFactory.swift index ca751bde1..41488064b 100644 --- a/Nynja/Services/ServiceFactory/ServiceFactory.swift +++ b/Nynja/Services/ServiceFactory/ServiceFactory.swift @@ -174,9 +174,8 @@ final class ServiceFactory: SharedServiceFactory, ServiceFactoryProtocol { func makeHomeDataProvider(limit: Int) -> HomeDataProvider { return HomeDataProviderImpl( - dependencies: ( - services: ( - dbManager: makeStorageService(), + dependencies: .init( + services: .init( conversationsProviding: makeConversationsProvider(), contactsProviding: makeContactsProvider() ), diff --git a/Nynja/StarActionDAO.swift b/Nynja/StarActionDAO.swift index ca97857e4..21c2585c7 100644 --- a/Nynja/StarActionDAO.swift +++ b/Nynja/StarActionDAO.swift @@ -24,7 +24,7 @@ final class StarActionDAO: StarActionDAOProtocol { } return dbManager.rowExists( in: StarActionTable.self, - where: "\(StarActionTable.Column.starId) = ?", + where: "\(Column.starId) = ?", arguments: [starLocalId] ) } diff --git a/Nynja/StarDAO.swift b/Nynja/StarDAO.swift index 239ee4757..aa879ed5f 100644 --- a/Nynja/StarDAO.swift +++ b/Nynja/StarDAO.swift @@ -6,24 +6,34 @@ // Copyright © 2018 TecSynt Solutions. All rights reserved. // +import GRDBCipher + class StarDAO: StarDAOProtocol { // MARK: - Fetch + static func fetchStarBy(rowId: Int64) -> DBStar? { return dbManager.fetch { db in - return try DBStar.star(db, rowId: rowId) + return try DBStar + .filter(Column.rowID == rowId) + .fetchOne(from: db) } } static func fetchStarBy(clientId: String) -> DBStar? { return dbManager.fetch { db in - return try DBStar.star(db, clientId: clientId) + return try DBStar + .filter(Column.clientId == clientId) + .fetchOne(from: db) } } static func fetchStars(rosterId: Int64) -> [String: Star] { - let stars = dbManager.fetch { db in - return try DBStar.stars(from: db, rosterId: rosterId) + let stars = dbManager.fetch { db -> [DBStar] in + let args = StarDAOFetchingArgs(rosterId: rosterId) + return try makeQuery(with: args) + .filter(Column.status != Star.Status.remove.rawValue) + .fetchAll(from: db) } var result: [String: Star] = [:] @@ -37,9 +47,26 @@ class StarDAO: StarDAOProtocol { return result } + static func fetchStars(with args: StarDAOFetchingArgs) -> [Star] { + return dbManager.fetch { db in + return try makeQuery(with: args) + .filter(Column.status != Star.Status.remove.rawValue) + .fetchAll(from: db) + }.serverModels + } + static func fetchUndeliveredStars(rosterId: Int64) -> [Star] { - return dbManager - .fetch { try DBStar.undeliveredStars(from: $0, rosterId: rosterId) } - .map { Star(star: $0) } + return dbManager.fetch { db -> [DBStar] in + let args = StarDAOFetchingArgs(rosterId: rosterId) + return try makeQuery(with: args) + .filter(Column.id == nil) + .fetchAll(from: db) + }.serverModels + } + + private static func makeQuery(with args: StarDAOFetchingArgs) -> QueryInterfaceRequest { + return DBStar.filter(Column.rosterId == args.rosterId) + .performIfValueExists(args.limit) { $0.limit($1, offset: args.offset) } + .performIfValueExists(args.orderColumns) { $0.order($1) } } } diff --git a/Nynja/StarDAOFetchingArgs.swift b/Nynja/StarDAOFetchingArgs.swift new file mode 100644 index 000000000..efa8bb74e --- /dev/null +++ b/Nynja/StarDAOFetchingArgs.swift @@ -0,0 +1,28 @@ +// +// StarDAOFetchingArgs.swift +// Nynja +// +// Created by Volodymyr Hryhoriev on 11/7/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import GRDBCipher + +struct StarDAOFetchingArgs { + let rosterId: Int64 + let limit: Int? + let offset: Int? + let orderColumns: [SQLOrderingTerm]? + + init( + rosterId: Int64, + limit: Int? = nil, + offset: Int? = nil, + orderColumns: [SQLOrderingTerm]? = nil) { + + self.rosterId = rosterId + self.limit = limit + self.offset = offset + self.orderColumns = orderColumns + } +} -- GitLab From 7914c26f9971ce908ae40d4414a0ca380be73ca6 Mon Sep 17 00:00:00 2001 From: Volodymyr Hryhoriev Date: Wed, 7 Nov 2018 14:38:02 +0200 Subject: [PATCH 16/68] 1. Create instance of `Column` for `JobTable`. 2. Make `Column` confirms to `CustomStringConvertible` in order to use it inside string interpolation. 3. Make `DBJob` confirms to `DBExtendedModelProtocol`. --- Nynja.xcodeproj/project.pbxproj | 4 ++ Nynja/DB/Extensions/ColumnExtension.swift | 16 +++++ Nynja/DB/Models/DBJob.swift | 81 ++++++++++++----------- Nynja/DB/Tables/Base/SharedColumn.swift | 11 ++- Nynja/DB/Tables/JobMessageTable.swift | 5 ++ Nynja/DB/Tables/JobTable.swift | 34 +++------- 6 files changed, 85 insertions(+), 66 deletions(-) create mode 100644 Nynja/DB/Extensions/ColumnExtension.swift diff --git a/Nynja.xcodeproj/project.pbxproj b/Nynja.xcodeproj/project.pbxproj index 06531497f..4ddde3bfd 100644 --- a/Nynja.xcodeproj/project.pbxproj +++ b/Nynja.xcodeproj/project.pbxproj @@ -650,6 +650,7 @@ 4B7E933E2170D4FF001558CF /* ServiceFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B7E933D2170D4FF001558CF /* ServiceFactory.swift */; }; 4B87712D2192F2920014AD09 /* StarDAOFetchingArgs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B87712C2192F2920014AD09 /* StarDAOFetchingArgs.swift */; }; 4B87712F2192F6940014AD09 /* ColumnDefinitionExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B87712E2192F6940014AD09 /* ColumnDefinitionExtension.swift */; }; + 4B877131219312570014AD09 /* ColumnExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B877130219312570014AD09 /* ColumnExtension.swift */; }; 4B8996C8204ECE9B00DCB183 /* ContactDAO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8996C7204ECE9B00DCB183 /* ContactDAO.swift */; }; 4B8996CA204ECEA700DCB183 /* ContactDAOProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8996C9204ECEA700DCB183 /* ContactDAOProtocol.swift */; }; 4B8996CD204ED33400DCB183 /* StarDAOProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8996CC204ED33400DCB183 /* StarDAOProtocol.swift */; }; @@ -2926,6 +2927,7 @@ 4B7E933D2170D4FF001558CF /* ServiceFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServiceFactory.swift; sourceTree = ""; }; 4B87712C2192F2920014AD09 /* StarDAOFetchingArgs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StarDAOFetchingArgs.swift; sourceTree = ""; }; 4B87712E2192F6940014AD09 /* ColumnDefinitionExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColumnDefinitionExtension.swift; sourceTree = ""; }; + 4B877130219312570014AD09 /* ColumnExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColumnExtension.swift; sourceTree = ""; }; 4B8996C7204ECE9B00DCB183 /* ContactDAO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactDAO.swift; sourceTree = ""; }; 4B8996C9204ECEA700DCB183 /* ContactDAOProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactDAOProtocol.swift; sourceTree = ""; }; 4B8996CC204ED33400DCB183 /* StarDAOProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StarDAOProtocol.swift; sourceTree = ""; }; @@ -12089,6 +12091,7 @@ 8509FC802158E11000734D93 /* TableAlternationExtension.swift */, E74FD69C1FC5D06200656611 /* TransactionObserverExtension.swift */, A48BF1C120A1CA390076D892 /* Array+Table.swift */, + 4B877130219312570014AD09 /* ColumnExtension.swift */, ); path = Extensions; sourceTree = ""; @@ -15291,6 +15294,7 @@ 2C95C06FF47CF32C3B35FF4F /* TutorialInteractor.swift in Sources */, 8EBFF1692004033F00CC4C25 /* GroupAudiosCell.swift in Sources */, 855AC540208E45AA00DC2335 /* StickerCellModel.swift in Sources */, + 4B877131219312570014AD09 /* ColumnExtension.swift in Sources */, 3A2A99831EFAD2FB002749B3 /* PageControl.swift in Sources */, E784388156A5228026955A54 /* TutorialWireframe.swift in Sources */, E73315F21FB0BB0300C273FF /* Array+Contact.swift in Sources */, diff --git a/Nynja/DB/Extensions/ColumnExtension.swift b/Nynja/DB/Extensions/ColumnExtension.swift new file mode 100644 index 000000000..298d22855 --- /dev/null +++ b/Nynja/DB/Extensions/ColumnExtension.swift @@ -0,0 +1,16 @@ +// +// ColumnExtension.swift +// Nynja +// +// Created by Volodymyr Hryhoriev on 11/7/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import GRDBCipher + +extension Column: CustomStringConvertible { + + public var description: String { + return name + } +} diff --git a/Nynja/DB/Models/DBJob.swift b/Nynja/DB/Models/DBJob.swift index 90c79a496..986b52457 100644 --- a/Nynja/DB/Models/DBJob.swift +++ b/Nynja/DB/Models/DBJob.swift @@ -9,7 +9,7 @@ import Foundation import GRDBCipher -class DBJob: Record, DBModelProtocol { +class DBJob: Record, DBExtendedModelProtocol { enum JobType: Int { case schedule = 0 @@ -28,8 +28,13 @@ class DBJob: Record, DBModelProtocol { var features : [DBFeature] = [] var type: JobType = .schedule + var serverModel: Job { + return Job(job: self) + } + // MARK: - Mapping + init?(job: Job) { super.init() @@ -47,15 +52,15 @@ class DBJob: Record, DBModelProtocol { } required init(row: Row) { - self.id = row[JobTable.Column.id.title] - self.serverId = row[JobTable.Column.serverId.title] - self.container = row[JobTable.Column.container.title] - self.feedId = row[JobTable.Column.feedId.title] - self.prev = row[JobTable.Column.prev.title] - self.next = row[JobTable.Column.next.title] - self.time = row[JobTable.Column.time.title] - self.status = row[JobTable.Column.status.title] - let jobType: Int? = row[JobTable.Column.type.title] + self.id = row[Column.id] + self.serverId = row[Column.serverId] + self.container = row[Column.container] + self.feedId = row[Column.feedId] + self.prev = row[Column.prev] + self.next = row[Column.next] + self.time = row[Column.time] + self.status = row[Column.status] + let jobType: Int? = row[Column.type] if let jobType = jobType, let type = JobType(rawValue: jobType) { self.type = type } @@ -68,15 +73,15 @@ class DBJob: Record, DBModelProtocol { } override func encode(to container: inout PersistenceContainer) { - container[JobTable.Column.id.title] = self.id - container[JobTable.Column.serverId.title] = self.serverId - container[JobTable.Column.container.title] = self.container - container[JobTable.Column.feedId.title] = self.feedId - container[JobTable.Column.prev.title] = self.prev - container[JobTable.Column.next.title] = self.next - container[JobTable.Column.time.title] = self.time - container[JobTable.Column.status.title] = self.status - container[JobTable.Column.type.title] = self.type.rawValue + container[Column.id] = self.id + container[Column.serverId] = self.serverId + container[Column.container] = self.container + container[Column.feedId] = self.feedId + container[Column.prev] = self.prev + container[Column.next] = self.next + container[Column.time] = self.time + container[Column.status] = self.status + container[Column.type] = self.type.rawValue } override func didInsert(with rowID: Int64, for column: String?) { @@ -85,26 +90,21 @@ class DBJob: Record, DBModelProtocol { // MARK: - Fetch static func job(from db: Database, rowId: Int64) throws -> DBJob? { - let job = try DBJob.filter(Column.rowID == rowId).fetchOne(db) - try job?.construct(db) - return job + return try DBJob.filter(Column.rowID == rowId).fetchOne(from: db) } static func job(_ db: Database, serverId: Int64) throws -> DBJob? { - let serverIdColumn = Column(JobTable.Column.serverId.title) - let job = try DBJob.filter(serverIdColumn == serverId).fetchOne(db) - try job?.construct(db) - return job + return try DBJob.filter(Column.serverId == serverId).fetchOne(from: db) } static func jobs(_ db: Database, filter: SQLExpressible? = nil) throws -> [DBJob] { - let jobs = try (filter == nil ? DBJob.fetchAll(db) : DBJob.filter(filter!).fetchAll(db)) - try jobs.forEach { try $0.construct(db) } - return jobs + return try (filter == nil ? + DBJob.all().fetchAll(from: db) : // TODO: need to think + DBJob.filter(filter!).fetchAll(from: db)) } static func jobs(_ db: Database, type: JobType) throws -> [DBJob] { - return try jobs(db, filter: Column(JobTable.Column.type.title) == type.rawValue) + return try jobs(db, filter: Column.type == type.rawValue) } static func jobs(_ db: Database, messageLocalId: String) throws -> [DBJob] { @@ -160,11 +160,12 @@ class DBJob: Record, DBModelProtocol { // MARK: - Private private func fetchId(_ db: Database) throws -> Int64? { - let idColumn = Column(JobTable.Column.id.title) - let serverIdColumn = Column(JobTable.Column.serverId.title) - - let predicate = (serverId != nil && serverIdColumn == self.serverId) - return try DBJob.filter(predicate).select(idColumn).asRequest(of: Int64.self).fetchOne(db) + let predicate = (serverId != nil && Column.serverId == serverId) + return try DBJob + .filter(predicate) + .select(Column.id) + .asRequest(of: Int64.self) + .fetchOne(db) } private func deleteMessages(_ db: Database, jobId: Int64) throws { @@ -178,17 +179,17 @@ class DBJob: Record, DBModelProtocol { } static func request(jobId: Int64) -> QueryInterfaceRequest { - return DBJob.filter(Column(JobTable.Column.id.title) == jobId) + return DBJob.filter(Column.id == jobId) } private static func sqlJobs(for messageLocalId: String, type: JobType = .schedule) -> String { let jobTable = JobTable.name - let jobType = "\(jobTable).\(JobTable.Column.type.title)" - let jobId = "\(jobTable).\(JobTable.Column.id.title)" + let jobType = "\(jobTable).\(Column.type)" + let jobId = "\(jobTable).\(Column.id)" let jobMessageTable = JobMessageTable.name - let jodMessageJobId = "\(jobMessageTable).\(JobMessageTable.Column.jobId.title)" - let jobMessageLocalId = "\(jobMessageTable).\(JobMessageTable.Column.localId.title)" + let jodMessageJobId = "\(jobMessageTable).\(Column.jobId)" + let jobMessageLocalId = "\(jobMessageTable).\(Column.localId)" let sql = """ select \(jobTable).* diff --git a/Nynja/DB/Tables/Base/SharedColumn.swift b/Nynja/DB/Tables/Base/SharedColumn.swift index 8e5a83d75..45050efac 100644 --- a/Nynja/DB/Tables/Base/SharedColumn.swift +++ b/Nynja/DB/Tables/Base/SharedColumn.swift @@ -13,12 +13,21 @@ extension Column { static let phoneId = Column("phoneId") static let rosterId = Column("rosterId") static let messageId = Column("messageId") + static let serverId = Column("serverId") static let localId = Column("localId") static let starId = Column("starId") + static let feedId = Column("feedId") + + static let container = Column("container") static let action = Column("action") static let reader = Column("reader") - static let created = Column("created") static let unread = Column("unread") + static let created = Column("created") + static let time = Column("time") + static let status = Column("status") static let type = Column("type") + + static let next = Column("next") + static let prev = Column("prev") } diff --git a/Nynja/DB/Tables/JobMessageTable.swift b/Nynja/DB/Tables/JobMessageTable.swift index cbd6bdb48..5f02501d1 100644 --- a/Nynja/DB/Tables/JobMessageTable.swift +++ b/Nynja/DB/Tables/JobMessageTable.swift @@ -60,3 +60,8 @@ extension JobMessageTable { case jobId } } + + +extension Column { + static let jobId = Column("jobId") +} diff --git a/Nynja/DB/Tables/JobTable.swift b/Nynja/DB/Tables/JobTable.swift index ce966f7f0..4182a2264 100644 --- a/Nynja/DB/Tables/JobTable.swift +++ b/Nynja/DB/Tables/JobTable.swift @@ -16,31 +16,15 @@ final class JobTable: Table { static func create(in db: Database) throws { try db.create(self) { t in - t.column(JobTable.Column.id, .integer).primaryKey(onConflict: nil, autoincrement: true) - t.column(JobTable.Column.serverId, .integer) - t.column(JobTable.Column.container, .text) - t.column(JobTable.Column.feedId, .text) - t.column(JobTable.Column.prev, .integer) - t.column(JobTable.Column.next, .integer) - t.column(JobTable.Column.time, .integer) - t.column(JobTable.Column.status, .text) - t.column(JobTable.Column.type, .integer).notNull().defaults(to: DBJob.JobType.schedule.rawValue) + t.column(Column.id, .integer).primaryKey(onConflict: nil, autoincrement: true) + t.column(Column.serverId, .integer) + t.column(Column.container, .text) + t.column(Column.feedId, .text) + t.column(Column.prev, .integer) + t.column(Column.next, .integer) + t.column(Column.time, .integer) + t.column(Column.status, .text) + t.column(Column.type, .integer).notNull().defaults(to: DBJob.JobType.schedule.rawValue) } } } - -// MARK: - Column -extension JobTable { - - enum Column: Int, Describable { - case id - case serverId - case container - case feedId - case prev - case next - case time - case status - case type - } -} -- GitLab From 9a5f50ebc4ad805823120df62424df9d8cf78b64 Mon Sep 17 00:00:00 2001 From: Volodymyr Hryhoriev Date: Wed, 7 Nov 2018 16:02:13 +0200 Subject: [PATCH 17/68] Move out extra extensions from the `DBModelProtocol` for to separate ones. --- Nynja.xcodeproj/project.pbxproj | 16 +++++ Nynja/DB/Extensions/Array+Table.swift | 1 - .../QueryInterfaceRequestExtension.swift | 23 ++++++++ .../DB/Extensions/TypedRequestExtension.swift | 33 +++++++++++ .../Models/Base/DBExtendedModelProtocol.swift | 11 ++++ Nynja/DB/Models/Base/DBModelProtocol.swift | 59 ------------------- .../ServerModel/SercerModelConvertible.swift | 19 ++++++ 7 files changed, 102 insertions(+), 60 deletions(-) create mode 100644 Nynja/DB/Extensions/QueryInterfaceRequestExtension.swift create mode 100644 Nynja/DB/Extensions/TypedRequestExtension.swift create mode 100644 Nynja/DB/Models/Base/DBExtendedModelProtocol.swift create mode 100644 Nynja/ServerModel/SercerModelConvertible.swift diff --git a/Nynja.xcodeproj/project.pbxproj b/Nynja.xcodeproj/project.pbxproj index 4ddde3bfd..3b2bbafa3 100644 --- a/Nynja.xcodeproj/project.pbxproj +++ b/Nynja.xcodeproj/project.pbxproj @@ -651,6 +651,10 @@ 4B87712D2192F2920014AD09 /* StarDAOFetchingArgs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B87712C2192F2920014AD09 /* StarDAOFetchingArgs.swift */; }; 4B87712F2192F6940014AD09 /* ColumnDefinitionExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B87712E2192F6940014AD09 /* ColumnDefinitionExtension.swift */; }; 4B877131219312570014AD09 /* ColumnExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B877130219312570014AD09 /* ColumnExtension.swift */; }; + 4B877133219314D50014AD09 /* TypedRequestExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B877132219314D50014AD09 /* TypedRequestExtension.swift */; }; + 4B8771352193154E0014AD09 /* DBExtendedModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8771342193154E0014AD09 /* DBExtendedModelProtocol.swift */; }; + 4B877137219315770014AD09 /* QueryInterfaceRequestExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B877136219315770014AD09 /* QueryInterfaceRequestExtension.swift */; }; + 4B877139219315AA0014AD09 /* SercerModelConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B877138219315AA0014AD09 /* SercerModelConvertible.swift */; }; 4B8996C8204ECE9B00DCB183 /* ContactDAO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8996C7204ECE9B00DCB183 /* ContactDAO.swift */; }; 4B8996CA204ECEA700DCB183 /* ContactDAOProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8996C9204ECEA700DCB183 /* ContactDAOProtocol.swift */; }; 4B8996CD204ED33400DCB183 /* StarDAOProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8996CC204ED33400DCB183 /* StarDAOProtocol.swift */; }; @@ -2928,6 +2932,10 @@ 4B87712C2192F2920014AD09 /* StarDAOFetchingArgs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StarDAOFetchingArgs.swift; sourceTree = ""; }; 4B87712E2192F6940014AD09 /* ColumnDefinitionExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColumnDefinitionExtension.swift; sourceTree = ""; }; 4B877130219312570014AD09 /* ColumnExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColumnExtension.swift; sourceTree = ""; }; + 4B877132219314D50014AD09 /* TypedRequestExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TypedRequestExtension.swift; sourceTree = ""; }; + 4B8771342193154E0014AD09 /* DBExtendedModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DBExtendedModelProtocol.swift; sourceTree = ""; }; + 4B877136219315770014AD09 /* QueryInterfaceRequestExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueryInterfaceRequestExtension.swift; sourceTree = ""; }; + 4B877138219315AA0014AD09 /* SercerModelConvertible.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SercerModelConvertible.swift; sourceTree = ""; }; 4B8996C7204ECE9B00DCB183 /* ContactDAO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactDAO.swift; sourceTree = ""; }; 4B8996C9204ECEA700DCB183 /* ContactDAOProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactDAOProtocol.swift; sourceTree = ""; }; 4B8996CC204ED33400DCB183 /* StarDAOProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StarDAOProtocol.swift; sourceTree = ""; }; @@ -10270,6 +10278,7 @@ A4166F5F205FEA120008F231 /* MessageLink.swift */, 26588E6620A20E49000D3E1A /* Customizable.swift */, 2651093E20ADB81100F1B38B /* NotificationSettingProtocol.swift */, + 4B877138219315AA0014AD09 /* SercerModelConvertible.swift */, ); path = ServerModel; sourceTree = ""; @@ -12092,6 +12101,8 @@ E74FD69C1FC5D06200656611 /* TransactionObserverExtension.swift */, A48BF1C120A1CA390076D892 /* Array+Table.swift */, 4B877130219312570014AD09 /* ColumnExtension.swift */, + 4B877132219314D50014AD09 /* TypedRequestExtension.swift */, + 4B877136219315770014AD09 /* QueryInterfaceRequestExtension.swift */, ); path = Extensions; sourceTree = ""; @@ -12180,6 +12191,7 @@ E7C9CEC91FCC27A30090C2E0 /* FeedProtocol.swift */, E7AE41651FCC498800C3ED5D /* FeedType.swift */, E76462881FCD64790091FC2E /* DBModelProtocol.swift */, + 4B8771342193154E0014AD09 /* DBExtendedModelProtocol.swift */, ); path = Base; sourceTree = ""; @@ -15726,6 +15738,7 @@ A4688DFC20652DE30013660D /* StorageChange.swift in Sources */, 5683555B8382F7F37FEE1AF5 /* ProfileWireframe.swift in Sources */, A42D51AC206A361400EEB952 /* Auth.swift in Sources */, + 4B877139219315AA0014AD09 /* SercerModelConvertible.swift in Sources */, A42D52CD206A53AB00EEB952 /* chain_Spec.swift in Sources */, 8580BACA20BD983400239D9D /* MentionTransitionProtocol.swift in Sources */, A44B4D5B20CE9BDF00CA700A /* ImageCell.swift in Sources */, @@ -15869,6 +15882,7 @@ A4679B8920B2DA550021FE9C /* Array+ChannelSubscriber.swift in Sources */, A4ED79AC20C7056C00A41F67 /* AllChannelsItemsFactory.swift in Sources */, E707C4AF1FA0F6E700B86137 /* ProfileActionCell.swift in Sources */, + 4B877137219315770014AD09 /* QueryInterfaceRequestExtension.swift in Sources */, 2648C4172069B52100863614 /* ChangeNumberStep2Wireframe.swift in Sources */, A45F111A20B4218D00F45004 /* MessageImageView.swift in Sources */, 8520040420D4F311007C0036 /* StickerPreviewAnimator.swift in Sources */, @@ -16010,6 +16024,7 @@ 00102F42202C914400A877A9 /* NynjaCalendarCell.swift in Sources */, 853FB0662049B193000996C5 /* SupportPresenter.swift in Sources */, 267BE2BA1FE13AB600C47E18 /* ParticipantsWireframe.swift in Sources */, + 4B8771352193154E0014AD09 /* DBExtendedModelProtocol.swift in Sources */, F11786CC20A8E4FD007A9A1B /* CameraVideoPreviewViewController.swift in Sources */, A408A0BA20C174040029F54B /* ChannelsListPresenter.swift in Sources */, 850FC60F203310D200832D87 /* SelectionAvatarView.swift in Sources */, @@ -16325,6 +16340,7 @@ 4B1D7E092029D86600703228 /* CreateGroupItemsFactory.swift in Sources */, 85052E5720D1A90D00BCC386 /* StickerImagePreviewView.swift in Sources */, 8580BACC20BD984500239D9D /* MessageEditInfo.swift in Sources */, + 4B877133219314D50014AD09 /* TypedRequestExtension.swift in Sources */, 4B2B1558218F5423000E4916 /* SharedColumn.swift in Sources */, 850FC5FA2032F64100832D87 /* ForwardSelectorWireFrame.swift in Sources */, 3AA4E6ACDBCB060172A7A279 /* FavoritesProtocols.swift in Sources */, diff --git a/Nynja/DB/Extensions/Array+Table.swift b/Nynja/DB/Extensions/Array+Table.swift index c85191d9d..1cd7aa1ad 100644 --- a/Nynja/DB/Extensions/Array+Table.swift +++ b/Nynja/DB/Extensions/Array+Table.swift @@ -11,5 +11,4 @@ extension Array where Element == Table.Type { var names: [String] { return self.map { $0.name } } - } diff --git a/Nynja/DB/Extensions/QueryInterfaceRequestExtension.swift b/Nynja/DB/Extensions/QueryInterfaceRequestExtension.swift new file mode 100644 index 000000000..65f82c707 --- /dev/null +++ b/Nynja/DB/Extensions/QueryInterfaceRequestExtension.swift @@ -0,0 +1,23 @@ +// +// QueryInterfaceRequestExtension.swift +// Nynja +// +// Created by Volodymyr Hryhoriev on 11/7/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import GRDBCipher + +extension QueryInterfaceRequest { + + func performIfValueExists( + _ value: V?, + closure: (QueryInterfaceRequest, V) -> QueryInterfaceRequest) -> QueryInterfaceRequest { + + guard let value = value else { + return self + } + + return closure(self, value) + } +} diff --git a/Nynja/DB/Extensions/TypedRequestExtension.swift b/Nynja/DB/Extensions/TypedRequestExtension.swift new file mode 100644 index 000000000..4a55d9ee7 --- /dev/null +++ b/Nynja/DB/Extensions/TypedRequestExtension.swift @@ -0,0 +1,33 @@ +// +// TypedRequestExtension.swift +// Nynja +// +// Created by Volodymyr Hryhoriev on 11/7/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import GRDBCipher + +enum DBModelFetchingKind { + case plain + case full +} + +extension TypedRequest where RowDecoder: DBModelProtocol { + + func fetchAll(kind: DBModelFetchingKind = .full, from db: Database) throws -> [RowDecoder] { + let models = try self.fetchAll(db) + if kind == .full { + try models.construct(db) + } + return models + } + + func fetchOne(kind: DBModelFetchingKind = .full, from db: Database) throws -> RowDecoder? { + let model = try self.fetchOne(db) + if kind == .full { + try model?.construct(db) + } + return model + } +} diff --git a/Nynja/DB/Models/Base/DBExtendedModelProtocol.swift b/Nynja/DB/Models/Base/DBExtendedModelProtocol.swift new file mode 100644 index 000000000..1090e9f0c --- /dev/null +++ b/Nynja/DB/Models/Base/DBExtendedModelProtocol.swift @@ -0,0 +1,11 @@ +// +// DBExtendedModelProtocol.swift +// Nynja +// +// Created by Volodymyr Hryhoriev on 11/7/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +protocol DBExtendedModelProtocol: DBModelProtocol, ServerModelConvertible { + +} diff --git a/Nynja/DB/Models/Base/DBModelProtocol.swift b/Nynja/DB/Models/Base/DBModelProtocol.swift index 466c19a00..f88a8db58 100644 --- a/Nynja/DB/Models/Base/DBModelProtocol.swift +++ b/Nynja/DB/Models/Base/DBModelProtocol.swift @@ -39,62 +39,3 @@ extension Array where Element: DBModelProtocol { try self.forEach { try $0.construct(db) } } } - - -// MARK: - Construct - -enum DBModelFetchingKind { - case plain - case full -} - -extension TypedRequest where RowDecoder: DBModelProtocol { - - func fetchAll(kind: DBModelFetchingKind = .full, from db: Database) throws -> [RowDecoder] { - let models = try self.fetchAll(db) - if kind == .full { - try models.construct(db) - } - return models - } - - func fetchOne(kind: DBModelFetchingKind = .full, from db: Database) throws -> RowDecoder? { - let model = try self.fetchOne(db) - if kind == .full { - try model?.construct(db) - } - return model - } -} - -protocol ServerModelConvertible { - associatedtype ServerModel - var serverModel: ServerModel { get } -} - -protocol DBExtendedModelProtocol: DBModelProtocol, ServerModelConvertible {} - -extension Array where Element: ServerModelConvertible { - - var serverModels: [Element.ServerModel] { - return self.map { $0.serverModel } - } -} - - -// MARK: - QueryInterfaceRequest - -extension QueryInterfaceRequest { - - func performIfValueExists( - _ value: V?, - closure: (QueryInterfaceRequest, V) -> QueryInterfaceRequest) -> QueryInterfaceRequest { - - guard let value = value else { - return self - } - - return closure(self, value) - } - -} diff --git a/Nynja/ServerModel/SercerModelConvertible.swift b/Nynja/ServerModel/SercerModelConvertible.swift new file mode 100644 index 000000000..99c695c19 --- /dev/null +++ b/Nynja/ServerModel/SercerModelConvertible.swift @@ -0,0 +1,19 @@ +// +// SercerModelConvertible.swift +// Nynja +// +// Created by Volodymyr Hryhoriev on 11/7/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +protocol ServerModelConvertible { + associatedtype ServerModel + var serverModel: ServerModel { get } +} + +extension Array where Element: ServerModelConvertible { + + var serverModels: [Element.ServerModel] { + return self.map { $0.serverModel } + } +} -- GitLab From 027f478a90cbc15d6026814ce4c0115670e3e6f8 Mon Sep 17 00:00:00 2001 From: Volodymyr Hryhoriev Date: Wed, 7 Nov 2018 19:02:21 +0200 Subject: [PATCH 18/68] Make paginated fetching of `Job` models. --- Nynja.xcodeproj/project.pbxproj | 16 ++++ Nynja/DB/Models/DBJob.swift | 1 - Nynja/DB/QueryArgs.swift | 88 +++++++++++++++++++ Nynja/DBJobQueryArgs.swift | 20 +++++ Nynja/JobDAO.swift | 27 +++--- .../HomeDataProvider/HomeDataProvider.swift | 1 + .../HomeDataProviderImpl.swift | 20 +++++ .../Interactor/ProfileInteractor.swift | 6 +- 8 files changed, 159 insertions(+), 20 deletions(-) create mode 100644 Nynja/DB/QueryArgs.swift create mode 100644 Nynja/DBJobQueryArgs.swift diff --git a/Nynja.xcodeproj/project.pbxproj b/Nynja.xcodeproj/project.pbxproj index 3b2bbafa3..1b250cf91 100644 --- a/Nynja.xcodeproj/project.pbxproj +++ b/Nynja.xcodeproj/project.pbxproj @@ -655,6 +655,8 @@ 4B8771352193154E0014AD09 /* DBExtendedModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8771342193154E0014AD09 /* DBExtendedModelProtocol.swift */; }; 4B877137219315770014AD09 /* QueryInterfaceRequestExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B877136219315770014AD09 /* QueryInterfaceRequestExtension.swift */; }; 4B877139219315AA0014AD09 /* SercerModelConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B877138219315AA0014AD09 /* SercerModelConvertible.swift */; }; + 4B87713B219328780014AD09 /* QueryArgs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B87713A219328780014AD09 /* QueryArgs.swift */; }; + 4B87713E219350940014AD09 /* DBJobQueryArgs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B87713D219350940014AD09 /* DBJobQueryArgs.swift */; }; 4B8996C8204ECE9B00DCB183 /* ContactDAO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8996C7204ECE9B00DCB183 /* ContactDAO.swift */; }; 4B8996CA204ECEA700DCB183 /* ContactDAOProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8996C9204ECEA700DCB183 /* ContactDAOProtocol.swift */; }; 4B8996CD204ED33400DCB183 /* StarDAOProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8996CC204ED33400DCB183 /* StarDAOProtocol.swift */; }; @@ -2936,6 +2938,8 @@ 4B8771342193154E0014AD09 /* DBExtendedModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DBExtendedModelProtocol.swift; sourceTree = ""; }; 4B877136219315770014AD09 /* QueryInterfaceRequestExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueryInterfaceRequestExtension.swift; sourceTree = ""; }; 4B877138219315AA0014AD09 /* SercerModelConvertible.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SercerModelConvertible.swift; sourceTree = ""; }; + 4B87713A219328780014AD09 /* QueryArgs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueryArgs.swift; sourceTree = ""; }; + 4B87713D219350940014AD09 /* DBJobQueryArgs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DBJobQueryArgs.swift; sourceTree = ""; }; 4B8996C7204ECE9B00DCB183 /* ContactDAO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactDAO.swift; sourceTree = ""; }; 4B8996C9204ECEA700DCB183 /* ContactDAOProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactDAOProtocol.swift; sourceTree = ""; }; 4B8996CC204ED33400DCB183 /* StarDAOProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StarDAOProtocol.swift; sourceTree = ""; }; @@ -6981,6 +6985,14 @@ name = StarDAO; sourceTree = ""; }; + 4B87713C219350830014AD09 /* Entity */ = { + isa = PBXGroup; + children = ( + 4B87713D219350940014AD09 /* DBJobQueryArgs.swift */, + ); + name = Entity; + sourceTree = ""; + }; 4B8996C6204ECE8500DCB183 /* Contact */ = { isa = PBXGroup; children = ( @@ -7004,6 +7016,7 @@ 4B8996D6204EDA5E00DCB183 /* Job */ = { isa = PBXGroup; children = ( + 4B87713C219350830014AD09 /* Entity */, 4B8996D7204EDA7700DCB183 /* JobDAOProtocol.swift */, 4B8996D9204EDA9B00DCB183 /* JobDAO.swift */, ); @@ -12000,6 +12013,7 @@ E74FD69B1FC5D04A00656611 /* Extensions */, E73CEF0F1FC2F52200246066 /* Models */, E7417E971FBED90600E5C124 /* Tables */, + 4B87713A219328780014AD09 /* QueryArgs.swift */, ); path = DB; sourceTree = ""; @@ -14925,6 +14939,7 @@ 262D43872033417F002F1E45 /* FriendExtansion+BERT.swift in Sources */, 2600CCC1216D47DC00EDC9C3 /* OptionallyActionCellViewModel.swift in Sources */, FE58F9B1208F00FE004AFDD3 /* MessageEditActionTable.swift in Sources */, + 4B87713B219328780014AD09 /* QueryArgs.swift in Sources */, F105C6A0209F71BF0091786A /* CameraInteractor.swift in Sources */, 4B4266BA204D898900194BC1 /* ForwardSelectorDisplayMode.swift in Sources */, 8572C3B92092364C00E4840C /* StickerPackageDataSource.swift in Sources */, @@ -16243,6 +16258,7 @@ A42D51B2206A361400EEB952 /* p2p.swift in Sources */, FEA655FB2167777F00B44029 /* TransferDetailsWireFrame.swift in Sources */, FEA655E32167777E00B44029 /* SeedBackupWalletCollectionViewCell.swift in Sources */, + 4B87713E219350940014AD09 /* DBJobQueryArgs.swift in Sources */, A4679B8720B2DA550021FE9C /* Array+ItemModels.swift in Sources */, A45F112720B4218D00F45004 /* MessageRepliedView.swift in Sources */, 85788C3C204422FB003600C9 /* BuildNumberProtocols.swift in Sources */, diff --git a/Nynja/DB/Models/DBJob.swift b/Nynja/DB/Models/DBJob.swift index 986b52457..f70e69738 100644 --- a/Nynja/DB/Models/DBJob.swift +++ b/Nynja/DB/Models/DBJob.swift @@ -200,5 +200,4 @@ class DBJob: Record, DBExtendedModelProtocol { return sql } - } diff --git a/Nynja/DB/QueryArgs.swift b/Nynja/DB/QueryArgs.swift new file mode 100644 index 000000000..ea1417aea --- /dev/null +++ b/Nynja/DB/QueryArgs.swift @@ -0,0 +1,88 @@ +// +// QueryArgs.swift +// Nynja +// +// Created by Volodymyr Hryhoriev on 11/7/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import GRDBCipher + +protocol QueryRequestMakeable { + func makeRequest() -> Request +} + +protocol TypedQueryRequestMakeable { + associatedtype Model: DBModelProtocol + func makeTypedRequest() -> QueryInterfaceRequest +} + +protocol QueryArgs: QueryRequestMakeable { + var limit: Int? { get } + var offset: Int? { get } + var orderingTerms: [SQLOrderingTerm]? { get } +} + +typealias QueryFilter = (QueryInterfaceRequest) -> QueryInterfaceRequest + +protocol CompositeQueryArgs: TypedQueryRequestMakeable { + var args: AnyQueryArgs { get } +} + +struct AnyQueryArgs: QueryArgs, TypedQueryRequestMakeable { + let limit: Int? + let offset: Int? + let orderingTerms: [SQLOrderingTerm]? + + private(set) var filters: [QueryFilter] = [] + + init(limit: Int? = nil, + offset: Int? = nil, + orderingTerms: [SQLOrderingTerm]? = nil, + filters: [QueryFilter] = []) { + self.limit = limit + self.offset = offset + self.orderingTerms = orderingTerms + self.filters = filters + } + + init(args: QueryArgs, filters: [QueryFilter] = []) { + self.init( + limit: args.limit, + offset: args.offset, + orderingTerms: args.orderingTerms) + } + + func filter(_ filter: @escaping QueryFilter) -> AnyQueryArgs { + var filters = self.filters + filters.append(filter) + return AnyQueryArgs(args: self, filters: filters) + } + + func makeTypedRequest() -> QueryInterfaceRequest { + var query = T.all() + .performIfValueExists(limit) { $0.limit($1, offset: offset) } + .performIfValueExists(orderingTerms) { $0.order($1) } + + filters.forEach { filter in + query = filter(query) + } + + return query + } + + func makeRequest() -> Request { + return makeTypedRequest() + } +} + +class DAO { + let dbManager = StorageService.sharedInstance + + func fetch(with maker: U, kind: DBModelFetchingKind = .full) -> [T] where U.Model == T { + return dbManager.fetch { db in + return try maker.makeTypedRequest().fetchAll(kind: kind, from: db) + } + } +} + diff --git a/Nynja/DBJobQueryArgs.swift b/Nynja/DBJobQueryArgs.swift new file mode 100644 index 000000000..84bf392c6 --- /dev/null +++ b/Nynja/DBJobQueryArgs.swift @@ -0,0 +1,20 @@ +// +// DBJobQueryArgs.swift +// Nynja +// +// Created by Volodymyr Hryhoriev on 11/7/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import GRDBCipher + +struct DBJobQueryArgs: CompositeQueryArgs { + let type: DBJob.JobType + let args: AnyQueryArgs + + func makeTypedRequest() -> QueryInterfaceRequest { + return AnyQueryArgs(args: args) + .filter { $0.filter(Column.type == self.type.rawValue) } + .makeTypedRequest() + } +} diff --git a/Nynja/JobDAO.swift b/Nynja/JobDAO.swift index 560f682c6..89694a89f 100644 --- a/Nynja/JobDAO.swift +++ b/Nynja/JobDAO.swift @@ -9,8 +9,9 @@ import GRDBCipher class JobDAO: JobDAOProtocol { - + // MARK: - Fetch + static func fetchJob(for serverId: Int64) -> DBJob? { return dbManager.fetch { db in return try DBJob.job(db, serverId: serverId) @@ -18,18 +19,13 @@ class JobDAO: JobDAOProtocol { } static func findJob(by rowId: Int64) -> Job? { - guard let job = dbManager.fetch({ db in - return try DBJob.job(from: db, rowId: rowId) - }) else { - return nil + return dbManager.fetch { db in + return try DBJob.job(from: db, rowId: rowId)?.serverModel } - - return Job(job: job) } static func findJob(for serverId: Int64) -> Job? { - guard let job = fetchJob(for: serverId) else { return nil } - return Job(job: job) + return fetchJob(for: serverId)?.serverModel } static func fetchJobs() -> [DBJob] { @@ -39,14 +35,18 @@ class JobDAO: JobDAOProtocol { } static func fetchJobs(of type: DBJob.JobType) -> [Job] { - let jobs = dbManager.fetch { db in + return dbManager.fetch { db in return try DBJob.jobs(db, type: type) - } - - return jobs.map { Job(job: $0) } + }.serverModels + } + + static func fetchJobs(with args: DBJobQueryArgs) -> [Job] { + return DAO().fetch(with: args).serverModels } + // MARK: - Delete + static func deleteJobs() { do { let jobs = fetchJobs() @@ -63,5 +63,4 @@ class JobDAO: JobDAOProtocol { try? dbManager.perform(action: .delete, with: jobs) } - } diff --git a/Nynja/Modules/Profile/Interactor/HomeDataProvider/HomeDataProvider.swift b/Nynja/Modules/Profile/Interactor/HomeDataProvider/HomeDataProvider.swift index 6332d5fa0..eb45e28c1 100644 --- a/Nynja/Modules/Profile/Interactor/HomeDataProvider/HomeDataProvider.swift +++ b/Nynja/Modules/Profile/Interactor/HomeDataProvider/HomeDataProvider.swift @@ -16,4 +16,5 @@ protocol HomeDataProvider { func fetchGroups(rosterId: Int64) -> [Room] func fetchHistory(rosterId: Int64, phoneId: String) -> [Contact] func fetchStars(rosterId: Int64) -> [Star] + func fetchScheduledMessages() -> [ScheduledMessage] } diff --git a/Nynja/Modules/Profile/Interactor/HomeDataProvider/HomeDataProviderImpl.swift b/Nynja/Modules/Profile/Interactor/HomeDataProvider/HomeDataProviderImpl.swift index 33b8fb09d..202b6b6a2 100644 --- a/Nynja/Modules/Profile/Interactor/HomeDataProvider/HomeDataProviderImpl.swift +++ b/Nynja/Modules/Profile/Interactor/HomeDataProvider/HomeDataProviderImpl.swift @@ -73,6 +73,26 @@ class HomeDataProviderImpl: HomeDataProvider, InitializeInjectable { limit: limit, orderColumns: [Column.created.desc]) } + + + // MARK: - Jobs + + func fetchScheduledMessages() -> [ScheduledMessage] { + let args = makeDBJobQueryArgs() + + return DAO() + .fetch(with: args) + .serverModels + .map { ScheduledMessage(job: $0) } + } + + private func makeDBJobQueryArgs() -> DBJobQueryArgs { + return DBJobQueryArgs( + type: .schedule, + args: .init( + limit: limit, + orderingTerms: [Column.id.desc])) + } } diff --git a/Nynja/Modules/Profile/Interactor/ProfileInteractor.swift b/Nynja/Modules/Profile/Interactor/ProfileInteractor.swift index f156b6a40..520eb66bb 100644 --- a/Nynja/Modules/Profile/Interactor/ProfileInteractor.swift +++ b/Nynja/Modules/Profile/Interactor/ProfileInteractor.swift @@ -137,11 +137,7 @@ fileprivate extension ProfileInteractor { } func fetchScheduledMessages() { - let jobs = JobDAO.fetchJobs(of: .schedule).sorted { $0.id ?? 0 > $1.id ?? 0 } - let scheduledMessages = jobs.map { - ScheduledMessage(job: $0) - } - scheduled = Array(scheduledMessages.prefix(presenter.elemsInSection)) + scheduled = homeDataProvider.fetchScheduledMessages() } func fetchLastEvents() { -- GitLab From 2bc04fc185a9d01060dca229ae65a6612729ad9c Mon Sep 17 00:00:00 2001 From: Volodymyr Hryhoriev Date: Thu, 8 Nov 2018 00:02:17 +0200 Subject: [PATCH 19/68] Correct sql for p2p and group lists query. --- Nynja.xcodeproj/project.pbxproj | 12 ----- Nynja/ContactDAO.swift | 4 +- Nynja/ContactDAOProtocol.swift | 2 +- Nynja/ConversationsProvider.swift | 7 +-- Nynja/ConversationsProviding.swift | 2 + .../ConversationsProvidingFetchingArgs.swift | 21 --------- Nynja/DB/Models/DBContact.swift | 45 ++++++++++++++----- Nynja/DB/Models/DBRoom.swift | 24 ++++++---- .../HomeDataProviderImpl.swift | 5 ++- .../Interactor/ProfileInteractor.swift | 4 +- 10 files changed, 62 insertions(+), 64 deletions(-) delete mode 100644 Nynja/ConversationsProvidingFetchingArgs.swift diff --git a/Nynja.xcodeproj/project.pbxproj b/Nynja.xcodeproj/project.pbxproj index 1b250cf91..614866c94 100644 --- a/Nynja.xcodeproj/project.pbxproj +++ b/Nynja.xcodeproj/project.pbxproj @@ -575,7 +575,6 @@ 4B1D7E14202A0A0200703228 /* GroupMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B1D7E13202A0A0200703228 /* GroupMode.swift */; }; 4B1F1230203C8DDE00D61D21 /* JobTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B1F122F203C8DDE00D61D21 /* JobTable.swift */; }; 4B2B1558218F5423000E4916 /* SharedColumn.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B2B1557218F5423000E4916 /* SharedColumn.swift */; }; - 4B2B155A218F6982000E4916 /* ConversationsProvidingFetchingArgs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B2B1559218F6982000E4916 /* ConversationsProvidingFetchingArgs.swift */; }; 4B2D063A202DDA2000010A0C /* BackSwipable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B2D0639202DDA2000010A0C /* BackSwipable.swift */; }; 4B2D063C202E1A1500010A0C /* ContactsExpandedItemsFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B2D063B202E1A1500010A0C /* ContactsExpandedItemsFactory.swift */; }; 4B348FD7216366A900CCB0E3 /* LogServiceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BF090B821635B4700DCCA5C /* LogServiceProtocol.swift */; }; @@ -2872,7 +2871,6 @@ 4B1D7E13202A0A0200703228 /* GroupMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupMode.swift; sourceTree = ""; }; 4B1F122F203C8DDE00D61D21 /* JobTable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JobTable.swift; sourceTree = ""; }; 4B2B1557218F5423000E4916 /* SharedColumn.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedColumn.swift; sourceTree = ""; }; - 4B2B1559218F6982000E4916 /* ConversationsProvidingFetchingArgs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationsProvidingFetchingArgs.swift; sourceTree = ""; }; 4B2D0639202DDA2000010A0C /* BackSwipable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackSwipable.swift; sourceTree = ""; }; 4B2D063B202E1A1500010A0C /* ContactsExpandedItemsFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactsExpandedItemsFactory.swift; sourceTree = ""; }; 4B348FF12163808300CCB0E3 /* DeletedIndexesCalculatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeletedIndexesCalculatorTests.swift; sourceTree = ""; }; @@ -7140,14 +7138,6 @@ name = Entity; sourceTree = ""; }; - 4BC8B38E2191ADB50086DC6C /* Entity */ = { - isa = PBXGroup; - children = ( - 4B2B1559218F6982000E4916 /* ConversationsProvidingFetchingArgs.swift */, - ); - name = Entity; - sourceTree = ""; - }; 4BDDAAE7218CA27200F775A7 /* HomeDataProvider */ = { isa = PBXGroup; children = ( @@ -9773,7 +9763,6 @@ A411D95820AC3916009D107C /* ConversationsProvider */ = { isa = PBXGroup; children = ( - 4BC8B38E2191ADB50086DC6C /* Entity */, A411D95920AC39C6009D107C /* ConversationsProviding.swift */, A411D95B20AC3A5A009D107C /* ConversationsProvider.swift */, ); @@ -16123,7 +16112,6 @@ 8502DB542061030100613C8C /* WheelPositionPickerViewController.swift in Sources */, 4B752B612163A5C900E852B9 /* DescExtension.swift in Sources */, A45F116120B422AF00F45004 /* Message+System.swift in Sources */, - 4B2B155A218F6982000E4916 /* ConversationsProvidingFetchingArgs.swift in Sources */, 4B06D30C2028A25D003B275B /* HomeItemsFactory.swift in Sources */, 266AE8C3203496B60096A12C /* AsyncOperation.swift in Sources */, FEA655DE2167777E00B44029 /* WalletDetailsInteractor.swift in Sources */, diff --git a/Nynja/ContactDAO.swift b/Nynja/ContactDAO.swift index 8e272ac35..7da9822c5 100644 --- a/Nynja/ContactDAO.swift +++ b/Nynja/ContactDAO.swift @@ -127,10 +127,10 @@ class ContactDAO: ContactDAOProtocol { .performIfValueExists(args.orderColumns) { $0.order($1) } } - static func fetchChats(rosterId: Int64, limit: Int? = nil, offset: Int? = nil) -> [Contact] { + static func fetchChats(with args: DBContact.RequestArgs) -> [Contact] { return dbManager.fetch { db in return try DBContact - .request(rosterId: rosterId, limit: limit, offset: offset) + .request(with: args) .fetchAll(from: db) }.serverModels } diff --git a/Nynja/ContactDAOProtocol.swift b/Nynja/ContactDAOProtocol.swift index 813726df1..7eacb84e0 100644 --- a/Nynja/ContactDAOProtocol.swift +++ b/Nynja/ContactDAOProtocol.swift @@ -32,7 +32,7 @@ protocol ContactDAOProtocol: DAOProtocol { excludingIds ids: [String], excludingStatuses statuses: [Contact.Status]?) -> [Contact] - static func fetchChats(rosterId: Int64, limit: Int?, offset: Int?) -> [Contact] + static func fetchChats(with args: DBContact.RequestArgs) -> [Contact] // MARK: -- Reader diff --git a/Nynja/ConversationsProvider.swift b/Nynja/ConversationsProvider.swift index 730bcbe81..8ca552172 100644 --- a/Nynja/ConversationsProvider.swift +++ b/Nynja/ConversationsProvider.swift @@ -12,13 +12,9 @@ class ConversationsProvider: ConversationsProviding { // MARK: - Chats func fetchChats(with args: FetchingArgs) -> [Contact] { - return ContactDAO.fetchChats( - rosterId: args.rosterId, - limit: args.limit, - offset: args.offset) + return ContactDAO.fetchChats(with: args) } - // MARK: - Groups func fetchGroups(with args: FetchingArgs) -> [Room] { @@ -30,6 +26,7 @@ class ConversationsProvider: ConversationsProviding { return .init( rosterId: args.rosterId, type: kind.rawValue, + onlyUnread: args.onlyUnread, limit: args.limit, offset: args.offset) } diff --git a/Nynja/ConversationsProviding.swift b/Nynja/ConversationsProviding.swift index 14e4df45f..1d0344749 100644 --- a/Nynja/ConversationsProviding.swift +++ b/Nynja/ConversationsProviding.swift @@ -7,6 +7,8 @@ // +typealias ConversationsProvidingFetchingArgs = DBContact.RequestArgs + protocol ConversationsProviding { func fetchChats(with args: ConversationsProvidingFetchingArgs) -> [Contact] diff --git a/Nynja/ConversationsProvidingFetchingArgs.swift b/Nynja/ConversationsProvidingFetchingArgs.swift deleted file mode 100644 index d7329f1cb..000000000 --- a/Nynja/ConversationsProvidingFetchingArgs.swift +++ /dev/null @@ -1,21 +0,0 @@ -// -// ConversationsProvidingFetchingArgs.swift -// Nynja -// -// Created by Volodymyr Hryhoriev on 11/4/18. -// Copyright © 2018 TecSynt Solutions. All rights reserved. -// - - -struct ConversationsProvidingFetchingArgs { - - let rosterId: Int64 - let limit: Int? - let offset: Int? - - init(rosterId: Int64, limit: Int? = nil, offset: Int? = nil) { - self.rosterId = rosterId - self.limit = limit - self.offset = offset - } -} diff --git a/Nynja/DB/Models/DBContact.swift b/Nynja/DB/Models/DBContact.swift index daf5b6cd0..08167226f 100644 --- a/Nynja/DB/Models/DBContact.swift +++ b/Nynja/DB/Models/DBContact.swift @@ -208,35 +208,36 @@ class DBContact: Record, DBExtendedModelProtocol { return DBContact.filter(Column(ContactTable.Column.phoneId.title) == phoneId) } - static func request(rosterId: Int64, limit: Int?, offset: Int?) -> AnyTypedRequest { + static func request(with args: RequestArgs) -> AnyTypedRequest { let contactTable = ContactTable.name let messageTable = MessageTable.name - let messageIdColumn = "\(contactTable).\(Column.messageId.name)" - let localIdColumn = "\(messageTable).\(Column.localId.name)" + let messageIdColumn = "\(contactTable).\(Column.messageId)" + let localIdColumn = "\(messageTable).\(Column.localId)" - let rosterIdColumn = "\(contactTable).\(Column.rosterId.name)" - let unreadColumn = "\(contactTable).\(Column.unread.name)" - let statusColumn = "\(contactTable).\(Column.status.name)" + let rosterIdColumn = "\(contactTable).\(Column.rosterId)" + let unreadColumn = "\(contactTable).\(Column.unread)" + let statusColumn = "\(contactTable).\(Column.status)" - let createdColumn = "\(messageTable).\(Column.created.name)" + let createdColumn = "\(messageTable).\(Column.created)" + + let unreadCondition = args.onlyUnread ? "and \(unreadColumn) > 0" : "" var sql = """ select \(contactTable).* from \(contactTable) left join \(messageTable) on \(messageIdColumn) == \(localIdColumn) - where \(rosterIdColumn) == \(rosterId) - and \(unreadColumn) > 0 + where \(rosterIdColumn) == \(args.rosterId) + \(unreadCondition) and \(statusColumn) in ('friend', 'ban', 'banned') order by \(createdColumn) desc - limit \(limit) offset \(offset) """ - if let limit = limit { + if let limit = args.limit { sql.append(" limit \(limit)") } - if let offset = offset { + if let offset = args.offset { sql.append(" offset \(offset)") } @@ -244,3 +245,23 @@ class DBContact: Record, DBExtendedModelProtocol { } } + +extension DBContact { + + struct RequestArgs { + let rosterId: Int64 + let onlyUnread: Bool + let limit: Int? + let offset: Int? + + public init(rosterId: Int64, + onlyUnread: Bool = false, + limit: Int? = nil, + offset: Int? = nil) { + self.rosterId = rosterId + self.onlyUnread = onlyUnread + self.limit = limit + self.offset = offset + } + } +} diff --git a/Nynja/DB/Models/DBRoom.swift b/Nynja/DB/Models/DBRoom.swift index 3e3b0f2f6..ede2aa874 100644 --- a/Nynja/DB/Models/DBRoom.swift +++ b/Nynja/DB/Models/DBRoom.swift @@ -301,14 +301,16 @@ class DBRoom: Record, DBExtendedModelProtocol { let roomTable = RoomTable.name let messageTable = MessageTable.name - let messageIdColumn = "\(roomTable).\(Column.messageId.name)" - let localIdColumn = "\(messageTable).\(Column.localId.name)" + let messageIdColumn = "\(roomTable).\(Column.messageId)" + let localIdColumn = "\(messageTable).\(Column.localId)" - let rosterIdColumn = "\(roomTable).\(Column.rosterId.name)" - let typeColumn = "\(roomTable).\(Column.type.name)" - let unreadColumn = "\(roomTable).\(Column.unread.name)" + let rosterIdColumn = "\(roomTable).\(Column.rosterId)" + let typeColumn = "\(roomTable).\(Column.type)" + let unreadColumn = "\(roomTable).\(Column.unread)" - let createdColumn = "\(messageTable).\(Column.created.name)" + let createdColumn = "\(messageTable).\(Column.created)" + + let unreadCondition = args.onlyUnread ? "and \(unreadColumn) > 0" : "" var sql = """ select \(roomTable).* @@ -316,7 +318,7 @@ class DBRoom: Record, DBExtendedModelProtocol { on \(messageIdColumn) == \(localIdColumn) where \(rosterIdColumn) == \(args.rosterId) and \(typeColumn) == '\(args.type)' - and \(unreadColumn) > 0 + \(unreadCondition) order by \(createdColumn) desc """ @@ -340,12 +342,18 @@ extension DBRoom { struct RequestArgs { let rosterId: Int64 let type: String + let onlyUnread: Bool let limit: Int? let offset: Int? - init(rosterId: Int64, type: String, limit: Int? = nil, offset: Int? = nil) { + init(rosterId: Int64, + type: String, + onlyUnread: Bool = false, + limit: Int? = nil, + offset: Int? = nil) { self.rosterId = rosterId self.type = type + self.onlyUnread = onlyUnread self.limit = limit self.offset = offset } diff --git a/Nynja/Modules/Profile/Interactor/HomeDataProvider/HomeDataProviderImpl.swift b/Nynja/Modules/Profile/Interactor/HomeDataProvider/HomeDataProviderImpl.swift index 202b6b6a2..8322d2897 100644 --- a/Nynja/Modules/Profile/Interactor/HomeDataProvider/HomeDataProviderImpl.swift +++ b/Nynja/Modules/Profile/Interactor/HomeDataProvider/HomeDataProviderImpl.swift @@ -41,7 +41,10 @@ class HomeDataProviderImpl: HomeDataProvider, InitializeInjectable { } private func makeConversationsProvidingFetchingArgs(rosterId: Int64) -> ConversationsProvidingFetchingArgs { - return ConversationsProvidingFetchingArgs(rosterId: rosterId, limit: limit) + return ConversationsProvidingFetchingArgs( + rosterId: rosterId, + onlyUnread: true, + limit: limit) } diff --git a/Nynja/Modules/Profile/Interactor/ProfileInteractor.swift b/Nynja/Modules/Profile/Interactor/ProfileInteractor.swift index 520eb66bb..613cb6390 100644 --- a/Nynja/Modules/Profile/Interactor/ProfileInteractor.swift +++ b/Nynja/Modules/Profile/Interactor/ProfileInteractor.swift @@ -15,8 +15,8 @@ class ProfileInteractor: BaseInteractor, ProfileInteractorInputProtocol { var myContact: Contact! var chats: CellModels = [] - var history: CellModels = [] var rooms: CellModels = [] + var history: CellModels = [] var starred: CellModels = [] var scheduled: CellModels = [] @@ -108,7 +108,7 @@ fileprivate extension ProfileInteractor { func fetchRooms() { performIfRosterIdExists { rosterId in - chats = homeDataProvider.fetchGroups(rosterId: rosterId) + rooms = homeDataProvider.fetchGroups(rosterId: rosterId) } } -- GitLab From dfaef5b0f1d2ffba0e384af2b5f46cbf1907920a Mon Sep 17 00:00:00 2001 From: Volodymyr Hryhoriev Date: Thu, 8 Nov 2018 12:54:01 +0200 Subject: [PATCH 20/68] Correct `StarDAO`. --- Nynja/StarDAO.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Nynja/StarDAO.swift b/Nynja/StarDAO.swift index aa879ed5f..cc40a15ca 100644 --- a/Nynja/StarDAO.swift +++ b/Nynja/StarDAO.swift @@ -32,7 +32,7 @@ class StarDAO: StarDAOProtocol { let stars = dbManager.fetch { db -> [DBStar] in let args = StarDAOFetchingArgs(rosterId: rosterId) return try makeQuery(with: args) - .filter(Column.status != Star.Status.remove.rawValue) + .filter(Column.status !== Star.Status.remove.rawValue) .fetchAll(from: db) } @@ -50,7 +50,7 @@ class StarDAO: StarDAOProtocol { static func fetchStars(with args: StarDAOFetchingArgs) -> [Star] { return dbManager.fetch { db in return try makeQuery(with: args) - .filter(Column.status != Star.Status.remove.rawValue) + .filter(Column.status !== Star.Status.remove.rawValue) .fetchAll(from: db) }.serverModels } -- GitLab From 7ca6f7f38e76e5d19edd4cf6396e9c65da9a94bc Mon Sep 17 00:00:00 2001 From: Volodymyr Hryhoriev Date: Thu, 8 Nov 2018 14:06:23 +0200 Subject: [PATCH 21/68] Add sql query for fetching the count of unread messages from all chats. --- Nynja/BadgeNumberService.swift | 14 +--- Nynja/ConversationsProvider.swift | 64 ++++++++++++++----- Nynja/ConversationsProviding.swift | 5 +- .../WireFrame/ChatsListWireframe.swift | 2 +- .../WireFrame/GroupsListWireframe.swift | 8 +-- .../View/MainViewController+Recents.swift | 6 +- .../ServiceFactory/ServiceFactory.swift | 6 +- 7 files changed, 63 insertions(+), 42 deletions(-) diff --git a/Nynja/BadgeNumberService.swift b/Nynja/BadgeNumberService.swift index 1d913c53c..51fd37560 100644 --- a/Nynja/BadgeNumberService.swift +++ b/Nynja/BadgeNumberService.swift @@ -18,7 +18,7 @@ class BadgeNumberService: BadgeNumberServiceProtocol, StorageSubscriber { private var counters: [String: Int64] = [:] private var subscribers: [AnyWeakSubscriber] = [] - private let conversationsProvider = ConversationsProvider() + private let conversationsProvider = ServiceFactory().makeConversationsProvider() // MARK: - Init & deinit private init() { @@ -26,17 +26,7 @@ class BadgeNumberService: BadgeNumberServiceProtocol, StorageSubscriber { } func initCounters() { - var badgeNumber: Int64 = 0 - - conversationsProvider - .fetchAllConversations() - .forEach { chat in - guard let id = chat.id else { return } - counters[id] = chat.unreadCount - badgeNumber += chat.unreadCount - } - - self.badgeNumber = badgeNumber + self.badgeNumber = conversationsProvider.fetchUnreadMessagesCount() } deinit { diff --git a/Nynja/ConversationsProvider.swift b/Nynja/ConversationsProvider.swift index 8ca552172..9ca5b34ad 100644 --- a/Nynja/ConversationsProvider.swift +++ b/Nynja/ConversationsProvider.swift @@ -6,9 +6,17 @@ // Copyright © 2018 TecSynt Solutions. All rights reserved. // -class ConversationsProvider: ConversationsProviding { +import GRDBCipher + +class ConversationsProvider: ConversationsProviding, InitializeInjectable { typealias FetchingArgs = ConversationsProvidingFetchingArgs + let dbManager: DBManagerProtocol + + required init(dependencies: Dependencies) { + self.dbManager = dependencies.dbManager + } + // MARK: - Chats func fetchChats(with args: FetchingArgs) -> [Contact] { @@ -50,28 +58,54 @@ class ConversationsProvider: ConversationsProviding { .sorted(by: comparator) } + func comparator(lhs: ChatModel, rhs: ChatModel) -> Bool { + let created1 = lhs.last_msg?.created ?? 0 + let created2 = rhs.last_msg?.created ?? 0 + return created1 > created2 + } // MARK: - All - func fetchAllConversations() -> [ChatModel] { - var chatModels: [ChatModel] = [] + func fetchUnreadMessagesCount() -> Int64 { + return dbManager.fetch { db in + return try makeUnreadCountRequest().fetchOne(db) + } ?? 0 + } + + private func makeUnreadCountRequest() -> AnyTypedRequest { + let unreadAlias = "unread" - let contacts = ContactDAO.fetchContactWithoutSelf() as [ChatModel] - chatModels.append(contentsOf: contacts) + let contactSql = makeUnreadCountSQL(for: ContactTable.name, unreadAlias: unreadAlias) + let roomSql = makeUnreadCountSQL(for: RoomTable.name, unreadAlias: unreadAlias) - let rooms = RoomDAO.fetchRooms() as [ChatModel] - chatModels.append(contentsOf: rooms) + let sql = """ + select sum(\(unreadAlias)) + from + (\(contactSql) + union + \(roomSql)) + """ - return chatModels + return SQLRequest(sql).asRequest(of: Int64.self) } - - // MARK: - Comparator - - func comparator(lhs: ChatModel, rhs: ChatModel) -> Bool { - let created1 = lhs.last_msg?.created ?? 0 - let created2 = rhs.last_msg?.created ?? 0 - return created1 > created2 + private func makeUnreadCountSQL(for tableName: String, unreadAlias: String) -> String { + let unreadColumn = "\(tableName).\(Column.unread)" + + return """ + select sum(\(unreadColumn)) as '\(unreadAlias)' + from \(tableName) + where \(unreadColumn) > 0 + """ } +} + + +// MARK: - InitInitializable + +extension ConversationsProvider { + struct Dependencies { + let dbManager: DBManagerProtocol + } } diff --git a/Nynja/ConversationsProviding.swift b/Nynja/ConversationsProviding.swift index 1d0344749..4f8a9240d 100644 --- a/Nynja/ConversationsProviding.swift +++ b/Nynja/ConversationsProviding.swift @@ -10,6 +10,7 @@ typealias ConversationsProvidingFetchingArgs = DBContact.RequestArgs protocol ConversationsProviding { + var dbManager: DBManagerProtocol { get } func fetchChats(with args: ConversationsProvidingFetchingArgs) -> [Contact] @@ -18,7 +19,7 @@ protocol ConversationsProviding { func fetchChannels() -> [Room] func fetchMyChannels() -> [Room] - func fetchAllConversations() -> [ChatModel] - func comparator(lhs: ChatModel, rhs: ChatModel) -> Bool + + func fetchUnreadMessagesCount() -> Int64 } diff --git a/Nynja/Modules/ChatsList/WireFrame/ChatsListWireframe.swift b/Nynja/Modules/ChatsList/WireFrame/ChatsListWireframe.swift index 608c9433c..d54d9eb7b 100644 --- a/Nynja/Modules/ChatsList/WireFrame/ChatsListWireframe.swift +++ b/Nynja/Modules/ChatsList/WireFrame/ChatsListWireframe.swift @@ -20,7 +20,7 @@ class ChatsListWireFrame: ChatsListWireFrameProtocol { let interactor = ChatsListInteractor( dependencies: .init(storageService: StorageService.sharedInstance, - conversationsProvider: ConversationsProvider())) + conversationsProvider: ServiceFactory().makeConversationsProvider())) self.main = main diff --git a/Nynja/Modules/GroupsList/WireFrame/GroupsListWireframe.swift b/Nynja/Modules/GroupsList/WireFrame/GroupsListWireframe.swift index 3db6d4df5..e777359cf 100644 --- a/Nynja/Modules/GroupsList/WireFrame/GroupsListWireframe.swift +++ b/Nynja/Modules/GroupsList/WireFrame/GroupsListWireframe.swift @@ -15,18 +15,14 @@ class GroupsListWireFrame: GroupsListWireFrameProtocol { func presentGroupsList(navigation: UINavigationController, main: MainWireFrame, animated: Bool) { self.navigation = navigation - self.main = main - - // Dependencies - let conversationsProvider = ConversationsProvider() - + self.main = main // Compomentes let view = GroupsListViewController() let presenter = GroupsListPresenter() let interactor = GroupsListInteractor( dependencies: .init(storageService: StorageService.sharedInstance, - conversationsProvider: ConversationsProvider())) + conversationsProvider: ServiceFactory().makeConversationsProvider())) // Connecting view.presenter = presenter diff --git a/Nynja/Modules/Main/View/MainViewController+Recents.swift b/Nynja/Modules/Main/View/MainViewController+Recents.swift index 3e952a30a..752cb8147 100644 --- a/Nynja/Modules/Main/View/MainViewController+Recents.swift +++ b/Nynja/Modules/Main/View/MainViewController+Recents.swift @@ -44,13 +44,13 @@ extension MainViewController { func showRecentP2PChats(indexPath: IndexPath?) { let conversations = fetchConversation( - using: ConversationsProvider().fetchChats(with:)) + using: ServiceFactory().makeConversationsProvider().fetchChats(with:)) showRecentConversation(indexPath: indexPath, conversations: conversations) } func showRecentGroupChats(indexPath: IndexPath?) { let conversations = fetchConversation( - using: ConversationsProvider().fetchGroups(with:)) + using: ServiceFactory().makeConversationsProvider().fetchGroups(with:)) showRecentConversation(indexPath: indexPath, conversations: conversations) } @@ -64,7 +64,7 @@ extension MainViewController { } func showRecentChannels(indexPath: IndexPath?) { - showRecentConversation(indexPath: indexPath, conversations: ConversationsProvider().fetchChannels()) + showRecentConversation(indexPath: indexPath, conversations: ServiceFactory().makeConversationsProvider().fetchChannels()) } private func showRecentConversation(indexPath: IndexPath?, conversations: [ChatModel]) { diff --git a/Nynja/Services/ServiceFactory/ServiceFactory.swift b/Nynja/Services/ServiceFactory/ServiceFactory.swift index 41488064b..99adf6859 100644 --- a/Nynja/Services/ServiceFactory/ServiceFactory.swift +++ b/Nynja/Services/ServiceFactory/ServiceFactory.swift @@ -102,13 +102,13 @@ final class ServiceFactory: SharedServiceFactory, ServiceFactoryProtocol { } func makeConversationsProvider() -> ConversationsProviding { - return ConversationsProvider() + return ConversationsProvider( + dependencies: .init(dbManager: makeStorageService())) } func makeStickersProvider() -> StickersProviding { return StickersProvider( - dependencies: .init(storage: makeStorageService()) - ) + dependencies: .init(storage: makeStorageService())) } func makePermissionManager() -> PermissionManager { -- GitLab From c85ec1bab1ffde4affd55c891cb440292b2c4d19 Mon Sep 17 00:00:00 2001 From: Volodymyr Hryhoriev Date: Thu, 8 Nov 2018 15:23:59 +0200 Subject: [PATCH 22/68] Remove unnecessary code from `RosterHandler`. --- Nynja/DB/Models/DBRoster.swift | 2 -- Nynja/Services/HandleServices/RosterHandler.swift | 5 ----- 2 files changed, 7 deletions(-) diff --git a/Nynja/DB/Models/DBRoster.swift b/Nynja/DB/Models/DBRoster.swift index c76950802..a2203ad58 100644 --- a/Nynja/DB/Models/DBRoster.swift +++ b/Nynja/DB/Models/DBRoster.swift @@ -24,9 +24,7 @@ class DBRoster: Record, DBModelProtocol { var contacts: [DBContact] = [] var rooms: [DBRoom] = [] - // TODO: Implement tags var stars: [DBStar] = [] -// var tag: [DBTag] = [] init?(roster: Roster, profileId: String) { guard let id = roster.id else { diff --git a/Nynja/Services/HandleServices/RosterHandler.swift b/Nynja/Services/HandleServices/RosterHandler.swift index 419d89e79..51ac27f17 100644 --- a/Nynja/Services/HandleServices/RosterHandler.swift +++ b/Nynja/Services/HandleServices/RosterHandler.swift @@ -17,12 +17,7 @@ class RosterHandler: BaseHandler { } if status == "patch" || status == "nick" { - if let currentRoster = RosterDAO.currentRoster { - roster.favorite = currentRoster.favorite - } - try? StorageService.sharedInstance.perform(action: .save, with: roster) } } - } -- GitLab From 4c2cbc39e306605fd41ac44021e15717a36403e4 Mon Sep 17 00:00:00 2001 From: Volodymyr Hryhoriev Date: Thu, 8 Nov 2018 16:05:37 +0200 Subject: [PATCH 23/68] Replace `currenRoster` with `currentContact` in order to improve performance. --- Nynja/ContactDAO.swift | 5 +---- Nynja/Extensions/Models/RosterExtension.swift | 4 ---- Nynja/Modules/Splash/Interactor/SplashInteractor.swift | 8 ++++---- Nynja/Modules/Splash/Presenter/SplashPresenter.swift | 4 ++-- Nynja/Modules/Splash/SplashProtocols.swift | 4 ++-- Nynja/Modules/Splash/WireFrame/SplashWireframe.swift | 6 +----- .../Extensions/Models/Contact/ContactExtension.swift | 4 ++++ 7 files changed, 14 insertions(+), 21 deletions(-) diff --git a/Nynja/ContactDAO.swift b/Nynja/ContactDAO.swift index 7da9822c5..2136e19e6 100644 --- a/Nynja/ContactDAO.swift +++ b/Nynja/ContactDAO.swift @@ -118,9 +118,7 @@ class ContactDAO: ContactDAOProtocol { }.serverModels } - private static func makeQuery( - with args: ContactDAOFetchingArgs) -> QueryInterfaceRequest { - + private static func makeQuery(with args: ContactDAOFetchingArgs) -> QueryInterfaceRequest { return DBContact .filter(Column.rosterId == args.rosterId) .performIfValueExists(args.limit) { $0.limit($1, offset: args.offset) } @@ -176,5 +174,4 @@ class ContactDAO: ContactDAOProtocol { try? dbManager.perform(action: .updateColumns([ContactTable.Column.reader.title]), with: contact) } - } diff --git a/Nynja/Extensions/Models/RosterExtension.swift b/Nynja/Extensions/Models/RosterExtension.swift index 12880f6a5..1f6df0177 100644 --- a/Nynja/Extensions/Models/RosterExtension.swift +++ b/Nynja/Extensions/Models/RosterExtension.swift @@ -23,10 +23,6 @@ extension Roster: FullNameable { return contact } - - var hasName: Bool { - return names != nil && names != "" - } var phoneId: String? { guard let phone = phone, let rosterId = id else { diff --git a/Nynja/Modules/Splash/Interactor/SplashInteractor.swift b/Nynja/Modules/Splash/Interactor/SplashInteractor.swift index 81bc95880..38422a4da 100644 --- a/Nynja/Modules/Splash/Interactor/SplashInteractor.swift +++ b/Nynja/Modules/Splash/Interactor/SplashInteractor.swift @@ -70,16 +70,16 @@ class SplashInteractor: SplashInteractorInputProtocol { LogService.log(topic: .db) { return "Setup DB: Splash" } storageService.setupDatabase(with: phoneId, application: UIApplication.shared) - guard let roster = RosterDAO.currentRoster else { - LogService.log(topic: .db) { return "Clear storage: can't find current roster" } + guard let contact = ContactDAO.currentContact else { + LogService.log(topic: .db) { return "Clear storage: can't find current contact" } prepareToShowAuth() return } - if roster.hasName { + if contact.hasName { presenter.showMain() } else { - presenter.showEditProfile(roster: roster) + presenter.showEditProfile() } } diff --git a/Nynja/Modules/Splash/Presenter/SplashPresenter.swift b/Nynja/Modules/Splash/Presenter/SplashPresenter.swift index 8ea3595dc..f001fed03 100644 --- a/Nynja/Modules/Splash/Presenter/SplashPresenter.swift +++ b/Nynja/Modules/Splash/Presenter/SplashPresenter.swift @@ -33,8 +33,8 @@ class SplashPresenter: BasePresenter, SplashPresenterProtocol, SplashInteractorO self.wireFrame.showMain() } - func showEditProfile(roster: Roster) { - self.wireFrame.showEditProfile(roster: roster) + func showEditProfile() { + self.wireFrame.showEditProfile() } func showJailbreakAlert(with completion: @escaping () -> Void) { diff --git a/Nynja/Modules/Splash/SplashProtocols.swift b/Nynja/Modules/Splash/SplashProtocols.swift index ba8af6fb1..93800181e 100644 --- a/Nynja/Modules/Splash/SplashProtocols.swift +++ b/Nynja/Modules/Splash/SplashProtocols.swift @@ -19,7 +19,7 @@ protocol SplashWireFrameProtocol: class { func showAuth() func showTutorial() func showMain() - func showEditProfile(roster: Roster) + func showEditProfile() } protocol SplashViewProtocol: class { @@ -51,7 +51,7 @@ protocol SplashInteractorOutputProtocol: class { func showAuth() func showTutorial() func showMain() - func showEditProfile(roster: Roster) + func showEditProfile() func showJailbreakAlert(with completion: @escaping () -> Void) } diff --git a/Nynja/Modules/Splash/WireFrame/SplashWireframe.swift b/Nynja/Modules/Splash/WireFrame/SplashWireframe.swift index decce3312..949955c74 100644 --- a/Nynja/Modules/Splash/WireFrame/SplashWireframe.swift +++ b/Nynja/Modules/Splash/WireFrame/SplashWireframe.swift @@ -41,11 +41,7 @@ class SplashWireFrame: SplashWireFrameProtocol { MainWireFrame().presentMain(navigation: navigation!, isRegistered: false, checkSession: true) } - func showEditProfile(roster: Roster) { - guard let contact = roster.myContact else { - assertionFailure("Contact should exist!") - return - } + func showEditProfile() { EditProfileWireFrame().presentEditProfile(navigation: navigation!, isRegistered: true, main: nil) } } diff --git a/Shared/Library/Extensions/Models/Contact/ContactExtension.swift b/Shared/Library/Extensions/Models/Contact/ContactExtension.swift index 321bdcb09..594fdbc4d 100644 --- a/Shared/Library/Extensions/Models/Contact/ContactExtension.swift +++ b/Shared/Library/Extensions/Models/Contact/ContactExtension.swift @@ -25,6 +25,10 @@ extension Contact: FullNameable { var alias: String? { return fullName } + + var hasName: Bool { + return names != nil && names != "" + } /// Returns the first letter of name. If name doesn't exist, it will return the first letter of surname. /// If surname doesn't exist too, it will return empty string. -- GitLab From 88a0b18ac465facf20f8526bd0dcc2f86a585bdf Mon Sep 17 00:00:00 2001 From: Volodymyr Hryhoriev Date: Thu, 8 Nov 2018 19:56:47 +0200 Subject: [PATCH 24/68] Make small refactoring in `DBObesrver` class. --- Nynja/DBObserver.swift | 10 ++++++---- .../UI/Extensions/String/StringExtensions.swift | 7 +++++++ 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/Nynja/DBObserver.swift b/Nynja/DBObserver.swift index 24c9ee755..366674dd8 100644 --- a/Nynja/DBObserver.swift +++ b/Nynja/DBObserver.swift @@ -14,7 +14,9 @@ class DBObserver: StorageObserver, TransactionObserver { private init() {} - private var dispatchQueue = DispatchQueue(label: "com.nynja.mobile.communicator.db_observer.queue", qos: .userInitiated) + private let processingQueue = DispatchQueue( + label: String.label(withSuffix: "db-observer.processing-queue"), + qos: .utility) // MARK: - Properties var subscribers: [SubscribeType: [StorageSubscriberReference]] = [:] @@ -56,7 +58,7 @@ class DBObserver: StorageObserver, TransactionObserver { } func databaseDidChange(with event: DatabaseEvent) { - dispatchQueue.async { + processingQueue.async { var temp = self.allChanges[event.tableName] ?? [] temp.append(ChangeInfo(event: event)) self.allChanges[event.tableName] = temp @@ -64,7 +66,7 @@ class DBObserver: StorageObserver, TransactionObserver { } func databaseWillCommit() throws { - dispatchQueue.async { + processingQueue.async { self.allChanges.forEach { (tableName, changes) in changes.forEach { info in guard info.kind != .insert else { return } @@ -75,7 +77,7 @@ class DBObserver: StorageObserver, TransactionObserver { } func databaseDidCommit(_ db: Database) { - dispatchQueue.async { + processingQueue.async { self.allChanges.forEach { (tableName, changes) in var storageChanges: [StorageChange] = [] diff --git a/Nynja/Library/UI/Extensions/String/StringExtensions.swift b/Nynja/Library/UI/Extensions/String/StringExtensions.swift index f936adddb..01ba0b492 100644 --- a/Nynja/Library/UI/Extensions/String/StringExtensions.swift +++ b/Nynja/Library/UI/Extensions/String/StringExtensions.swift @@ -379,3 +379,10 @@ extension String { self = self.capitalizingFirstLetter() } } + +extension String { + + static func label(withSuffix suffix: String) -> String { + return Bundle(for: ServiceFactory.self).bundleIdentifier.appending(suffix) + } +} -- GitLab From a4d995ee5c4689d63e93420dcaf41da1d0d082e5 Mon Sep 17 00:00:00 2001 From: Volodymyr Hryhoriev Date: Fri, 9 Nov 2018 13:14:58 +0200 Subject: [PATCH 25/68] Update model version. --- Nynja/Resources/LoadDBConfig.xcconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Nynja/Resources/LoadDBConfig.xcconfig b/Nynja/Resources/LoadDBConfig.xcconfig index c1e7b411b..7b7891380 100644 --- a/Nynja/Resources/LoadDBConfig.xcconfig +++ b/Nynja/Resources/LoadDBConfig.xcconfig @@ -13,7 +13,7 @@ AppName = NYNJALoad ServerPort = 1883 Config = dev AppGroup = group.com.nynja.mobile.communicator.dev -ModelsVersion = 9 +ModelsVersion = 10 isServerConnectionSecure = false ConfServerAddress = 35.198.118.190 ConfServerPort = 80 -- GitLab From cdd3559282cb939195ea6ccabf654bf1e406411f Mon Sep 17 00:00:00 2001 From: Volodymyr Hryhoriev Date: Thu, 8 Nov 2018 20:50:57 +0200 Subject: [PATCH 26/68] Make `MQTTService` perform work in background thread. --- Nynja/Services/MQTT/MQTTService.swift | 41 ++++++++++--------- Nynja/Services/MQTT/MQTTServiceHelper.swift | 30 ++++++++------ .../Handlers/Base/HandlerService.swift | 1 - 3 files changed, 40 insertions(+), 32 deletions(-) diff --git a/Nynja/Services/MQTT/MQTTService.swift b/Nynja/Services/MQTT/MQTTService.swift index 120020895..45ff69be5 100644 --- a/Nynja/Services/MQTT/MQTTService.swift +++ b/Nynja/Services/MQTT/MQTTService.swift @@ -26,23 +26,24 @@ final class MQTTService: NSObject, CocoaMQTTDelegate, ConnectionServiceDelegate case disconnected } - var mqtt: CocoaMQTT? - static let version = Bundle.main.modelsVersion - let host = Host() + var mqtt: CocoaMQTT? var push: String? let deviceId = UIDevice.current.persistentIdentifier - - let semaphore = DispatchSemaphore(value: 1) + private let semaphore = DispatchSemaphore(value: 1) var wasRunApp: Bool = false typealias MQTTServiceSubscribers = [WeakRef] private var subscribers = MQTTServiceSubscribers() - private let subscribersQueue = DispatchQueue(label: "com.nynja.mobile.communicator.mqttservice.subscribers") + private let subscribersQueue = DispatchQueue(label: String.label(withSuffix: "mqtt-service.subscribers-queue"), attributes: .concurrent) + + let loggingQueue = DispatchQueue(label: String.label(withSuffix: "mqtt-service.logging-queue")) + private let receivingQueue = DispatchQueue(label: String.label(withSuffix: "mqtt-service.receiving-queue"), qos: .utility) + let publishingQueue = DispatchQueue(label: String.label(withSuffix: "mqtt-service.publishing-queue"), qos: .utility) let showHandlers : Set = [] @@ -247,7 +248,9 @@ final class MQTTService: NSObject, CocoaMQTTDelegate, ConnectionServiceDelegate func mqtt(_ mqtt: CocoaMQTT, didReceiveMessage message: CocoaMQTTMessage, id: UInt16 ) { printMessage(msg: message, isSent: false) - HandlerService.handle(response: message) + receivingQueue.async { [weak self] in + HandlerService.handle(response: message) + } } func mqttDidDisconnect(_ mqtt: CocoaMQTT, withError err: Error?) { @@ -272,29 +275,30 @@ final class MQTTService: NSObject, CocoaMQTTDelegate, ConnectionServiceDelegate // MARK: Subscribers func addSubscriber(_ subscriber: MQTTServiceDelegate) { - subscribersQueue.sync { - subscribers.append(WeakRef(value: subscriber)) + subscribersQueue.async(flags: .barrier) { [weak self] in + guard let `self` = self else { return } + self.subscribers.append(WeakRef(value: subscriber)) } } func removeSubscriber(_ subscriber: MQTTServiceDelegate) { - subscribersQueue.sync { - subscribers = subscribers.filter { $0.value !== subscriber } + subscribersQueue.async(flags: .barrier) { [weak self] in + guard let `self` = self else { return } + self.subscribers = self.subscribers.filter { $0.value !== subscriber } } } func removeAllSubscribers() { - subscribersQueue.sync { - subscribers.removeAll() + subscribersQueue.async(flags: .barrier) { [weak self] in + guard let `self` = self else { return } + self.subscribers.removeAll() } } - /* Notify all subscribers that reachbility status changed */ - private func notifySubscribers(with eventClosure: (MQTTServiceDelegate) -> Void) { - - subscribersQueue.sync { - subscribers.forEach { (weak) in + subscribersQueue.sync { [weak self] in + guard let `self` = self else { return } + self.subscribers.forEach { (weak) in if let delegate = weak.value as? MQTTServiceDelegate { eventClosure(delegate) } @@ -306,7 +310,6 @@ final class MQTTService: NSObject, CocoaMQTTDelegate, ConnectionServiceDelegate // MARK: - ConnectionServiceDelegate func connectionStatusChanged(_ sender: ConnectionService, service: ConnectionService.Service, oldValue: ConnectionService.ConnectionServiceState) { - if service == .networking { guard let networkStatus = sender.state[.networking] else { return } switch networkStatus { diff --git a/Nynja/Services/MQTT/MQTTServiceHelper.swift b/Nynja/Services/MQTT/MQTTServiceHelper.swift index 41e228ac7..fd07ad5bd 100644 --- a/Nynja/Services/MQTT/MQTTServiceHelper.swift +++ b/Nynja/Services/MQTT/MQTTServiceHelper.swift @@ -12,23 +12,29 @@ import CocoaMQTT extension MQTTService { func publish(model: BaseMQTTModel) { - guard let mqtt = self.mqtt, mqtt.connState == .connected else { - return + publishingQueue.async { [weak self] in + guard let mqtt = self?.mqtt, mqtt.connState == .connected else { + return + } + + let finalModel: CocoaMQTTMessage = model.getMessage() + mqtt.publish(finalModel) } - - let finalModel: CocoaMQTTMessage = model.getMessage() - mqtt.publish(finalModel) } func printMessage(msg: CocoaMQTTMessage, isSent: Bool) { - let desc = self.prepareOutputMessage(msg: msg, isSent: isSent) - guard let bert = desc.1 as? BertTuple else { - return + loggingQueue.async { [weak self] in + guard let `self` = self else { return } + + let desc = self.prepareOutputMessage(msg: msg, isSent: isSent) + guard let bert = desc.1 as? BertTuple else { + return + } + + if self.shouldShowLog(for: bert) { + LogService.log(topic: .MQTT) { return desc.0 } + } } - - if shouldShowLog(for: bert) { - LogService.log(topic: .MQTT) { return desc.0 } - } } private func shouldShowLog(for bert: BertTuple) -> Bool { diff --git a/Shared/Services/Handlers/Base/HandlerService.swift b/Shared/Services/Handlers/Base/HandlerService.swift index 7d5c1fee4..efbb47e23 100644 --- a/Shared/Services/Handlers/Base/HandlerService.swift +++ b/Shared/Services/Handlers/Base/HandlerService.swift @@ -17,5 +17,4 @@ class HandlerService: HandlerServiceProtocol { let handlerType = HandlerFactory.handler(for: handler) handlerType.executeHandle(data: params) } - } -- GitLab From 1e24b64133ea95f06dc91407844c5b3f471f5c82 Mon Sep 17 00:00:00 2001 From: Volodymyr Hryhoriev Date: Thu, 8 Nov 2018 20:56:03 +0200 Subject: [PATCH 27/68] Update `NotificationManager`: 1. Do preparation work in background thread. 2. Do UI related work in main thread. --- Nynja/NotificationManager.swift | 34 ++++++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/Nynja/NotificationManager.swift b/Nynja/NotificationManager.swift index 4059601d6..d1a6987a8 100644 --- a/Nynja/NotificationManager.swift +++ b/Nynja/NotificationManager.swift @@ -53,6 +53,10 @@ final class NotificationManager { // MARK: - Properties + private let processingQueue = DispatchQueue( + label: String.label(withSuffix: "notification-manager.processing-queue"), + qos: .utility) + private(set) var previousView: NotificationView? private(set) var typeForNavigate: InAppNotificationType? { @@ -107,7 +111,9 @@ final class NotificationManager { return } - handleNavigation(presenter: mainPresenter) + processingQueue.async { [weak self] in + self?.handleNavigation(presenter: mainPresenter) + } } @@ -138,10 +144,12 @@ final class NotificationManager { switch type { case .message: if let vc = self.getMessageVC { - DispatchQueue.main.async { - if self.isFromPush { - vc.navigationController?.popToRootViewController(animated: false) + dispatchAsyncMain { [weak self] in + guard let `self` = self, self.isFromPush else { + return } + + vc.navigationController?.popToRootViewController(animated: false) } } @@ -154,7 +162,6 @@ final class NotificationManager { navigateToHistory(presenter: presenter) case .friend(let requestType): navigateToFriend(presenter: presenter, requestType: requestType) - default: break } } @@ -205,7 +212,10 @@ final class NotificationManager { self.showNotificationView(view: view) } else { - presenter.wireFrame.showHistory() + let time = withDelay ? DispatchTime.now() + 0.3 : DispatchTime.now() + DispatchQueue.main.asyncAfter(deadline: time) { + presenter.wireFrame.showHistory() + } clean() } } @@ -356,9 +366,15 @@ final class NotificationManager { // MARK: - Show Notification View private func showNotificationView(view: NotificationView) { - previousView?.remove() - previousView = view - view.show(duration: 3) + dispatchAsyncMain { [weak self] in + guard let `self` = self else { + return + } + + self.previousView?.remove() + self.previousView = view + view.show(duration: 3) + } } // MARK: - Get Notification View -- GitLab From bda7150f929157dfd2cc097d062d825687703400 Mon Sep 17 00:00:00 2001 From: Volodymyr Hryhoriev Date: Fri, 9 Nov 2018 11:24:57 +0200 Subject: [PATCH 28/68] Make `AlertManager` present view controller on main thread. --- Nynja/Library/UI/AlertManager.swift | 53 ++++++++++++++++++++--------- 1 file changed, 36 insertions(+), 17 deletions(-) diff --git a/Nynja/Library/UI/AlertManager.swift b/Nynja/Library/UI/AlertManager.swift index f0519ed17..c013d0aa9 100644 --- a/Nynja/Library/UI/AlertManager.swift +++ b/Nynja/Library/UI/AlertManager.swift @@ -19,13 +19,13 @@ class AlertManager { return UIApplication.shared.keyWindow?.presentedViewController } - func showAlertOk(title: String, message: String, completion:(()->Void)? = nil) { + func showAlertOk(title: String, message: String, completion: (()->Void)? = nil) { let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) let defaultAction = UIAlertAction(title: String.localizable.ok, style: .default) { (action) in completion?() } alert.addAction(defaultAction) - presentingController?.present(alert, animated: true, completion: nil) + present(alert, from: presentingController) } func showAlert(title: String, dismissInterval: TimeInterval) { @@ -33,7 +33,7 @@ class AlertManager { dispatchAsyncMainAfter(dismissInterval) { alert.dismiss(animated: true, completion: nil) } - presentingController?.present(alert, animated: true, completion: nil) + present(alert, from: presentingController) } func showAlertOk(message: String, completion:(()->Void)? = nil) { @@ -58,7 +58,7 @@ class AlertManager { textFieldConfig.map { alert.addTextField(configurationHandler: $0) } - presentingController?.present(alert, animated: true, completion: nil) + present(alert, from: presentingController) } func showAlertWithTwoActions(title: String, message: String, firstActionTitle: String, @@ -74,13 +74,14 @@ class AlertManager { alert.addAction(firstAlertAction) alert.addAction(secondAlertAction) - presentingController?.present(alert, animated: true, completion: nil) + present(alert, from: presentingController) } func showAlertWithThreeActions(title: String?, message: String?, firstActionTitle: String, secondActionTitle: String, thirdActionTitle: String, firstAction: Handler?, secondAction: Handler?, thirdAction: Handler?) { let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) + let firstAlertAction = UIAlertAction(title: firstActionTitle, style: .default) { (action) in firstAction?() } @@ -90,11 +91,12 @@ class AlertManager { let thirdAlertAction = UIAlertAction(title: thirdActionTitle, style: .default) { (action) in thirdAction?() } + alert.addAction(firstAlertAction) alert.addAction(secondAlertAction) alert.addAction(thirdAlertAction) - presentingController?.present(alert, animated: true, completion: nil) + present(alert, from: presentingController) } func showAlertAllowPermission(title: String = "", @@ -144,8 +146,7 @@ class AlertManager { func showActionSheet(title: String?, message: String?, actions: [UIAlertAction]) { let alert = UIAlertController(title: title, message: message, preferredStyle: .actionSheet) actions.forEach { alert.addAction($0) } - - presentingController?.present(alert, animated: true, completion: nil) + present(alert, from: presentingController) } func linkMenu(open:(()->Void)? = nil, showInChat:(()->Void)? = nil, share:(()->Void)? = nil, cancel: (()->Void)? = nil) { @@ -218,21 +219,23 @@ class AlertManager { func showNativeShare(with activityItems: [Any]) { let activityViewController = UIActivityViewController(activityItems: activityItems, applicationActivities: nil) - - presentingController?.present(activityViewController, animated: true, completion: nil) + present(activityViewController, from: presentingController) } } + // MARK: - No Internet connection + extension AlertManager { func showNoInternetConnection() { showAlertOk(message: String.localizable.noInternetConnection) } - } + // MARK: - AlertImageViewController + extension AlertManager { func showImageAlert(with image: UIImage?, message: String?, on viewController: UIViewController? = nil, duration: Double? = nil, completion: AlertImageViewController.DismissCompletion? = nil) { @@ -241,19 +244,35 @@ extension AlertManager { alertController.duration = duration alertController.completion = completion - if let viewController = viewController { - viewController.present(alertController, animated: true, completion: nil) - } else { - presentingController?.present(alertController, animated: true, completion: nil) - } + let presentingVC = viewController ?? presentingController + present(alertController, from: presentingVC) } } + //MARK: - TimeFieldViewController + extension AlertManager { + func showTimeFieldAlert(with initialValue: Int, completion: TimeCallback?) { let alertControler = AlertTextFieldViewController(initialValue: initialValue) alertControler.completion = completion - presentingController?.present(alertControler, animated: true, completion: nil) + present(alertControler, from: presentingController) + } +} + + +// MARK: - Private + +private extension AlertManager { + + func present(_ vc: UIViewController, from presentingVC: UIViewController?, animated: Bool = true) { + guard let presentingVC = presentingVC else { + return + } + + dispatchAsyncMain { + presentingVC.present(vc, animated: animated, completion: nil) + } } } -- GitLab From 3daad58d9b4c9c455c5e2e74707b8bcd3828b91c Mon Sep 17 00:00:00 2001 From: Volodymyr Hryhoriev Date: Fri, 9 Nov 2018 12:50:36 +0200 Subject: [PATCH 29/68] Make `StorageService` thread safe. --- Nynja/MemberHandler.swift | 4 +- .../Services/HandleServices/RoomHandler.swift | 1 - Nynja/Services/StorageService.swift | 17 +++++- Nynja/StorageService+UserInfo.swift | 60 ++++++++++++------- Nynja/UserInfo.swift | 1 - 5 files changed, 57 insertions(+), 26 deletions(-) diff --git a/Nynja/MemberHandler.swift b/Nynja/MemberHandler.swift index 361d24a8b..2332ee5a9 100644 --- a/Nynja/MemberHandler.swift +++ b/Nynja/MemberHandler.swift @@ -23,9 +23,7 @@ class MemberHandler: BaseHandler { try? StorageService.sharedInstance.perform(action: .save, with: member) default: return - } - + } } - } diff --git a/Nynja/Services/HandleServices/RoomHandler.swift b/Nynja/Services/HandleServices/RoomHandler.swift index 6193bb931..29df69ebd 100644 --- a/Nynja/Services/HandleServices/RoomHandler.swift +++ b/Nynja/Services/HandleServices/RoomHandler.swift @@ -228,5 +228,4 @@ class RoomHandler: BaseHandler { oldRoom.readers = room.readers oldRoom.status = room.status } - } diff --git a/Nynja/Services/StorageService.swift b/Nynja/Services/StorageService.swift index d003ee29f..0d214a471 100644 --- a/Nynja/Services/StorageService.swift +++ b/Nynja/Services/StorageService.swift @@ -28,6 +28,10 @@ class StorageService { } #endif + let isolationQueue = DispatchQueue( + label: String.label(withSuffix: "storage-service.isolation-queue"), + attributes: .concurrent) + // MARK: - Properties lazy var countries: [CountryModel] = { @@ -48,6 +52,12 @@ class StorageService { // MARK: - Setup func setupDatabase(with name: String, application: UIApplication) { + isolationQueue.sync(flags: .barrier) { [weak self] in + self?._setupDatabase(with: name, application: application) + } + } + + private func _setupDatabase(with name: String, application: UIApplication) { #if !SHARE_EXTENSION LogService.log(topic: .db) { return "Setup DB: name = \(name)" } @@ -103,6 +113,12 @@ class StorageService { // MARK: - Clear func clearStorage() { + isolationQueue.sync(flags: .barrier) { [weak self] in + self?._clearStorage() + } + } + + private func _clearStorage() { #if !SHARE_EXTENSION databaseManager.clear() #endif @@ -145,6 +161,5 @@ extension StorageService: DBManagerProtocol { func perform(action: DatabaseAction, with models: [DBModelProtocol]) throws { try databaseManager.perform(action: action, with: models) } - } #endif diff --git a/Nynja/StorageService+UserInfo.swift b/Nynja/StorageService+UserInfo.swift index 2e7b6d7b5..e567eb45e 100644 --- a/Nynja/StorageService+UserInfo.swift +++ b/Nynja/StorageService+UserInfo.swift @@ -15,7 +15,7 @@ extension StorageService: UserInfo { } private var tokenData: Data? { - return userDefaults?.data(forKey: UserIdentifiers.token.rawValue) + return value(forId: .token) } var token: String? { @@ -25,15 +25,19 @@ extension StorageService: UserInfo { return token } set { - guard let token = newValue, let data = token.data(using: encoding) else { - userDefaults?.removeObject(forKey: UserIdentifiers.token.rawValue) - userDefaults?.synchronize() - return + isolationQueue.async(flags: .barrier) { [weak self] in + guard let `self` = self else { return } + + guard let token = newValue, let data = token.data(using: self.encoding) else { + self.userDefaults?.removeObject(forKey: UserIdentifiers.token.rawValue) + self.userDefaults?.synchronize() + return + } + + self.set(data as NSData, forId: .token, isAsync: false) + LogService.log(topic: .userDefaults) { return "Save token: \(token)" } + MQTTService.sharedInstance.reconnect() } - - set(data as NSData, forId: .token) - LogService.log(topic: .userDefaults) { return "Save token: \(token)" } - MQTTService.sharedInstance.reconnect() } } @@ -42,30 +46,27 @@ extension StorageService: UserInfo { } var phone: String? { - get { return userDefaults?.string(forKey: UserIdentifiers.phone.rawValue) } + get { return value(forId: .phone) } set { set(newValue, forId: .phone) } } var rosterId: Int64? { - get { - guard let id = userDefaults?.value(forKey: UserIdentifiers.rosterId.rawValue) as? Int64 else { return nil } - return id - } + get { return value(forId: .rosterId) } set { set(newValue, forId: .rosterId) } } var clientId: String? { - get { return userDefaults?.string(forKey: UserIdentifiers.clientId.rawValue) } + get { return value(forId: .clientId) } set { set(newValue, forId: .clientId) } } var wasLogined: Bool { - get { return userDefaults?.bool(forKey: UserIdentifiers.wasLogined.rawValue) ?? false } + get { return value(forId: .wasLogined) ?? false } set { set(newValue, forId: .wasLogined) } } var wasRun: Bool { - get { return userDefaults?.bool(forKey: UserIdentifiers.wasRun.rawValue) ?? false } + get { return value(forId: .wasRun) ?? false } set { set(newValue, forId: .wasRun) } } @@ -79,9 +80,28 @@ extension StorageService: UserInfo { rosterId = roster.id } - private func set(_ value: Any?, forId id: UserIdentifiers) { - userDefaults?.set(value, forKey: id.rawValue) - userDefaults?.synchronize() + private func value(forId id: UserIdentifiers) -> T? { + var value: T? + isolationQueue.sync { [weak self] in + value = self?.userDefaults?.value(forKey: id.rawValue) as? T + } + return value } + private func set(_ value: Any?, forId id: UserIdentifiers, isAsync: Bool = true) { + let set = { [weak self] (value: Any?, id: UserIdentifiers) in + guard let `self` = self else { return } + + self.userDefaults?.set(value, forKey: id.rawValue) + self.userDefaults?.synchronize() + } + + if isAsync { + isolationQueue.async { + set(value, id) + } + } else { + set(value, id) + } + } } diff --git a/Nynja/UserInfo.swift b/Nynja/UserInfo.swift index b54af5b57..50d70c707 100644 --- a/Nynja/UserInfo.swift +++ b/Nynja/UserInfo.swift @@ -61,5 +61,4 @@ extension UserInfo { rosterId = nil clientId = nil } - } -- GitLab From 7b78e556a6a8a8ef9f9ff56fdf68ef08320d4636 Mon Sep 17 00:00:00 2001 From: Volodymyr Hryhoriev Date: Fri, 9 Nov 2018 13:54:01 +0200 Subject: [PATCH 30/68] Call delegate methods in main thread for in `IoHandler`. --- Nynja/Services/MQTT/MQTTService.swift | 2 +- Shared/Services/Handlers/IoHandler.swift | 60 ++++++++++++------------ 2 files changed, 32 insertions(+), 30 deletions(-) diff --git a/Nynja/Services/MQTT/MQTTService.swift b/Nynja/Services/MQTT/MQTTService.swift index 45ff69be5..da49aa27c 100644 --- a/Nynja/Services/MQTT/MQTTService.swift +++ b/Nynja/Services/MQTT/MQTTService.swift @@ -175,7 +175,7 @@ final class MQTTService: NSObject, CocoaMQTTDelegate, ConnectionServiceDelegate self.state = .notAuthenticated(isLoggedOutFromServer: true) - IoHandler.delegate?.sessionNotFound() + IoHandler.notify { $0.sessionNotFound() } notifySubscribers { (delegate) in delegate.mqttServiceDidReceiveAuthenticationFailure(self) } diff --git a/Shared/Services/Handlers/IoHandler.swift b/Shared/Services/Handlers/IoHandler.swift index f6fd64282..0ce08c23c 100644 --- a/Shared/Services/Handlers/IoHandler.swift +++ b/Shared/Services/Handlers/IoHandler.swift @@ -33,7 +33,6 @@ protocol IoHandlerDelegate: class { func contactQRNotFound() func contactByUsernameNotFound() func usernameIsBusy() - } extension IoHandlerDelegate { @@ -62,11 +61,9 @@ extension IoHandlerDelegate { func usernameIsBusy() {} func numberNotAllowed() {} - - } -class IoHandler:BaseHandler { +class IoHandler: BaseHandler { static weak var delegate: IoHandlerDelegate? @@ -78,10 +75,6 @@ class IoHandler:BaseHandler { return .sharedInstance } - static var keychainService: KeychainService { - return .standard - } - static func executeHandle(data: BertTuple) { if let IO = get_io().parse(bert: data) as? io { var code: String? = nil @@ -110,68 +103,68 @@ class IoHandler:BaseHandler { if let action = code { switch action { case "deleted": - self.delegate?.sessionDeleted() + notify { $0.sessionDeleted() } case "cleared": - self.delegate?.sessionsCleared() + notify { $0.sessionsCleared() } case "sms_sent": - self.delegate?.smsSent() + notify { $0.smsSent() } case "invalid_data": - self.delegate?.invalidData() + notify { $0.invalidData() } case "session_not_found": - self.delegate?.sessionNotFound() + notify { $0.sessionNotFound() } case "call_in_progress": - self.delegate?.callInProgress() + notify { $0.callInProgress() } case "login": StorageService.sharedInstance.wasLogined = true case "mismatch_user_data": - self.delegate?.mismatchUserData() + notify { $0.mismatchUserData() } case "invalid_sms_code": - self.delegate?.wrongCode() + notify { $0.wrongCode() } case "attempts_expired": - self.delegate?.attemptsExpired() + notify { $0.attemptsExpired() } case "number_not_allowed": - self.delegate?.numberNotAllowed() + notify { $0.numberNotAllowed() } case "logout": LogService.log(topic: .db) { return "Clear storage: IoHandler" } storageService.clearStorage() - + mqttService.state = .notAuthenticated(isLoggedOutFromServer: true) mqttService.disconnect() mqttService.reconnect() - self.delegate?.logout() + notify { $0.logout() } case "phone": if let roster = IO.data as? Roster { if let contact = roster.userlist?.first { - self.delegate?.getContactSuccess(contact: contact) + notify { $0.getContactSuccess(contact: contact) } } else { - self.delegate?.contactNotFound() + notify { $0.contactNotFound() } } } case "phonebook": if let roster = IO.data as? Roster { if let contacts = roster.userlist { - self.delegate?.getContactsSuccess(contacts: contacts) + notify { $0.getContactsSuccess(contacts: contacts) } } else { - self.delegate?.contactsNotFound() + notify { $0.contactsNotFound() } } } case "qrcode": if let roster = IO.data as? Roster { if let contact = roster.userlist?.first { - self.delegate?.getContactQRSuccess(contact: contact) + notify { $0.getContactQRSuccess(contact: contact) } } else { - self.delegate?.contactQRNotFound() + notify { $0.contactQRNotFound() } } } case "nick": - self.delegate?.usernameIsBusy() + notify { $0.usernameIsBusy() } case "username": if let roster = IO.data as? Roster { if let contact = roster.userlist?.first { - self.delegate?.getContactByUsernameSucces(contact: contact) + notify { $0.getContactByUsernameSucces(contact: contact) } } else { - self.delegate?.contactByUsernameNotFound() + notify { $0.contactByUsernameNotFound() } } } default: @@ -181,4 +174,13 @@ class IoHandler:BaseHandler { } } + static func notify(_ closure: (IoHandlerDelegate) -> Void) { + guard let delegate = self.delegate else { + return + } + + dispatchAsyncMain { + closure(delegate) + } + } } -- GitLab From 45b40f471de04eb512a9febdda34e673400a8c06 Mon Sep 17 00:00:00 2001 From: Volodymyr Hryhoriev Date: Fri, 9 Nov 2018 13:54:17 +0200 Subject: [PATCH 31/68] Update `ContactHandler`. --- Nynja/Services/HandleServices/ContactHandler.swift | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Nynja/Services/HandleServices/ContactHandler.swift b/Nynja/Services/HandleServices/ContactHandler.swift index 8a6aa1cfe..cf29ea6ed 100644 --- a/Nynja/Services/HandleServices/ContactHandler.swift +++ b/Nynja/Services/HandleServices/ContactHandler.swift @@ -16,6 +16,10 @@ class ContactHandler: BaseHandler { return .sharedInstance } + static var notificationManager: NotificationManager { + return .shared + } + // MARK: - Handler @@ -79,7 +83,7 @@ class ContactHandler: BaseHandler { if [.request, .authorization, .ignore].contains(prevStatus) { let friendRequestType: InAppNotificationType.FriendRequestType = prevStatus == .request ? .outcoming : .incoming - NotificationManager.shared.handle(bert: data, + notificationManager.handle(bert: data, type: .friend(friendRequestType) ) } } catch { @@ -90,7 +94,7 @@ class ContactHandler: BaseHandler { private static func handleAuthorization(_ contact: Contact, data: BertTuple) { do { try storageService.perform(action: .save, with: contact) - NotificationManager.shared.handle(bert: data, type: .request) + notificationManager.handle(bert: data, type: .request) } catch { LogService.log(topic: .db) { return "Storage Service Error: can't save contact with status 'authorization'" } } -- GitLab From be16dea70cf49c2a7ad535f79715564a66461243 Mon Sep 17 00:00:00 2001 From: Volodymyr Hryhoriev Date: Fri, 9 Nov 2018 13:55:31 +0200 Subject: [PATCH 32/68] Rename `HistoryHandlerDelegate` as `HistoryHandlerSubscriber`. --- Nynja.xcodeproj/project.pbxproj | 26 ++++++++++++++++--- Nynja/HistoryHandlerSubscriber.swift | 21 +++++++++++++++ .../Interactor/MessageInteractor.swift | 4 +-- .../HandleServices/HistoryHandler.swift | 20 +++----------- .../MessageHandlerSubscriber.swift | 1 + Shared/Services/Handlers/ErrorsHandler.swift | 1 - 6 files changed, 51 insertions(+), 22 deletions(-) create mode 100644 Nynja/HistoryHandlerSubscriber.swift diff --git a/Nynja.xcodeproj/project.pbxproj b/Nynja.xcodeproj/project.pbxproj index 614866c94..7fa5725a2 100644 --- a/Nynja.xcodeproj/project.pbxproj +++ b/Nynja.xcodeproj/project.pbxproj @@ -656,6 +656,7 @@ 4B877139219315AA0014AD09 /* SercerModelConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B877138219315AA0014AD09 /* SercerModelConvertible.swift */; }; 4B87713B219328780014AD09 /* QueryArgs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B87713A219328780014AD09 /* QueryArgs.swift */; }; 4B87713E219350940014AD09 /* DBJobQueryArgs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B87713D219350940014AD09 /* DBJobQueryArgs.swift */; }; + 4B8771782195AC5B0014AD09 /* HistoryHandlerSubscriber.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8771772195AC5B0014AD09 /* HistoryHandlerSubscriber.swift */; }; 4B8996C8204ECE9B00DCB183 /* ContactDAO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8996C7204ECE9B00DCB183 /* ContactDAO.swift */; }; 4B8996CA204ECEA700DCB183 /* ContactDAOProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8996C9204ECEA700DCB183 /* ContactDAOProtocol.swift */; }; 4B8996CD204ED33400DCB183 /* StarDAOProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8996CC204ED33400DCB183 /* StarDAOProtocol.swift */; }; @@ -2938,6 +2939,7 @@ 4B877138219315AA0014AD09 /* SercerModelConvertible.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SercerModelConvertible.swift; sourceTree = ""; }; 4B87713A219328780014AD09 /* QueryArgs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueryArgs.swift; sourceTree = ""; }; 4B87713D219350940014AD09 /* DBJobQueryArgs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DBJobQueryArgs.swift; sourceTree = ""; }; + 4B8771772195AC5B0014AD09 /* HistoryHandlerSubscriber.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryHandlerSubscriber.swift; sourceTree = ""; }; 4B8996C7204ECE9B00DCB183 /* ContactDAO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactDAO.swift; sourceTree = ""; }; 4B8996C9204ECEA700DCB183 /* ContactDAOProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactDAOProtocol.swift; sourceTree = ""; }; 4B8996CC204ED33400DCB183 /* StarDAOProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StarDAOProtocol.swift; sourceTree = ""; }; @@ -5938,14 +5940,13 @@ isa = PBXGroup; children = ( A497F56520EFA517005CC60F /* Base */, + 4B8771752195ABE80014AD09 /* MessageHandler */, + 4B8771762195AC3C0014AD09 /* HistoryHandler */, 265AEA141FE9AFA600AC4806 /* MemberHandler.swift */, 269666171FB57963009E41C1 /* RoomHandler.swift */, 3A771CA91F191B38008D968A /* ProfileHandler.swift */, - 3A19FEAC1F3B7F1D00ACE750 /* MessageHandler.swift */, - 855C9FE52125B4C0000E3429 /* MessageHandlerSubscriber.swift */, 3A2374D81F262A1600701045 /* ContactHandler.swift */, 3A237BCC1F30E5D400C42B6E /* RosterHandler.swift */, - 3A1EB9A41F3A848A00658E93 /* HistoryHandler.swift */, 260552A51F9E1CD100D68DE6 /* SearchHandler.swift */, 263D66321FE8D95100A509F8 /* TypingHandler.swift */, 26FA420F201821B400E6F6EC /* StarHandler.swift */, @@ -6991,6 +6992,24 @@ name = Entity; sourceTree = ""; }; + 4B8771752195ABE80014AD09 /* MessageHandler */ = { + isa = PBXGroup; + children = ( + 3A19FEAC1F3B7F1D00ACE750 /* MessageHandler.swift */, + 855C9FE52125B4C0000E3429 /* MessageHandlerSubscriber.swift */, + ); + name = MessageHandler; + sourceTree = ""; + }; + 4B8771762195AC3C0014AD09 /* HistoryHandler */ = { + isa = PBXGroup; + children = ( + 3A1EB9A41F3A848A00658E93 /* HistoryHandler.swift */, + 4B8771772195AC5B0014AD09 /* HistoryHandlerSubscriber.swift */, + ); + name = HistoryHandler; + sourceTree = ""; + }; 4B8996C6204ECE8500DCB183 /* Contact */ = { isa = PBXGroup; children = ( @@ -15343,6 +15362,7 @@ FEA656052167777F00B44029 /* WalletBalancesWallet.swift in Sources */, A45F112E20B4218D00F45004 /* MessageContentProtocol.swift in Sources */, 0008E9132032D5AC003E316E /* MQTTServiceSchedule.swift in Sources */, + 4B8771782195AC5B0014AD09 /* HistoryHandlerSubscriber.swift in Sources */, A432CF1A20B4347D00993AFB /* MaterialTextInput.swift in Sources */, B77C11E62109254800CCB42E /* InterpretationTypePresenter.swift in Sources */, B750EF062046D7C700A99F9C /* SpeedStringRepresentable.swift in Sources */, diff --git a/Nynja/HistoryHandlerSubscriber.swift b/Nynja/HistoryHandlerSubscriber.swift new file mode 100644 index 000000000..08580e950 --- /dev/null +++ b/Nynja/HistoryHandlerSubscriber.swift @@ -0,0 +1,21 @@ +// +// HistoryHandlerSubscriber.swift +// Nynja +// +// Created by Volodymyr Hryhoriev on 11/9/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + +protocol HistoryHandlerSubscriber: class { + func getHistorySuccess() + func getJobsHistorySuccess() + func getStickerPacksSuccess() +} + +extension HistoryHandlerSubscriber { + func getHistorySuccess() {} + func getJobsHistorySuccess() {} + func getStickerPacksSuccess() {} +} diff --git a/Nynja/Modules/Message/Interactor/MessageInteractor.swift b/Nynja/Modules/Message/Interactor/MessageInteractor.swift index d455ff61b..686bad549 100644 --- a/Nynja/Modules/Message/Interactor/MessageInteractor.swift +++ b/Nynja/Modules/Message/Interactor/MessageInteractor.swift @@ -10,7 +10,7 @@ import UIKit import CoreLocation -final class MessageInteractor: BaseInteractor, MessageInteractorInputProtocol, HistoryHandlerDelegate, TypingHandlerDelegate, ConnectionServiceDelegate, MQTTServiceDelegate, MessageProcessingDelegate, MessageHandlerSubscriber, MessageInteractorCallProtocol { +final class MessageInteractor: BaseInteractor, MessageInteractorInputProtocol, HistoryHandlerSubscriber, TypingHandlerDelegate, ConnectionServiceDelegate, MQTTServiceDelegate, MessageProcessingDelegate, MessageHandlerSubscriber, MessageInteractorCallProtocol { private var callService = NynjaCommunicatorService.sharedInstance @@ -915,7 +915,7 @@ final class MessageInteractor: BaseInteractor, MessageInteractorInputProtocol, H } } - // MARK: - HistoryHandlerDelegate + // MARK: - HistoryHandlerSubscriber private var isNew = false private var isHistoryUpdating: Bool = false diff --git a/Nynja/Services/HandleServices/HistoryHandler.swift b/Nynja/Services/HandleServices/HistoryHandler.swift index 11d6bf824..4b8bb4a73 100644 --- a/Nynja/Services/HandleServices/HistoryHandler.swift +++ b/Nynja/Services/HandleServices/HistoryHandler.swift @@ -8,18 +8,6 @@ import Foundation -protocol HistoryHandlerDelegate: class { - func getHistorySuccess() - func getJobsHistorySuccess() - func getStickerPacksSuccess() -} - -extension HistoryHandlerDelegate { - func getHistorySuccess() {} - func getJobsHistorySuccess() {} - func getStickerPacksSuccess() {} -} - final class HistoryHandler: BaseHandler { // MARK: - Subscribers @@ -28,13 +16,13 @@ final class HistoryHandler: BaseHandler { private static var subscribers = [WeakRef]() - private static func notify(block: (HistoryHandlerDelegate) -> Void) { + private static func notify(block: (HistoryHandlerSubscriber) -> Void) { subscribersLock.lock() - subscribers.forEach { ($0.value as? HistoryHandlerDelegate).map { block($0) } } + subscribers.forEach { ($0.value as? HistoryHandlerSubscriber).map { block($0) } } subscribersLock.unlock() } - static func addSubscriber(_ subscriber: HistoryHandlerDelegate) { + static func addSubscriber(_ subscriber: HistoryHandlerSubscriber) { subscribersLock.lock() defer { subscribersLock.unlock() } @@ -45,7 +33,7 @@ final class HistoryHandler: BaseHandler { subscribers.append(ref) } - static func removeSubscriber(_ subscriber: HistoryHandlerDelegate) { + static func removeSubscriber(_ subscriber: HistoryHandlerSubscriber) { subscribersLock.lock() subscribers = subscribers.filter { $0.value != nil && $0.value !== subscriber } subscribersLock.unlock() diff --git a/Nynja/Services/HandleServices/MessageHandlerSubscriber.swift b/Nynja/Services/HandleServices/MessageHandlerSubscriber.swift index 35f8bf9ef..08e51eb72 100644 --- a/Nynja/Services/HandleServices/MessageHandlerSubscriber.swift +++ b/Nynja/Services/HandleServices/MessageHandlerSubscriber.swift @@ -10,6 +10,7 @@ protocol MessageHandlerSubscriber: class { func willSave(_ message: Message) func didSave(_ message: Message) } + extension MessageHandlerSubscriber { func willSave(_ message: Message) { } func didSave(_ message: Message) { } diff --git a/Shared/Services/Handlers/ErrorsHandler.swift b/Shared/Services/Handlers/ErrorsHandler.swift index d284a35cb..5465d1571 100644 --- a/Shared/Services/Handlers/ErrorsHandler.swift +++ b/Shared/Services/Handlers/ErrorsHandler.swift @@ -20,5 +20,4 @@ final class ErrorsHandler: BaseHandler { let handler = HandlerFactory.handler(for: handlerKind) handler.executeHandle(data: dataTuple, codes: Set(codes)) } - } -- GitLab From f59a346e60fb022dd17f3efc2392d0c857f207bc Mon Sep 17 00:00:00 2001 From: Volodymyr Hryhoriev Date: Fri, 9 Nov 2018 13:55:43 +0200 Subject: [PATCH 33/68] Correct `IoHandler`. --- Shared/Services/Handlers/IoHandler.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Shared/Services/Handlers/IoHandler.swift b/Shared/Services/Handlers/IoHandler.swift index 0ce08c23c..9700c7486 100644 --- a/Shared/Services/Handlers/IoHandler.swift +++ b/Shared/Services/Handlers/IoHandler.swift @@ -174,7 +174,7 @@ class IoHandler: BaseHandler { } } - static func notify(_ closure: (IoHandlerDelegate) -> Void) { + static func notify(_ closure: @escaping (IoHandlerDelegate) -> Void) { guard let delegate = self.delegate else { return } -- GitLab From c385770e4c85bde32c18f853a548d0b3decfa079 Mon Sep 17 00:00:00 2001 From: Volodymyr Hryhoriev Date: Fri, 9 Nov 2018 14:04:28 +0200 Subject: [PATCH 34/68] Make `NotificationManager` more thread safe. --- Nynja/NotificationManager.swift | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Nynja/NotificationManager.swift b/Nynja/NotificationManager.swift index d1a6987a8..16778475e 100644 --- a/Nynja/NotificationManager.swift +++ b/Nynja/NotificationManager.swift @@ -120,8 +120,10 @@ final class NotificationManager { // MARK: - Handle BERT func handle(bert: BertTuple, type: InAppNotificationType) { - tupleForNavigate = bert - typeForNavigate = type + processingQueue.async { [weak self] in + self?.tupleForNavigate = bert + self?.typeForNavigate = type + } } @@ -443,7 +445,9 @@ final class NotificationManager { // MARK: - Clean func clean() { - self.typeForNavigate = nil - self.tupleForNavigate = nil + processingQueue.async { [weak self] in + self?.typeForNavigate = nil + self?.tupleForNavigate = nil + } } } -- GitLab From fdca9198f675696131fb0b48eab43c634fa6f1e4 Mon Sep 17 00:00:00 2001 From: Volodymyr Hryhoriev Date: Fri, 9 Nov 2018 14:23:21 +0200 Subject: [PATCH 35/68] Call delegate methods in main thread for in `TypingHandler `. --- Nynja.xcodeproj/project.pbxproj | 18 +++++++++++- .../HandleServices/SearchHandler.swift | 1 - .../HandleServices/TypingHandler.swift | 10 ++----- Nynja/TypingHandlerDelegate.swift | 13 +++++++++ Shared/Library/StaticDelegating.swift | 28 +++++++++++++++++++ 5 files changed, 60 insertions(+), 10 deletions(-) create mode 100644 Nynja/TypingHandlerDelegate.swift create mode 100644 Shared/Library/StaticDelegating.swift diff --git a/Nynja.xcodeproj/project.pbxproj b/Nynja.xcodeproj/project.pbxproj index 7fa5725a2..27b2e4ca3 100644 --- a/Nynja.xcodeproj/project.pbxproj +++ b/Nynja.xcodeproj/project.pbxproj @@ -657,6 +657,8 @@ 4B87713B219328780014AD09 /* QueryArgs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B87713A219328780014AD09 /* QueryArgs.swift */; }; 4B87713E219350940014AD09 /* DBJobQueryArgs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B87713D219350940014AD09 /* DBJobQueryArgs.swift */; }; 4B8771782195AC5B0014AD09 /* HistoryHandlerSubscriber.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8771772195AC5B0014AD09 /* HistoryHandlerSubscriber.swift */; }; + 4B87717B2195AF9D0014AD09 /* TypingHandlerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B87717A2195AF9D0014AD09 /* TypingHandlerDelegate.swift */; }; + 4B87717D2195AFF50014AD09 /* StaticDelegating.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B87717C2195AFF50014AD09 /* StaticDelegating.swift */; }; 4B8996C8204ECE9B00DCB183 /* ContactDAO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8996C7204ECE9B00DCB183 /* ContactDAO.swift */; }; 4B8996CA204ECEA700DCB183 /* ContactDAOProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8996C9204ECEA700DCB183 /* ContactDAOProtocol.swift */; }; 4B8996CD204ED33400DCB183 /* StarDAOProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8996CC204ED33400DCB183 /* StarDAOProtocol.swift */; }; @@ -2940,6 +2942,8 @@ 4B87713A219328780014AD09 /* QueryArgs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueryArgs.swift; sourceTree = ""; }; 4B87713D219350940014AD09 /* DBJobQueryArgs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DBJobQueryArgs.swift; sourceTree = ""; }; 4B8771772195AC5B0014AD09 /* HistoryHandlerSubscriber.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryHandlerSubscriber.swift; sourceTree = ""; }; + 4B87717A2195AF9D0014AD09 /* TypingHandlerDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TypingHandlerDelegate.swift; sourceTree = ""; }; + 4B87717C2195AFF50014AD09 /* StaticDelegating.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StaticDelegating.swift; sourceTree = ""; }; 4B8996C7204ECE9B00DCB183 /* ContactDAO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactDAO.swift; sourceTree = ""; }; 4B8996C9204ECEA700DCB183 /* ContactDAOProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactDAOProtocol.swift; sourceTree = ""; }; 4B8996CC204ED33400DCB183 /* StarDAOProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StarDAOProtocol.swift; sourceTree = ""; }; @@ -5942,13 +5946,13 @@ A497F56520EFA517005CC60F /* Base */, 4B8771752195ABE80014AD09 /* MessageHandler */, 4B8771762195AC3C0014AD09 /* HistoryHandler */, + 4B8771792195AF7E0014AD09 /* TypingHandler */, 265AEA141FE9AFA600AC4806 /* MemberHandler.swift */, 269666171FB57963009E41C1 /* RoomHandler.swift */, 3A771CA91F191B38008D968A /* ProfileHandler.swift */, 3A2374D81F262A1600701045 /* ContactHandler.swift */, 3A237BCC1F30E5D400C42B6E /* RosterHandler.swift */, 260552A51F9E1CD100D68DE6 /* SearchHandler.swift */, - 263D66321FE8D95100A509F8 /* TypingHandler.swift */, 26FA420F201821B400E6F6EC /* StarHandler.swift */, 0008E91A20333A38003E316E /* JobHandler.swift */, 855EF424202CCADB00541BE3 /* ExtendedStarHandler.swift */, @@ -7010,6 +7014,15 @@ name = HistoryHandler; sourceTree = ""; }; + 4B8771792195AF7E0014AD09 /* TypingHandler */ = { + isa = PBXGroup; + children = ( + 4B87717A2195AF9D0014AD09 /* TypingHandlerDelegate.swift */, + 263D66321FE8D95100A509F8 /* TypingHandler.swift */, + ); + name = TypingHandler; + sourceTree = ""; + }; 4B8996C6204ECE8500DCB183 /* Contact */ = { isa = PBXGroup; children = ( @@ -10912,6 +10925,7 @@ A49E1BCA20A9A6880074DFD3 /* Library */ = { isa = PBXGroup; children = ( + 4B87717C2195AFF50014AD09 /* StaticDelegating.swift */, A4C92FFC20B323B600D6FB0F /* Extensions */, A49E1BCB20A9A68D0074DFD3 /* Models */, ); @@ -15575,6 +15589,7 @@ 2605311B212740FD002E1CF1 /* LogOutputProtocols.swift in Sources */, FBCE841420E525A6003B7558 /* NetworkService.swift in Sources */, A409B1CF2108D48E0051C20B /* QueryFactory.swift in Sources */, + 4B87717D2195AFF50014AD09 /* StaticDelegating.swift in Sources */, A42D52B7206A53AA00EEB952 /* reader_Spec.swift in Sources */, F119E66A20D24B960043A532 /* MultiplePreviewProtocols.swift in Sources */, 850FC611203312FA00832D87 /* ForwardSelectorViewControllerLayout.swift in Sources */, @@ -15698,6 +15713,7 @@ 26DCB2412064B9B1001EF0AB /* InviteFriendHeaderViewLayout.swift in Sources */, F105C6BB20A1347E0091786A /* PhotoPreviewPresenter.swift in Sources */, FEA655FA2167777F00B44029 /* TransferDetailsPresenter.swift in Sources */, + 4B87717B2195AF9D0014AD09 /* TypingHandlerDelegate.swift in Sources */, 8ED0F3CE1FBC5CF2004916AB /* GroupsListInteractor.swift in Sources */, 267BE2851FDE983400C47E18 /* SettingsGroupVC.swift in Sources */, 8580BAC720BD983400239D9D /* MentionFetchProtocols.swift in Sources */, diff --git a/Nynja/Services/HandleServices/SearchHandler.swift b/Nynja/Services/HandleServices/SearchHandler.swift index 9d8570b15..55acb374b 100644 --- a/Nynja/Services/HandleServices/SearchHandler.swift +++ b/Nynja/Services/HandleServices/SearchHandler.swift @@ -28,5 +28,4 @@ class SearchHandler: BaseHandler { break } } - } diff --git a/Nynja/Services/HandleServices/TypingHandler.swift b/Nynja/Services/HandleServices/TypingHandler.swift index e9023cba8..15b48900e 100644 --- a/Nynja/Services/HandleServices/TypingHandler.swift +++ b/Nynja/Services/HandleServices/TypingHandler.swift @@ -8,18 +8,12 @@ import Foundation -protocol TypingHandlerDelegate: class { - func getTyping(typing: Typing) -} - -class TypingHandler: BaseHandler { - +class TypingHandler: BaseHandler, StaticDelegating { static weak var delegate: TypingHandlerDelegate? static func executeHandle(data: BertTuple) { if let typing = get_Typing().parse(bert: data) as? Typing { - delegate?.getTyping(typing: typing) + delegate { $0.getTyping(typing: typing) } } } - } diff --git a/Nynja/TypingHandlerDelegate.swift b/Nynja/TypingHandlerDelegate.swift new file mode 100644 index 000000000..de1844d5f --- /dev/null +++ b/Nynja/TypingHandlerDelegate.swift @@ -0,0 +1,13 @@ +// +// TypingHandlerDelegate.swift +// Nynja +// +// Created by Volodymyr Hryhoriev on 11/9/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + +protocol TypingHandlerDelegate: class { + func getTyping(typing: Typing) +} diff --git a/Shared/Library/StaticDelegating.swift b/Shared/Library/StaticDelegating.swift new file mode 100644 index 000000000..87d9661e1 --- /dev/null +++ b/Shared/Library/StaticDelegating.swift @@ -0,0 +1,28 @@ +// +// StaticDelegating.swift +// Nynja +// +// Created by Volodymyr Hryhoriev on 11/9/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +protocol StaticDelegating { + associatedtype Delegate + + static var delegate: Delegate? { get set } + + static func delegate(on queue: DispatchQueue, closure: @escaping (Delegate) -> Void) +} + +extension StaticDelegating { + + static func delegate(on queue: DispatchQueue = .main, closure: @escaping (Delegate) -> Void) { + guard let delegate = self.delegate else { + return + } + + queue.async { + closure(delegate) + } + } +} -- GitLab From 3f72354d768107f8428147a0f16303295a5f490f Mon Sep 17 00:00:00 2001 From: Volodymyr Hryhoriev Date: Fri, 9 Nov 2018 14:33:19 +0200 Subject: [PATCH 36/68] Correct `TypingHandler`. --- Nynja.xcodeproj/project.pbxproj | 18 +++- .../HandleServices/TypingHandler.swift | 2 +- Nynja/Services/MQTT/MQTTService.swift | 2 +- Shared/Library/StaticDelegating.swift | 2 + .../Handlers/{ => IoHandler}/IoHandler.swift | 99 +++++-------------- .../IoHandler/IoHandlerDelegate.swift | 64 ++++++++++++ 6 files changed, 107 insertions(+), 80 deletions(-) rename Shared/Services/Handlers/{ => IoHandler}/IoHandler.swift (56%) create mode 100644 Shared/Services/Handlers/IoHandler/IoHandlerDelegate.swift diff --git a/Nynja.xcodeproj/project.pbxproj b/Nynja.xcodeproj/project.pbxproj index 27b2e4ca3..12dab44f0 100644 --- a/Nynja.xcodeproj/project.pbxproj +++ b/Nynja.xcodeproj/project.pbxproj @@ -560,6 +560,9 @@ 4B06D3202028A9B1003B275B /* P2pChatItemsFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B06D31F2028A9B1003B275B /* P2pChatItemsFactory.swift */; }; 4B06D3222028A9C6003B275B /* GroupChatItemsFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B06D3212028A9C6003B275B /* GroupChatItemsFactory.swift */; }; 4B06D3242028B209003B275B /* WCBaseItemsFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B06D3232028B209003B275B /* WCBaseItemsFactory.swift */; }; + 4B0CC1FD2195B52000E0BA61 /* IoHandlerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B0CC1FC2195B52000E0BA61 /* IoHandlerDelegate.swift */; }; + 4B0CC1FE2195B52000E0BA61 /* IoHandlerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B0CC1FC2195B52000E0BA61 /* IoHandlerDelegate.swift */; }; + 4B0CC1FF2195B58000E0BA61 /* StaticDelegating.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B87717C2195AFF50014AD09 /* StaticDelegating.swift */; }; 4B1D7DFA2029BF3400703228 /* HistoryItemsFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B1D7DF92029BF3400703228 /* HistoryItemsFactory.swift */; }; 4B1D7DFC2029C37900703228 /* FavoritesItemsFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B1D7DFB2029C37900703228 /* FavoritesItemsFactory.swift */; }; 4B1D7DFE2029C41C00703228 /* AboutItemsFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B1D7DFD2029C41C00703228 /* AboutItemsFactory.swift */; }; @@ -2858,6 +2861,7 @@ 4B06D31F2028A9B1003B275B /* P2pChatItemsFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = P2pChatItemsFactory.swift; sourceTree = ""; }; 4B06D3212028A9C6003B275B /* GroupChatItemsFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupChatItemsFactory.swift; sourceTree = ""; }; 4B06D3232028B209003B275B /* WCBaseItemsFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WCBaseItemsFactory.swift; sourceTree = ""; }; + 4B0CC1FC2195B52000E0BA61 /* IoHandlerDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IoHandlerDelegate.swift; sourceTree = ""; }; 4B15A544CC681BABD1A631AF /* QRCodeGeneratorInteractor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = QRCodeGeneratorInteractor.swift; sourceTree = ""; }; 4B1D7DF92029BF3400703228 /* HistoryItemsFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryItemsFactory.swift; sourceTree = ""; }; 4B1D7DFB2029C37900703228 /* FavoritesItemsFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoritesItemsFactory.swift; sourceTree = ""; }; @@ -6600,6 +6604,15 @@ name = Chat; sourceTree = ""; }; + 4B0CC1FB2195B3C200E0BA61 /* IoHandler */ = { + isa = PBXGroup; + children = ( + 3A1DC73E1EF15B65006A8E9F /* IoHandler.swift */, + 4B0CC1FC2195B52000E0BA61 /* IoHandlerDelegate.swift */, + ); + path = IoHandler; + sourceTree = ""; + }; 4B0DBA892137F6F800D79163 /* ChatService */ = { isa = PBXGroup; children = ( @@ -10980,8 +10993,8 @@ isa = PBXGroup; children = ( A497F56C20EFA86F005CC60F /* Base */, + 4B0CC1FB2195B3C200E0BA61 /* IoHandler */, A497F56D20EFA8B6005CC60F /* ErrorsHandler.swift */, - 3A1DC73E1EF15B65006A8E9F /* IoHandler.swift */, ); path = Handlers; sourceTree = ""; @@ -14612,6 +14625,7 @@ A42CE60420692EDB000889CC /* serviceTask_Spec.swift in Sources */, 264FFA981FC5917D0028243D /* WheelItemModel.swift in Sources */, 8503B51C20503B4B006F0593 /* NynjaCellButton.swift in Sources */, + 4B0CC1FE2195B52000E0BA61 /* IoHandlerDelegate.swift in Sources */, A4F3DAB32084949F00FF71C7 /* MessageExtension+BERT.swift in Sources */, 26B32B941FE20B9700888A0A /* mucExtension+BERT.swift in Sources */, A42CE5B620692EDB000889CC /* Room_Spec.swift in Sources */, @@ -14819,6 +14833,7 @@ A497F56E20EFA8B6005CC60F /* ErrorsHandler.swift in Sources */, 263D66311FE8D30200A509F8 /* TypingExtension+BERT.swift in Sources */, A42CE56220692EDB000889CC /* cur.swift in Sources */, + 4B0CC1FF2195B58000E0BA61 /* StaticDelegating.swift in Sources */, E7A77FDB1FACC58A004AE609 /* KeychainService.swift in Sources */, 85D669E720BD959800FBD803 /* Int+AnyObject.swift in Sources */, A42CE58420692EDB000889CC /* Vox.swift in Sources */, @@ -15019,6 +15034,7 @@ 3A1F74FA1F5ED344009A11E4 /* PushService.swift in Sources */, FEA656042167777F00B44029 /* WalletBalancesInteractor.swift in Sources */, 261F2E2E200EB0AD007D0813 /* RepliesVC+CellDelegate.swift in Sources */, + 4B0CC1FD2195B52000E0BA61 /* IoHandlerDelegate.swift in Sources */, A45F110620B4218D00F45004 /* MessageConfiguration.swift in Sources */, 26F5C8BE206BD49B003A7FF5 /* DefaultActionItemModel.swift in Sources */, 4B0213042037331100650298 /* ScheduleMessageViewControllerConstants.swift in Sources */, diff --git a/Nynja/Services/HandleServices/TypingHandler.swift b/Nynja/Services/HandleServices/TypingHandler.swift index 15b48900e..ce19b3bb3 100644 --- a/Nynja/Services/HandleServices/TypingHandler.swift +++ b/Nynja/Services/HandleServices/TypingHandler.swift @@ -8,7 +8,7 @@ import Foundation -class TypingHandler: BaseHandler, StaticDelegating { +final class TypingHandler: BaseHandler, StaticDelegating { static weak var delegate: TypingHandlerDelegate? static func executeHandle(data: BertTuple) { diff --git a/Nynja/Services/MQTT/MQTTService.swift b/Nynja/Services/MQTT/MQTTService.swift index da49aa27c..5ffe2ffb2 100644 --- a/Nynja/Services/MQTT/MQTTService.swift +++ b/Nynja/Services/MQTT/MQTTService.swift @@ -175,7 +175,7 @@ final class MQTTService: NSObject, CocoaMQTTDelegate, ConnectionServiceDelegate self.state = .notAuthenticated(isLoggedOutFromServer: true) - IoHandler.notify { $0.sessionNotFound() } + IoHandler.delegate { $0.sessionNotFound() } notifySubscribers { (delegate) in delegate.mqttServiceDidReceiveAuthenticationFailure(self) } diff --git a/Shared/Library/StaticDelegating.swift b/Shared/Library/StaticDelegating.swift index 87d9661e1..39173973e 100644 --- a/Shared/Library/StaticDelegating.swift +++ b/Shared/Library/StaticDelegating.swift @@ -6,6 +6,8 @@ // Copyright © 2018 TecSynt Solutions. All rights reserved. // +import Foundation + protocol StaticDelegating { associatedtype Delegate diff --git a/Shared/Services/Handlers/IoHandler.swift b/Shared/Services/Handlers/IoHandler/IoHandler.swift similarity index 56% rename from Shared/Services/Handlers/IoHandler.swift rename to Shared/Services/Handlers/IoHandler/IoHandler.swift index 9700c7486..90565ee0e 100644 --- a/Shared/Services/Handlers/IoHandler.swift +++ b/Shared/Services/Handlers/IoHandler/IoHandler.swift @@ -8,62 +8,7 @@ import Foundation -protocol IoHandlerDelegate: class { - func smsSent() - func logined() - func wrongCode() - func mismatchUserData() - func sessionNotFound() - func attemptsExpired() - func notAuthorized() - func added() - func invalidData() - func callInProgress() - func logout() - func numberNotAllowed() - func sessionsCleared() - func sessionDeleted() - - func getContactSuccess(contact: Contact) - func getContactQRSuccess(contact: Contact) - func getContactByUsernameSucces(contact: Contact) - func getContactsSuccess(contacts: [Contact]) - func contactNotFound() - func contactsNotFound() - func contactQRNotFound() - func contactByUsernameNotFound() - func usernameIsBusy() -} - -extension IoHandlerDelegate { - func smsSent() {} - func logined() {} - func wrongCode() {} - func mismatchUserData() {} - func sessionNotFound() {} - func attemptsExpired() {} - func notAuthorized() {} - func added() {} - func invalidData() {} - func callInProgress() {} - func logout() {} - func sessionsCleared() {} - func sessionDeleted() {} - - func getContactSuccess(contact: Contact) {} - func getContactQRSuccess(contact: Contact) {} - func getContactByUsernameSucces(contact: Contact) {} - func getContactsSuccess(contacts: [Contact]) {} - func contactNotFound() {} - func contactsNotFound() {} - func contactQRNotFound() {} - func contactByUsernameNotFound() {} - - func usernameIsBusy() {} - func numberNotAllowed() {} -} - -class IoHandler: BaseHandler { +class IoHandler: BaseHandler, StaticDelegating { static weak var delegate: IoHandlerDelegate? @@ -103,27 +48,27 @@ class IoHandler: BaseHandler { if let action = code { switch action { case "deleted": - notify { $0.sessionDeleted() } + delegate { $0.sessionDeleted() } case "cleared": - notify { $0.sessionsCleared() } + delegate { $0.sessionsCleared() } case "sms_sent": - notify { $0.smsSent() } + delegate { $0.smsSent() } case "invalid_data": - notify { $0.invalidData() } + delegate { $0.invalidData() } case "session_not_found": - notify { $0.sessionNotFound() } + delegate { $0.sessionNotFound() } case "call_in_progress": - notify { $0.callInProgress() } + delegate { $0.callInProgress() } case "login": StorageService.sharedInstance.wasLogined = true case "mismatch_user_data": - notify { $0.mismatchUserData() } + delegate { $0.mismatchUserData() } case "invalid_sms_code": - notify { $0.wrongCode() } + delegate { $0.wrongCode() } case "attempts_expired": - notify { $0.attemptsExpired() } + delegate { $0.attemptsExpired() } case "number_not_allowed": - notify { $0.numberNotAllowed() } + delegate { $0.numberNotAllowed() } case "logout": LogService.log(topic: .db) { return "Clear storage: IoHandler" } storageService.clearStorage() @@ -132,39 +77,39 @@ class IoHandler: BaseHandler { mqttService.disconnect() mqttService.reconnect() - notify { $0.logout() } + delegate { $0.logout() } case "phone": if let roster = IO.data as? Roster { if let contact = roster.userlist?.first { - notify { $0.getContactSuccess(contact: contact) } + delegate { $0.getContactSuccess(contact: contact) } } else { - notify { $0.contactNotFound() } + delegate { $0.contactNotFound() } } } case "phonebook": if let roster = IO.data as? Roster { if let contacts = roster.userlist { - notify { $0.getContactsSuccess(contacts: contacts) } + delegate { $0.getContactsSuccess(contacts: contacts) } } else { - notify { $0.contactsNotFound() } + delegate { $0.contactsNotFound() } } } case "qrcode": if let roster = IO.data as? Roster { if let contact = roster.userlist?.first { - notify { $0.getContactQRSuccess(contact: contact) } + delegate { $0.getContactQRSuccess(contact: contact) } } else { - notify { $0.contactQRNotFound() } + delegate { $0.contactQRNotFound() } } } case "nick": - notify { $0.usernameIsBusy() } + delegate { $0.usernameIsBusy() } case "username": if let roster = IO.data as? Roster { if let contact = roster.userlist?.first { - notify { $0.getContactByUsernameSucces(contact: contact) } + delegate { $0.getContactByUsernameSucces(contact: contact) } } else { - notify { $0.contactByUsernameNotFound() } + delegate { $0.contactByUsernameNotFound() } } } default: @@ -174,7 +119,7 @@ class IoHandler: BaseHandler { } } - static func notify(_ closure: @escaping (IoHandlerDelegate) -> Void) { + static func delegate(_ closure: @escaping (IoHandlerDelegate) -> Void) { guard let delegate = self.delegate else { return } diff --git a/Shared/Services/Handlers/IoHandler/IoHandlerDelegate.swift b/Shared/Services/Handlers/IoHandler/IoHandlerDelegate.swift new file mode 100644 index 000000000..47393593b --- /dev/null +++ b/Shared/Services/Handlers/IoHandler/IoHandlerDelegate.swift @@ -0,0 +1,64 @@ +// +// IoHandlerDelegate.swift +// Nynja +// +// Created by Volodymyr Hryhoriev on 11/9/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + +protocol IoHandlerDelegate: class { + func smsSent() + func logined() + func wrongCode() + func mismatchUserData() + func sessionNotFound() + func attemptsExpired() + func notAuthorized() + func added() + func invalidData() + func callInProgress() + func logout() + func numberNotAllowed() + func sessionsCleared() + func sessionDeleted() + + func getContactSuccess(contact: Contact) + func getContactQRSuccess(contact: Contact) + func getContactByUsernameSucces(contact: Contact) + func getContactsSuccess(contacts: [Contact]) + func contactNotFound() + func contactsNotFound() + func contactQRNotFound() + func contactByUsernameNotFound() + func usernameIsBusy() +} + +extension IoHandlerDelegate { + func smsSent() {} + func logined() {} + func wrongCode() {} + func mismatchUserData() {} + func sessionNotFound() {} + func attemptsExpired() {} + func notAuthorized() {} + func added() {} + func invalidData() {} + func callInProgress() {} + func logout() {} + func sessionsCleared() {} + func sessionDeleted() {} + + func getContactSuccess(contact: Contact) {} + func getContactQRSuccess(contact: Contact) {} + func getContactByUsernameSucces(contact: Contact) {} + func getContactsSuccess(contacts: [Contact]) {} + func contactNotFound() {} + func contactsNotFound() {} + func contactQRNotFound() {} + func contactByUsernameNotFound() {} + + func usernameIsBusy() {} + func numberNotAllowed() {} +} -- GitLab From f63322b77ef864ed65ea1834160f9a3ac31dcfdc Mon Sep 17 00:00:00 2001 From: Volodymyr Hryhoriev Date: Fri, 9 Nov 2018 14:37:54 +0200 Subject: [PATCH 37/68] Correct `IoHandler`. --- Shared/Services/Handlers/IoHandler/IoHandler.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Shared/Services/Handlers/IoHandler/IoHandler.swift b/Shared/Services/Handlers/IoHandler/IoHandler.swift index 90565ee0e..61aa420c3 100644 --- a/Shared/Services/Handlers/IoHandler/IoHandler.swift +++ b/Shared/Services/Handlers/IoHandler/IoHandler.swift @@ -8,7 +8,7 @@ import Foundation -class IoHandler: BaseHandler, StaticDelegating { +final class IoHandler: BaseHandler, StaticDelegating { static weak var delegate: IoHandlerDelegate? -- GitLab From c7033c2b46a24bd479baddc212f5744f56a1de9d Mon Sep 17 00:00:00 2001 From: Volodymyr Hryhoriev Date: Fri, 9 Nov 2018 15:10:15 +0200 Subject: [PATCH 38/68] Call delegate methods in main thread for in `LinkHandler`. --- Nynja.xcodeproj/project.pbxproj | 16 ++++++++++++++-- Nynja/LinkHandler.swift | 13 +++---------- Nynja/LinkHandlerDelegate.swift | 12 ++++++++++++ 3 files changed, 29 insertions(+), 12 deletions(-) create mode 100644 Nynja/LinkHandlerDelegate.swift diff --git a/Nynja.xcodeproj/project.pbxproj b/Nynja.xcodeproj/project.pbxproj index 12dab44f0..ed3dc051e 100644 --- a/Nynja.xcodeproj/project.pbxproj +++ b/Nynja.xcodeproj/project.pbxproj @@ -563,6 +563,7 @@ 4B0CC1FD2195B52000E0BA61 /* IoHandlerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B0CC1FC2195B52000E0BA61 /* IoHandlerDelegate.swift */; }; 4B0CC1FE2195B52000E0BA61 /* IoHandlerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B0CC1FC2195B52000E0BA61 /* IoHandlerDelegate.swift */; }; 4B0CC1FF2195B58000E0BA61 /* StaticDelegating.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B87717C2195AFF50014AD09 /* StaticDelegating.swift */; }; + 4B0CC2022195B69900E0BA61 /* LinkHandlerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B0CC2012195B69900E0BA61 /* LinkHandlerDelegate.swift */; }; 4B1D7DFA2029BF3400703228 /* HistoryItemsFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B1D7DF92029BF3400703228 /* HistoryItemsFactory.swift */; }; 4B1D7DFC2029C37900703228 /* FavoritesItemsFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B1D7DFB2029C37900703228 /* FavoritesItemsFactory.swift */; }; 4B1D7DFE2029C41C00703228 /* AboutItemsFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B1D7DFD2029C41C00703228 /* AboutItemsFactory.swift */; }; @@ -2862,6 +2863,7 @@ 4B06D3212028A9C6003B275B /* GroupChatItemsFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupChatItemsFactory.swift; sourceTree = ""; }; 4B06D3232028B209003B275B /* WCBaseItemsFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WCBaseItemsFactory.swift; sourceTree = ""; }; 4B0CC1FC2195B52000E0BA61 /* IoHandlerDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IoHandlerDelegate.swift; sourceTree = ""; }; + 4B0CC2012195B69900E0BA61 /* LinkHandlerDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkHandlerDelegate.swift; sourceTree = ""; }; 4B15A544CC681BABD1A631AF /* QRCodeGeneratorInteractor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = QRCodeGeneratorInteractor.swift; sourceTree = ""; }; 4B1D7DF92029BF3400703228 /* HistoryItemsFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryItemsFactory.swift; sourceTree = ""; }; 4B1D7DFB2029C37900703228 /* FavoritesItemsFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoritesItemsFactory.swift; sourceTree = ""; }; @@ -5951,6 +5953,7 @@ 4B8771752195ABE80014AD09 /* MessageHandler */, 4B8771762195AC3C0014AD09 /* HistoryHandler */, 4B8771792195AF7E0014AD09 /* TypingHandler */, + 4B0CC2002195B67D00E0BA61 /* LinkHandler */, 265AEA141FE9AFA600AC4806 /* MemberHandler.swift */, 269666171FB57963009E41C1 /* RoomHandler.swift */, 3A771CA91F191B38008D968A /* ProfileHandler.swift */, @@ -5961,7 +5964,6 @@ 0008E91A20333A38003E316E /* JobHandler.swift */, 855EF424202CCADB00541BE3 /* ExtendedStarHandler.swift */, 00E9825B205FD351008BF03D /* AuthHandler.swift */, - A48C154320EF7A15002DA994 /* LinkHandler.swift */, ); name = Handlers; sourceTree = ""; @@ -6613,6 +6615,15 @@ path = IoHandler; sourceTree = ""; }; + 4B0CC2002195B67D00E0BA61 /* LinkHandler */ = { + isa = PBXGroup; + children = ( + A48C154320EF7A15002DA994 /* LinkHandler.swift */, + 4B0CC2012195B69900E0BA61 /* LinkHandlerDelegate.swift */, + ); + name = LinkHandler; + sourceTree = ""; + }; 4B0DBA892137F6F800D79163 /* ChatService */ = { isa = PBXGroup; children = ( @@ -7030,8 +7041,8 @@ 4B8771792195AF7E0014AD09 /* TypingHandler */ = { isa = PBXGroup; children = ( - 4B87717A2195AF9D0014AD09 /* TypingHandlerDelegate.swift */, 263D66321FE8D95100A509F8 /* TypingHandler.swift */, + 4B87717A2195AF9D0014AD09 /* TypingHandlerDelegate.swift */, ); name = TypingHandler; sourceTree = ""; @@ -16345,6 +16356,7 @@ A43B25D720AB1EE400FF8107 /* NewChannelWireFrame.swift in Sources */, A4166F5C205FE3670008F231 /* JobService.swift in Sources */, 26F03C0D20698B0000712CB0 /* ChatWheelItemModel.swift in Sources */, + 4B0CC2022195B69900E0BA61 /* LinkHandlerDelegate.swift in Sources */, E4F62F7771D4BB7FE3B48FA2 /* VideoPreviewProtocols.swift in Sources */, FBD8857A2147F9640099B8C3 /* AssetsConstants.swift in Sources */, 8580BAEC20BD9A7100239D9D /* LinkRecognizable.swift in Sources */, diff --git a/Nynja/LinkHandler.swift b/Nynja/LinkHandler.swift index feebe3ae1..620ad386d 100644 --- a/Nynja/LinkHandler.swift +++ b/Nynja/LinkHandler.swift @@ -6,13 +6,7 @@ // Copyright © 2018 TecSynt Solutions. All rights reserved. // -protocol LinkHandlerDelegate: class { - func generatedLink(_ link: Link) - func linkIsAvailable(_ link: Link) -} - - -final class LinkHandler: BaseHandler { +final class LinkHandler: BaseHandler, StaticDelegating { static weak var delegate: LinkHandlerDelegate? @@ -35,9 +29,9 @@ final class LinkHandler: BaseHandler { switch status { case .check: - delegate?.linkIsAvailable(link) + delegate { $0.linkIsAvailable(link) } case .gen: - delegate?.generatedLink(link) + delegate { $0.generatedLink(link) } case .add, .delete, .update: // TODO: will be implemented in future. break @@ -51,5 +45,4 @@ final class LinkHandler: BaseHandler { .filter { StatusCode.linkCodes.contains($0) } .forEach { statusCodeManager.notify(model: link, code: $0) } } - } diff --git a/Nynja/LinkHandlerDelegate.swift b/Nynja/LinkHandlerDelegate.swift new file mode 100644 index 000000000..dd65b2588 --- /dev/null +++ b/Nynja/LinkHandlerDelegate.swift @@ -0,0 +1,12 @@ +// +// LinkHandlerDelegate.swift +// Nynja +// +// Created by Volodymyr Hryhoriev on 11/9/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +protocol LinkHandlerDelegate: class { + func generatedLink(_ link: Link) + func linkIsAvailable(_ link: Link) +} -- GitLab From 45789b297759920779910e8d57ec68c81be36726 Mon Sep 17 00:00:00 2001 From: Volodymyr Hryhoriev Date: Fri, 9 Nov 2018 15:23:54 +0200 Subject: [PATCH 39/68] Call delegate methods in main thread for in `AuthHandler`. --- Nynja.xcodeproj/project.pbxproj | 26 ++++++++++++++----- .../Handlers/AuthHandler}/AuthHandler.swift | 24 +++++++---------- .../AuthHandler/AuthHandlerDelegate.swift | 19 ++++++++++++++ 3 files changed, 48 insertions(+), 21 deletions(-) rename {Nynja => Shared/Services/Handlers/AuthHandler}/AuthHandler.swift (59%) create mode 100644 Shared/Services/Handlers/AuthHandler/AuthHandlerDelegate.swift diff --git a/Nynja.xcodeproj/project.pbxproj b/Nynja.xcodeproj/project.pbxproj index ed3dc051e..277c808fd 100644 --- a/Nynja.xcodeproj/project.pbxproj +++ b/Nynja.xcodeproj/project.pbxproj @@ -64,9 +64,7 @@ 00E98254205C2726008BF03D /* SessionFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00E98253205C2726008BF03D /* SessionFooterView.swift */; }; 00E98256205C2740008BF03D /* SessionItemCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00E98255205C2740008BF03D /* SessionItemCell.swift */; }; 00E9825A205FC324008BF03D /* SessionDescView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00E98259205FC324008BF03D /* SessionDescView.swift */; }; - 00E9825C205FD351008BF03D /* AuthHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00E9825B205FD351008BF03D /* AuthHandler.swift */; }; 00E9825E205FDB1A008BF03D /* AuthExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00E9825D205FDB1A008BF03D /* AuthExtension.swift */; }; - 00E9825F205FE86E008BF03D /* AuthHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00E9825B205FD351008BF03D /* AuthHandler.swift */; }; 00EB7872206E286A00E3FB03 /* WCReusableViews.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00EB7871206E286A00E3FB03 /* WCReusableViews.swift */; }; 00EDCA0D202B7243000928D4 /* TimeZoneSelectorDS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00EDCA0C202B7243000928D4 /* TimeZoneSelectorDS.swift */; }; 00F7B33E2029DD4B00E443E1 /* AudioItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00F7B33D2029DD4B00E443E1 /* AudioItemView.swift */; }; @@ -542,6 +540,10 @@ 45F60C4B14438C65076457AB /* EditUsernameProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83894D517BFF22637F2878B7 /* EditUsernameProtocols.swift */; }; 4B02130220372C5700650298 /* OtherItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B02130120372C5700650298 /* OtherItemView.swift */; }; 4B0213042037331100650298 /* ScheduleMessageViewControllerConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B0213032037331100650298 /* ScheduleMessageViewControllerConstants.swift */; }; + 4B030F2D2195BFF300F293B7 /* AuthHandlerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B030F2B2195BFF300F293B7 /* AuthHandlerDelegate.swift */; }; + 4B030F2E2195BFF300F293B7 /* AuthHandlerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B030F2B2195BFF300F293B7 /* AuthHandlerDelegate.swift */; }; + 4B030F2F2195BFF300F293B7 /* AuthHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B030F2C2195BFF300F293B7 /* AuthHandler.swift */; }; + 4B030F302195BFF300F293B7 /* AuthHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B030F2C2195BFF300F293B7 /* AuthHandler.swift */; }; 4B052CB0203614D400BC2A9B /* StringAtomExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B052CAF203614D400BC2A9B /* StringAtomExtension.swift */; }; 4B052CB12036193900BC2A9B /* StringAtomExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B052CAF203614D400BC2A9B /* StringAtomExtension.swift */; }; 4B058F03204EA928004C7D9F /* DAOProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B058F02204EA928004C7D9F /* DAOProtocol.swift */; }; @@ -2455,7 +2457,6 @@ 00E98253205C2726008BF03D /* SessionFooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionFooterView.swift; sourceTree = ""; }; 00E98255205C2740008BF03D /* SessionItemCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionItemCell.swift; sourceTree = ""; }; 00E98259205FC324008BF03D /* SessionDescView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionDescView.swift; sourceTree = ""; }; - 00E9825B205FD351008BF03D /* AuthHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthHandler.swift; sourceTree = ""; }; 00E9825D205FDB1A008BF03D /* AuthExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthExtension.swift; sourceTree = ""; }; 00EB7871206E286A00E3FB03 /* WCReusableViews.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WCReusableViews.swift; sourceTree = ""; }; 00EBF72D0964E3EC64F5B966 /* SplashProtocols.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = SplashProtocols.swift; sourceTree = ""; }; @@ -2842,6 +2843,8 @@ 499373C9A81A05B77308A5F0 /* AddContactByUsernameProtocols.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AddContactByUsernameProtocols.swift; sourceTree = ""; }; 4B02130120372C5700650298 /* OtherItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OtherItemView.swift; sourceTree = ""; }; 4B0213032037331100650298 /* ScheduleMessageViewControllerConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScheduleMessageViewControllerConstants.swift; sourceTree = ""; }; + 4B030F2B2195BFF300F293B7 /* AuthHandlerDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthHandlerDelegate.swift; sourceTree = ""; }; + 4B030F2C2195BFF300F293B7 /* AuthHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthHandler.swift; sourceTree = ""; }; 4B052CAF203614D400BC2A9B /* StringAtomExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringAtomExtension.swift; sourceTree = ""; }; 4B058EFD204EA751004C7D9F /* ProfileDAO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileDAO.swift; sourceTree = ""; }; 4B058F00204EA7F5004C7D9F /* DBManagerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DBManagerProtocol.swift; sourceTree = ""; }; @@ -5963,7 +5966,6 @@ 26FA420F201821B400E6F6EC /* StarHandler.swift */, 0008E91A20333A38003E316E /* JobHandler.swift */, 855EF424202CCADB00541BE3 /* ExtendedStarHandler.swift */, - 00E9825B205FD351008BF03D /* AuthHandler.swift */, ); name = Handlers; sourceTree = ""; @@ -6474,6 +6476,15 @@ path = DateTime; sourceTree = ""; }; + 4B030F2A2195BFF300F293B7 /* AuthHandler */ = { + isa = PBXGroup; + children = ( + 4B030F2B2195BFF300F293B7 /* AuthHandlerDelegate.swift */, + 4B030F2C2195BFF300F293B7 /* AuthHandler.swift */, + ); + path = AuthHandler; + sourceTree = ""; + }; 4B058EF9204EA6EE004C7D9F /* DB */ = { isa = PBXGroup; children = ( @@ -11005,6 +11016,7 @@ children = ( A497F56C20EFA86F005CC60F /* Base */, 4B0CC1FB2195B3C200E0BA61 /* IoHandler */, + 4B030F2A2195BFF300F293B7 /* AuthHandler */, A497F56D20EFA8B6005CC60F /* ErrorsHandler.swift */, ); path = Handlers; @@ -14709,7 +14721,6 @@ 4BF090CD21635FED00DCCA5C /* Message+Type.swift in Sources */, 26A856222074C4F000C642EA /* ActionsView+Layout.swift in Sources */, 266AE8C42034971A0096A12C /* AsyncOperation.swift in Sources */, - 00E9825F205FE86E008BF03D /* AuthHandler.swift in Sources */, 359EB27C1F9A2C8C00147437 /* MQTTServiceHelper.swift in Sources */, 4B5A0B74216E3BDD002C4160 /* ForwardAvatarViewModel.swift in Sources */, A4B544F020EFB4EC00EB7B0F /* BertTupleExtension.swift in Sources */, @@ -14822,6 +14833,7 @@ 8551CF0F2170857300829CF1 /* Message+Construct.swift in Sources */, A42CE57620692EDB000889CC /* push.swift in Sources */, A42CE55020692EDB000889CC /* Feature.swift in Sources */, + 4B030F302195BFF300F293B7 /* AuthHandler.swift in Sources */, 35B1AB831F9FB0DD00E65233 /* AmazonManager.swift in Sources */, 26B32B911FE20B7A00888A0A /* p2pExtension+BERT.swift in Sources */, 262D438820335225002F1E45 /* FriendRequstModel.swift in Sources */, @@ -14946,6 +14958,7 @@ 4B8AB705215CE52300C69DE1 /* SequenceExtension.swift in Sources */, 26C0C1EF2073DE2600C530DA /* ForwardSelectorProtocols+ShareExt.swift in Sources */, 26A8563A20750B5D00C642EA /* ScheduleInfo.swift in Sources */, + 4B030F2E2195BFF300F293B7 /* AuthHandlerDelegate.swift in Sources */, A45F115420B4224100F45004 /* RoomExtension.swift in Sources */, 26C0C1DC2073D94E00C530DA /* P2pExtension.swift in Sources */, 4B5A0B73216E3BDD002C4160 /* ActionsView.swift in Sources */, @@ -15821,6 +15834,7 @@ 8572C3BB2092366100E4840C /* StickerCollectionDataSource.swift in Sources */, E735853D1F6C2705003354B5 /* Geometry.swift in Sources */, 85B0013421272694000C89FE /* MessageInteractor+History.swift in Sources */, + 4B030F2F2195BFF300F293B7 /* AuthHandler.swift in Sources */, F119E67720D27E990043A532 /* ImagePreviewCVCell.swift in Sources */, 260313A720A0A4BA009AC66D /* ChatLanguageSettingsTableDataSource.swift in Sources */, 260313A520A0A4BA009AC66D /* BaseCell.swift in Sources */, @@ -16196,6 +16210,7 @@ FEA655DD2167777E00B44029 /* WalletDetailsProtocols.swift in Sources */, A43B259820AB1DFA00FF8107 /* TextInputContent.swift in Sources */, E723FD221F9E59A600E0B602 /* ProfileSection.swift in Sources */, + 4B030F2D2195BFF300F293B7 /* AuthHandlerDelegate.swift in Sources */, 4B752B652163A64300E852B9 /* Desc+Place.swift in Sources */, 8524C4D22177713C003BF374 /* Member+Status.swift in Sources */, E77D58A01F98C38100FBE926 /* ProfileSectionHeaderView.swift in Sources */, @@ -16650,7 +16665,6 @@ C02DD71CA3832908D422B83C /* CreateGroupWireframe.swift in Sources */, A45F110E20B4218D00F45004 /* PositionType.swift in Sources */, 26EEA5472091F84E0066D3B0 /* CollectionsExtensions.swift in Sources */, - 00E9825C205FD351008BF03D /* AuthHandler.swift in Sources */, 1CCEA1165C5D016C6768E5DC /* GroupRulesProtocols.swift in Sources */, 6B13E514036AA364CE2AC038 /* GroupRulesViewController.swift in Sources */, A4330A712109EBB30060BD93 /* CountriesProviding.swift in Sources */, diff --git a/Nynja/AuthHandler.swift b/Shared/Services/Handlers/AuthHandler/AuthHandler.swift similarity index 59% rename from Nynja/AuthHandler.swift rename to Shared/Services/Handlers/AuthHandler/AuthHandler.swift index c5ec8f5f2..ef1dd76f4 100644 --- a/Nynja/AuthHandler.swift +++ b/Shared/Services/Handlers/AuthHandler/AuthHandler.swift @@ -8,26 +8,20 @@ import Foundation -protocol AuthHandlerDelegate: class { - func processGetAll(auths: [Auth]) - func processDelete(auth: Auth) -} - -extension AuthHandlerDelegate { - func processGetAll(auths: [Auth]) {} - func processDelete(auth: Auth) {} -} - -class AuthHandler: BaseHandler { +final class AuthHandler: BaseHandler, StaticDelegating { static weak var delegate: AuthHandlerDelegate? static func executeHandle(data: BertTuple) { - guard let auth = get_Auth().parse(bert: data) as? Auth else {return} - guard let type = StringAtom.string(auth.type) else {return} + guard let auth = get_Auth().parse(bert: data) as? Auth, + let type = StringAtom.string(auth.type) else { + return + } + if type == "deleted" { - delegate?.processDelete(auth: auth) + delegate { $0.processDelete(auth: auth) } } + if auth.type?.string == "update" { LogService.log(topic: .MQTT) { return "Recived new token: \(auth.token ?? "")" } if let token = auth.token { @@ -38,6 +32,6 @@ class AuthHandler: BaseHandler { static func executeHandle(data: BertList) { let auths = data.elements.compactMap { get_Auth().parse(bert: $0) as? Auth } - delegate?.processGetAll(auths: auths) + delegate { $0.processGetAll(auths: auths) } } } diff --git a/Shared/Services/Handlers/AuthHandler/AuthHandlerDelegate.swift b/Shared/Services/Handlers/AuthHandler/AuthHandlerDelegate.swift new file mode 100644 index 000000000..aa6944289 --- /dev/null +++ b/Shared/Services/Handlers/AuthHandler/AuthHandlerDelegate.swift @@ -0,0 +1,19 @@ +// +// AuthHandlerDelegate.swift +// Nynja +// +// Created by Volodymyr Hryhoriev on 11/9/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + +protocol AuthHandlerDelegate: class { + func processGetAll(auths: [Auth]) + func processDelete(auth: Auth) +} + +extension AuthHandlerDelegate { + func processGetAll(auths: [Auth]) {} + func processDelete(auth: Auth) {} +} -- GitLab From 7ed1b0228c7a93c2db8ba3761b3f0c8150b22554 Mon Sep 17 00:00:00 2001 From: Volodymyr Hryhoriev Date: Fri, 9 Nov 2018 15:30:12 +0200 Subject: [PATCH 40/68] Make small refactoring in handlers. --- Nynja/ExtendedStarHandler.swift | 1 - Nynja/JobHandler.swift | 3 +-- Nynja/Services/HandleServices/ContactHandler.swift | 4 +--- Nynja/Services/HandleServices/ProfileHandler.swift | 2 +- Nynja/Services/HandleServices/RoomHandler.swift | 2 +- Nynja/Services/HandleServices/RosterHandler.swift | 4 +--- Nynja/Services/HandleServices/SearchHandler.swift | 4 +--- Nynja/Services/HandleServices/StarHandler.swift | 8 +++++--- 8 files changed, 11 insertions(+), 17 deletions(-) diff --git a/Nynja/ExtendedStarHandler.swift b/Nynja/ExtendedStarHandler.swift index 0322062f2..4ea3c670b 100644 --- a/Nynja/ExtendedStarHandler.swift +++ b/Nynja/ExtendedStarHandler.swift @@ -24,5 +24,4 @@ final class ExtendedStarHandler: BaseHandler { try? StorageService.sharedInstance.perform(action: .save, with: stars) } - } diff --git a/Nynja/JobHandler.swift b/Nynja/JobHandler.swift index cbf72bb79..0ea1a1720 100644 --- a/Nynja/JobHandler.swift +++ b/Nynja/JobHandler.swift @@ -6,7 +6,7 @@ // Copyright © 2018 TecSynt Solutions. All rights reserved. // -class JobHandler: BaseHandler { +final class JobHandler: BaseHandler { static func executeHandle(data: BertTuple) { guard let job = get_Job().parse(bert: data) as? Job, @@ -23,6 +23,5 @@ class JobHandler: BaseHandler { break } } - } diff --git a/Nynja/Services/HandleServices/ContactHandler.swift b/Nynja/Services/HandleServices/ContactHandler.swift index cf29ea6ed..42104e794 100644 --- a/Nynja/Services/HandleServices/ContactHandler.swift +++ b/Nynja/Services/HandleServices/ContactHandler.swift @@ -6,9 +6,7 @@ // Copyright © 2017 TecSynt Solutions. All rights reserved. // -import Foundation - -class ContactHandler: BaseHandler { +final class ContactHandler: BaseHandler { // MARK: - Dependencies diff --git a/Nynja/Services/HandleServices/ProfileHandler.swift b/Nynja/Services/HandleServices/ProfileHandler.swift index d3a900ce3..1c32e9f90 100644 --- a/Nynja/Services/HandleServices/ProfileHandler.swift +++ b/Nynja/Services/HandleServices/ProfileHandler.swift @@ -6,7 +6,7 @@ // Copyright © 2017 TecSynt Solutions. All rights reserved. // -class ProfileHandler: BaseHandler { +final class ProfileHandler: BaseHandler { // MARK: - Dependencies diff --git a/Nynja/Services/HandleServices/RoomHandler.swift b/Nynja/Services/HandleServices/RoomHandler.swift index 29df69ebd..f935f5d98 100644 --- a/Nynja/Services/HandleServices/RoomHandler.swift +++ b/Nynja/Services/HandleServices/RoomHandler.swift @@ -6,7 +6,7 @@ // Copyright © 2017 TecSynt Solutions. All rights reserved. // -class RoomHandler: BaseHandler { +final class RoomHandler: BaseHandler { // MARK: - Dependencies diff --git a/Nynja/Services/HandleServices/RosterHandler.swift b/Nynja/Services/HandleServices/RosterHandler.swift index 51ac27f17..ea054dddd 100644 --- a/Nynja/Services/HandleServices/RosterHandler.swift +++ b/Nynja/Services/HandleServices/RosterHandler.swift @@ -6,9 +6,7 @@ // Copyright © 2017 TecSynt Solutions. All rights reserved. // -import Foundation - -class RosterHandler: BaseHandler { +final class RosterHandler: BaseHandler { static func executeHandle(data: BertTuple) { guard let roster = get_Roster().parse(bert: data) as? Roster, diff --git a/Nynja/Services/HandleServices/SearchHandler.swift b/Nynja/Services/HandleServices/SearchHandler.swift index 55acb374b..5889095bd 100644 --- a/Nynja/Services/HandleServices/SearchHandler.swift +++ b/Nynja/Services/HandleServices/SearchHandler.swift @@ -6,9 +6,7 @@ // Copyright © 2017 TecSynt Solutions. All rights reserved. // -import Foundation - -class SearchHandler: BaseHandler { +final class SearchHandler: BaseHandler { static func executeHandle(data: BertTuple) { guard let search = get_Search().parse(bert: data) as? Search, diff --git a/Nynja/Services/HandleServices/StarHandler.swift b/Nynja/Services/HandleServices/StarHandler.swift index 416651962..22d335905 100644 --- a/Nynja/Services/HandleServices/StarHandler.swift +++ b/Nynja/Services/HandleServices/StarHandler.swift @@ -6,12 +6,14 @@ // Copyright © 2018 TecSynt Solutions. All rights reserved. // -class StarHandler: BaseHandler { +final class StarHandler: BaseHandler { static func executeHandle(data: BertTuple) { - guard let star = get_Star().parse(bert: data) as? Star, let status = star.starStatus else { - return + guard let star = get_Star().parse(bert: data) as? Star, + let status = star.starStatus else { + return } + do { switch status { case .add: -- GitLab From ab838cdfdd68e8b712a54998f05896ac11e3f7a2 Mon Sep 17 00:00:00 2001 From: Volodymyr Hryhoriev Date: Fri, 9 Nov 2018 16:33:48 +0200 Subject: [PATCH 41/68] Refactor `Aps`. --- Nynja/Services/Aps.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Nynja/Services/Aps.swift b/Nynja/Services/Aps.swift index 371112e55..dc39a64b1 100644 --- a/Nynja/Services/Aps.swift +++ b/Nynja/Services/Aps.swift @@ -56,7 +56,7 @@ struct Aps { self.contact = contact self.room = room - self.isValidIp = (nyn.dns == MQTTService.sharedInstance.host.url) + self.isValidIp = (nyn.dns == Host().url) self.isValidVersion = (NynjaPush.validVersion(version: nyn.version)) self.shouldNotify = NynjaPush.shouldNotify(room: room, contact: contact) @@ -238,7 +238,7 @@ extension Aps { static func validVersion(version: String) -> Bool { guard let versionString = version.components(separatedBy: "/").last, let version = Int(versionString) else { return false } - return version == MQTTService.version + return version == Bundle.main.modelsVersion } static func getContact(model: String) -> Contact? { -- GitLab From f2f881a4e7352f6645ecdb64ead136cf60393c5b Mon Sep 17 00:00:00 2001 From: Volodymyr Hryhoriev Date: Fri, 9 Nov 2018 16:33:59 +0200 Subject: [PATCH 42/68] Refactor `PushService`. --- Nynja/Services/PushService.swift | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Nynja/Services/PushService.swift b/Nynja/Services/PushService.swift index 91c423ae4..5ec5a47a0 100644 --- a/Nynja/Services/PushService.swift +++ b/Nynja/Services/PushService.swift @@ -12,6 +12,10 @@ import UserNotifications final class PushService: NSObject, PKPushRegistryDelegate, UserSettingsRespondable { + private var storageService: StorageService { + return .sharedInstance + } + static let sharedInstance: PushService = { let instance = PushService() instance.registerForPushNotifications() @@ -52,7 +56,7 @@ final class PushService: NSObject, PKPushRegistryDelegate, UserSettingsRespondab @objc(pushRegistry:didReceiveIncomingPushWithPayload:forType:) func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType) { - guard MQTTService.sharedInstance.isAuthenticated, + guard storageService.hasToken, UIApplication.shared.applicationState != .active, let aps = Aps(data: payload.dictionaryPayload), aps.isValidIp, @@ -214,7 +218,7 @@ final class PushService: NSObject, PKPushRegistryDelegate, UserSettingsRespondab message.localStatus = status } try MessageDAO.trustIfNextMessageExists(before: message) - try StorageService.sharedInstance.perform(action: .save, with: message) + try storageService.perform(action: .save, with: message) } private func getMessage(_ aps: Aps) -> Message? { @@ -252,7 +256,7 @@ final class PushService: NSObject, PKPushRegistryDelegate, UserSettingsRespondab if let lastMessage = contact.last_msg { try prepareForSave(message: lastMessage) } - try StorageService.sharedInstance.perform(action: .save, with: contact) + try storageService.perform(action: .save, with: contact) } catch { LogService.log(topic: .db) { return error.localizedDescription } } @@ -263,7 +267,7 @@ final class PushService: NSObject, PKPushRegistryDelegate, UserSettingsRespondab if let lastMessage = room.last_msg { try prepareForSave(message: lastMessage) } - try StorageService.sharedInstance.perform(action: .save, with: room) + try storageService.perform(action: .save, with: room) } catch { LogService.log(topic: .db) { return error.localizedDescription } } -- GitLab From 0d05a31afd520b157a459209f2a00663fcb78c2b Mon Sep 17 00:00:00 2001 From: Volodymyr Hryhoriev Date: Fri, 9 Nov 2018 16:34:13 +0200 Subject: [PATCH 43/68] Refactor `SplashViewController`. --- .../Modules/Splash/View/SplashViewController.swift | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Nynja/Modules/Splash/View/SplashViewController.swift b/Nynja/Modules/Splash/View/SplashViewController.swift index 5ad891dfd..43a74e7c4 100644 --- a/Nynja/Modules/Splash/View/SplashViewController.swift +++ b/Nynja/Modules/Splash/View/SplashViewController.swift @@ -16,7 +16,9 @@ class SplashViewController: BaseVC, SplashViewProtocol { } } + // MARK: - Views + lazy var splash: UIView = { let launchScreen = UIStoryboard(name: "LaunchScreen", bundle: nil).instantiateInitialViewController() if let launchView = launchScreen?.view { @@ -40,7 +42,7 @@ class SplashViewController: BaseVC, SplashViewProtocol { let lbl = UILabel() lbl.font = UIFont(name: FontFamily.NotoSans.bold.name, size: 16)! lbl.numberOfLines = 0 - lbl.textColor = UIColor.nynja.manatee //UIColor.nynja.almostBlack + lbl.textColor = UIColor.nynja.manatee self.view.addSubview(lbl) lbl.snp.makeConstraints({ (make) in @@ -50,16 +52,15 @@ class SplashViewController: BaseVC, SplashViewProtocol { return lbl }() + // MARK: - View lifecycle + override func viewDidLoad() { super.viewDidLoad() self.view.frame = UIScreen.main.bounds splash.isHidden = false - if let dict = Bundle.main.infoDictionary { - if let buildV = dict["CFBundleVersion"] as? String { - let host = MQTTService.sharedInstance.host - info.text = "Build: \(buildV)"//" \nServer: \(host) (\"\(host.url)\")" - } + if let buildVersion = Bundle.main.buildVersion { + info.text = "Build: \(buildVersion)" } } -- GitLab From fcb24e05cb5837ebdf28d9233ea811da6d8fbda4 Mon Sep 17 00:00:00 2001 From: Volodymyr Hryhoriev Date: Fri, 9 Nov 2018 16:59:45 +0200 Subject: [PATCH 44/68] Refactor `MQTTService` and connected modules. --- Nynja.xcodeproj/project.pbxproj | 70 +++- .../Main/Interactor/MainInteractor.swift | 5 - Nynja/Modules/Main/MainProtocols.swift | 1 - .../Main/Presenter/MainPresenter.swift | 1 - .../MQTT/{ => API}/MQTTServiceAuth.swift | 8 +- .../MQTT/{ => API}/MQTTServiceChat.swift | 0 .../MQTT/{ => API}/MQTTServiceFriend.swift | 0 .../MQTT/{ => API}/MQTTServiceLink.swift | 0 .../MQTT/{ => API}/MQTTServiceProfile.swift | 0 .../MQTT/{ => API}/MQTTServiceSchedule.swift | 0 .../MQTT/{ => API}/MQTTServiceStars.swift | 0 .../MQTT/{ => API}/MQTTServiceWallet.swift | 0 Nynja/Services/MQTT/Entities/Host.swift | 19 ++ .../MQTTService+Helper.swift} | 15 +- .../Extensions/MQTTService+QueuePool.swift | 28 ++ Nynja/Services/MQTT/MQTTService.swift | 309 ++++++++---------- Nynja/Services/MQTT/MQTTServiceDelegate.swift | 23 ++ Nynja/Services/PushService.swift | 7 +- Nynja/StorageService+UserInfo.swift | 5 + Nynja/UserInfo.swift | 4 + .../Handlers/IoHandler/IoHandler.swift | 2 - 21 files changed, 276 insertions(+), 221 deletions(-) rename Nynja/Services/MQTT/{ => API}/MQTTServiceAuth.swift (95%) rename Nynja/Services/MQTT/{ => API}/MQTTServiceChat.swift (100%) rename Nynja/Services/MQTT/{ => API}/MQTTServiceFriend.swift (100%) rename Nynja/Services/MQTT/{ => API}/MQTTServiceLink.swift (100%) rename Nynja/Services/MQTT/{ => API}/MQTTServiceProfile.swift (100%) rename Nynja/Services/MQTT/{ => API}/MQTTServiceSchedule.swift (100%) rename Nynja/Services/MQTT/{ => API}/MQTTServiceStars.swift (100%) rename Nynja/Services/MQTT/{ => API}/MQTTServiceWallet.swift (100%) create mode 100644 Nynja/Services/MQTT/Entities/Host.swift rename Nynja/Services/MQTT/{MQTTServiceHelper.swift => Extensions/MQTTService+Helper.swift} (80%) create mode 100644 Nynja/Services/MQTT/Extensions/MQTTService+QueuePool.swift create mode 100644 Nynja/Services/MQTT/MQTTServiceDelegate.swift diff --git a/Nynja.xcodeproj/project.pbxproj b/Nynja.xcodeproj/project.pbxproj index 277c808fd..91aef5bb2 100644 --- a/Nynja.xcodeproj/project.pbxproj +++ b/Nynja.xcodeproj/project.pbxproj @@ -468,7 +468,7 @@ 359EB23D1F9A1BE600147437 /* Queue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A8045D91F60E18E00AED866 /* Queue.swift */; }; 359EB2571F9A1E5000147437 /* BaseMQTTModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A62B7D71F4CB9D100F45B51 /* BaseMQTTModel.swift */; }; 359EB27B1F9A28C500147437 /* MessageHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 359EB27A1F9A28C500147437 /* MessageHandler.swift */; }; - 359EB27C1F9A2C8C00147437 /* MQTTServiceHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A8045D71F60C98200AED866 /* MQTTServiceHelper.swift */; }; + 359EB27C1F9A2C8C00147437 /* MQTTService+Helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A8045D71F60C98200AED866 /* MQTTService+Helper.swift */; }; 359EB2831F9A2E6A00147437 /* ProfileHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 359EB2821F9A2E6A00147437 /* ProfileHandler.swift */; }; 359EB2841F9A6F2300147437 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 6D36F8E41F0ADBD300FA1AC8 /* Localizable.strings */; }; 35B1AB821F9FB06500E65233 /* Attachment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35B1AB811F9FB06500E65233 /* Attachment.swift */; }; @@ -517,7 +517,7 @@ 3A8045D31F60C8E200AED866 /* MQTTServiceProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A8045CF1F60C8E200AED866 /* MQTTServiceProfile.swift */; }; 3A8045D41F60C8E200AED866 /* MQTTServiceAuth.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A8045D01F60C8E200AED866 /* MQTTServiceAuth.swift */; }; 3A8045D61F60C93D00AED866 /* MQTTServiceChat.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A8045D51F60C93D00AED866 /* MQTTServiceChat.swift */; }; - 3A8045D81F60C98200AED866 /* MQTTServiceHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A8045D71F60C98200AED866 /* MQTTServiceHelper.swift */; }; + 3A8045D81F60C98200AED866 /* MQTTService+Helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A8045D71F60C98200AED866 /* MQTTService+Helper.swift */; }; 3A8045DA1F60E18E00AED866 /* Queue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A8045D91F60E18E00AED866 /* Queue.swift */; }; 3AA13C761F2252F900BE5D8F /* SearchModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AA13C751F2252F900BE5D8F /* SearchModel.swift */; }; 3AA4E6ACDBCB060172A7A279 /* FavoritesProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 462440AD41D807CE8957FDD9 /* FavoritesProtocols.swift */; }; @@ -544,6 +544,12 @@ 4B030F2E2195BFF300F293B7 /* AuthHandlerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B030F2B2195BFF300F293B7 /* AuthHandlerDelegate.swift */; }; 4B030F2F2195BFF300F293B7 /* AuthHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B030F2C2195BFF300F293B7 /* AuthHandler.swift */; }; 4B030F302195BFF300F293B7 /* AuthHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B030F2C2195BFF300F293B7 /* AuthHandler.swift */; }; + 4B030F322195CD4500F293B7 /* MQTTServiceDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B030F312195CD4500F293B7 /* MQTTServiceDelegate.swift */; }; + 4B030F332195CD4500F293B7 /* MQTTServiceDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B030F312195CD4500F293B7 /* MQTTServiceDelegate.swift */; }; + 4B030F362195CD9B00F293B7 /* MQTTService+QueuePool.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B030F352195CD9B00F293B7 /* MQTTService+QueuePool.swift */; }; + 4B030F372195CD9B00F293B7 /* MQTTService+QueuePool.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B030F352195CD9B00F293B7 /* MQTTService+QueuePool.swift */; }; + 4B030F3B2195CF8100F293B7 /* Host.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B030F3A2195CF8100F293B7 /* Host.swift */; }; + 4B030F3C2195CF8100F293B7 /* Host.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B030F3A2195CF8100F293B7 /* Host.swift */; }; 4B052CB0203614D400BC2A9B /* StringAtomExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B052CAF203614D400BC2A9B /* StringAtomExtension.swift */; }; 4B052CB12036193900BC2A9B /* StringAtomExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B052CAF203614D400BC2A9B /* StringAtomExtension.swift */; }; 4B058F03204EA928004C7D9F /* DAOProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B058F02204EA928004C7D9F /* DAOProtocol.swift */; }; @@ -2812,7 +2818,7 @@ 3A8045CF1F60C8E200AED866 /* MQTTServiceProfile.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MQTTServiceProfile.swift; sourceTree = ""; }; 3A8045D01F60C8E200AED866 /* MQTTServiceAuth.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MQTTServiceAuth.swift; sourceTree = ""; }; 3A8045D51F60C93D00AED866 /* MQTTServiceChat.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MQTTServiceChat.swift; sourceTree = ""; }; - 3A8045D71F60C98200AED866 /* MQTTServiceHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MQTTServiceHelper.swift; sourceTree = ""; }; + 3A8045D71F60C98200AED866 /* MQTTService+Helper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "MQTTService+Helper.swift"; sourceTree = ""; }; 3A8045D91F60E18E00AED866 /* Queue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Queue.swift; path = Library/Queue.swift; sourceTree = ""; }; 3A8C12DDFD0D831F21959665 /* Pods-Nynja-Share.devautotests.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Nynja-Share.devautotests.xcconfig"; path = "Pods/Target Support Files/Pods-Nynja-Share/Pods-Nynja-Share.devautotests.xcconfig"; sourceTree = ""; }; 3AA13C751F2252F900BE5D8F /* SearchModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SearchModel.swift; sourceTree = ""; }; @@ -2845,6 +2851,9 @@ 4B0213032037331100650298 /* ScheduleMessageViewControllerConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScheduleMessageViewControllerConstants.swift; sourceTree = ""; }; 4B030F2B2195BFF300F293B7 /* AuthHandlerDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthHandlerDelegate.swift; sourceTree = ""; }; 4B030F2C2195BFF300F293B7 /* AuthHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthHandler.swift; sourceTree = ""; }; + 4B030F312195CD4500F293B7 /* MQTTServiceDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MQTTServiceDelegate.swift; sourceTree = ""; }; + 4B030F352195CD9B00F293B7 /* MQTTService+QueuePool.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MQTTService+QueuePool.swift"; sourceTree = ""; }; + 4B030F3A2195CF8100F293B7 /* Host.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Host.swift; sourceTree = ""; }; 4B052CAF203614D400BC2A9B /* StringAtomExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringAtomExtension.swift; sourceTree = ""; }; 4B058EFD204EA751004C7D9F /* ProfileDAO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileDAO.swift; sourceTree = ""; }; 4B058F00204EA7F5004C7D9F /* DBManagerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DBManagerProtocol.swift; sourceTree = ""; }; @@ -6081,16 +6090,11 @@ 3A8045CC1F60C8E200AED866 /* MQTT */ = { isa = PBXGroup; children = ( - FBCE83D420E52396003B7558 /* MQTTServiceWallet.swift */, + 4B030F392195CF7600F293B7 /* Entities */, + 4B030F342195CD4A00F293B7 /* API */, + 4B030F382195CDDA00F293B7 /* Extensions */, 3A8045CD1F60C8E200AED866 /* MQTTService.swift */, - 3A8045D71F60C98200AED866 /* MQTTServiceHelper.swift */, - 3A8045D51F60C93D00AED866 /* MQTTServiceChat.swift */, - 3A8045CE1F60C8E200AED866 /* MQTTServiceFriend.swift */, - 3A8045CF1F60C8E200AED866 /* MQTTServiceProfile.swift */, - 3A8045D01F60C8E200AED866 /* MQTTServiceAuth.swift */, - 0008E9122032D5AC003E316E /* MQTTServiceSchedule.swift */, - 855EF422202CC85300541BE3 /* MQTTServiceStars.swift */, - A48C153E20EF765E002DA994 /* MQTTServiceLink.swift */, + 4B030F312195CD4500F293B7 /* MQTTServiceDelegate.swift */, ); name = MQTT; path = Services/MQTT; @@ -6485,6 +6489,38 @@ path = AuthHandler; sourceTree = ""; }; + 4B030F342195CD4A00F293B7 /* API */ = { + isa = PBXGroup; + children = ( + FBCE83D420E52396003B7558 /* MQTTServiceWallet.swift */, + 3A8045D51F60C93D00AED866 /* MQTTServiceChat.swift */, + 3A8045CE1F60C8E200AED866 /* MQTTServiceFriend.swift */, + 3A8045CF1F60C8E200AED866 /* MQTTServiceProfile.swift */, + 3A8045D01F60C8E200AED866 /* MQTTServiceAuth.swift */, + 0008E9122032D5AC003E316E /* MQTTServiceSchedule.swift */, + 855EF422202CC85300541BE3 /* MQTTServiceStars.swift */, + A48C153E20EF765E002DA994 /* MQTTServiceLink.swift */, + ); + path = API; + sourceTree = ""; + }; + 4B030F382195CDDA00F293B7 /* Extensions */ = { + isa = PBXGroup; + children = ( + 3A8045D71F60C98200AED866 /* MQTTService+Helper.swift */, + 4B030F352195CD9B00F293B7 /* MQTTService+QueuePool.swift */, + ); + path = Extensions; + sourceTree = ""; + }; + 4B030F392195CF7600F293B7 /* Entities */ = { + isa = PBXGroup; + children = ( + 4B030F3A2195CF8100F293B7 /* Host.swift */, + ); + path = Entities; + sourceTree = ""; + }; 4B058EF9204EA6EE004C7D9F /* DB */ = { isa = PBXGroup; children = ( @@ -14721,7 +14757,7 @@ 4BF090CD21635FED00DCCA5C /* Message+Type.swift in Sources */, 26A856222074C4F000C642EA /* ActionsView+Layout.swift in Sources */, 266AE8C42034971A0096A12C /* AsyncOperation.swift in Sources */, - 359EB27C1F9A2C8C00147437 /* MQTTServiceHelper.swift in Sources */, + 359EB27C1F9A2C8C00147437 /* MQTTService+Helper.swift in Sources */, 4B5A0B74216E3BDD002C4160 /* ForwardAvatarViewModel.swift in Sources */, A4B544F020EFB4EC00EB7B0F /* BertTupleExtension.swift in Sources */, 4BF090CF2163601400DCCA5C /* Message+LinkedId.swift in Sources */, @@ -14762,6 +14798,7 @@ A42CE5E820692EDB000889CC /* Auth_Spec.swift in Sources */, A415132320DBD5C100C2C01F /* Link_Spec.swift in Sources */, A42CE5C620692EDB000889CC /* reader_Spec.swift in Sources */, + 4B030F372195CD9B00F293B7 /* MQTTService+QueuePool.swift in Sources */, 359EB2831F9A2E6A00147437 /* ProfileHandler.swift in Sources */, A42CE5A820692EDB000889CC /* Star.swift in Sources */, A42CE5C020692EDB000889CC /* ok_Spec.swift in Sources */, @@ -14770,6 +14807,7 @@ A42CE59420692EDB000889CC /* Tag.swift in Sources */, A42CE5A020692EDB000889CC /* ok.swift in Sources */, 267D465720AB45E200D42242 /* Customizable.swift in Sources */, + 4B030F332195CD4500F293B7 /* MQTTServiceDelegate.swift in Sources */, A42CE60820692EDB000889CC /* task_Spec.swift in Sources */, A43B25B220AB1E2600FF8107 /* ImagePlaceholderField.swift in Sources */, A43B25B120AB1E1B00FF8107 /* NynjaSearchField.swift in Sources */, @@ -14785,6 +14823,7 @@ A42CE5B020692EDB000889CC /* TypeSpec.swift in Sources */, 26B32B971FE20BB500888A0A /* DescExtension+BERT.swift in Sources */, A42CE54220692EDA000889CC /* muc.swift in Sources */, + 4B030F3C2195CF8100F293B7 /* Host.swift in Sources */, 2611CEF82182090900FFD4DD /* LogWriterProtocol.swift in Sources */, A45F115620B4226600F45004 /* RawRepresentable+Localized.swift in Sources */, 4BD53BF4202C8BCA00569C1A /* AVURLAsset+Duration.swift in Sources */, @@ -15015,7 +15054,7 @@ 4B1D7E112029FF5000703228 /* Array+WheelItemModel.swift in Sources */, A46C36342121999100172773 /* DDMechanism.swift in Sources */, 854A4B302080D6C400759152 /* CellWithImageTableViewCell.swift in Sources */, - 3A8045D81F60C98200AED866 /* MQTTServiceHelper.swift in Sources */, + 3A8045D81F60C98200AED866 /* MQTTService+Helper.swift in Sources */, 8E9601971FF2EC8100E0C21D /* GroupFilesListVC.swift in Sources */, 4B06D3222028A9C6003B275B /* GroupChatItemsFactory.swift in Sources */, A4F3DABE2084990C00FF71C7 /* Roster+DB.swift in Sources */, @@ -15876,6 +15915,7 @@ A94B03A70E016BDA759B0703 /* EditProfileViewController.swift in Sources */, 8562853920D166E5000C9739 /* CollectionPreviewState.swift in Sources */, 4B7C73F2215A5509007924DB /* MotionManager.swift in Sources */, + 4B030F322195CD4500F293B7 /* MQTTServiceDelegate.swift in Sources */, F11786BB20A8A63F007A9A1B /* CoordinatorProtocol.swift in Sources */, F105C6BE20A1347E0091786A /* PhotoPreviewInteractor.swift in Sources */, F18AEAFD20C15792004FE01C /* SelectAvatarCoordinator.swift in Sources */, @@ -16431,6 +16471,7 @@ C99D6CD77325A09D045DB760 /* FavoritesViewController.swift in Sources */, 00E98256205C2740008BF03D /* SessionItemCell.swift in Sources */, 85D77807211D9B980044E72F /* ScrollPosition.swift in Sources */, + 4B030F362195CD9B00F293B7 /* MQTTService+QueuePool.swift in Sources */, FEA655F22167777E00B44029 /* PaymentWireFrame.swift in Sources */, DF55CCC682DAB5392F2A763D /* FavoritesPresenter.swift in Sources */, 4BDDAAEB218CA2C700F775A7 /* HomeDataProviderImpl.swift in Sources */, @@ -16699,6 +16740,7 @@ 260313AB20A0A4BA009AC66D /* ChatLanguageSettingsInteractor.swift in Sources */, 7C51CDC1260CE191C07EE46C /* SelectCountryViewController.swift in Sources */, 8596CEF22048A763006FC65D /* ThemeCellModel.swift in Sources */, + 4B030F3B2195CF8100F293B7 /* Host.swift in Sources */, 8566772020C1924500DD4204 /* MessageInteractor+MessageHandlerSubscriber.swift in Sources */, A1AD6864F4F49D9FC8997D59 /* SelectCountryPresenter.swift in Sources */, 32E5A25AD25BF752EB3864AB /* SelectCountryInteractor.swift in Sources */, diff --git a/Nynja/Modules/Main/Interactor/MainInteractor.swift b/Nynja/Modules/Main/Interactor/MainInteractor.swift index 6952408d4..b9eebeb06 100644 --- a/Nynja/Modules/Main/Interactor/MainInteractor.swift +++ b/Nynja/Modules/Main/Interactor/MainInteractor.swift @@ -146,10 +146,6 @@ class MainInteractor: MainInteractorInputProtocol, EditPhotoDelegate, MQTTServic self.presenter.changeScreenToAuth() } } - - func saveLogoutState() { - MQTTService.sharedInstance.state = .notAuthenticated(isLoggedOutFromServer: false) - } private func setupIntercom() { Intercom.logout() @@ -166,5 +162,4 @@ class MainInteractor: MainInteractorInputProtocol, EditPhotoDelegate, MQTTServic userAttributes.phone = contact.phoneNumber Intercom.updateUser(userAttributes) } - } diff --git a/Nynja/Modules/Main/MainProtocols.swift b/Nynja/Modules/Main/MainProtocols.swift index 98d9743d6..45812c17c 100644 --- a/Nynja/Modules/Main/MainProtocols.swift +++ b/Nynja/Modules/Main/MainProtocols.swift @@ -238,7 +238,6 @@ protocol MainInteractorInputProtocol: class { func logout() func deleteAccount() func updateAvatar(url: URL) - func saveLogoutState() func dialInGroup(name: String) func createGroupCall(callId: String?, contacts: [Contact], room: Room?) func createConferenceCall(callId: String?, contacts: [Contact], room: Room?) diff --git a/Nynja/Modules/Main/Presenter/MainPresenter.swift b/Nynja/Modules/Main/Presenter/MainPresenter.swift index 5292588ec..1cc2cee73 100644 --- a/Nynja/Modules/Main/Presenter/MainPresenter.swift +++ b/Nynja/Modules/Main/Presenter/MainPresenter.swift @@ -135,7 +135,6 @@ class MainPresenter: MainPresenterProtocol, MainInteractorOutputProtocol, Schedu } func logout() { - self.interactor.saveLogoutState() self.interactor.logout() self.changeScreenToAuth() } diff --git a/Nynja/Services/MQTT/MQTTServiceAuth.swift b/Nynja/Services/MQTT/API/MQTTServiceAuth.swift similarity index 95% rename from Nynja/Services/MQTT/MQTTServiceAuth.swift rename to Nynja/Services/MQTT/API/MQTTServiceAuth.swift index 30e1c9094..bdca505a5 100644 --- a/Nynja/Services/MQTT/MQTTServiceAuth.swift +++ b/Nynja/Services/MQTT/API/MQTTServiceAuth.swift @@ -49,11 +49,11 @@ extension MQTTService { publish(model: model) } - func updatePushToken(push_token: String) { - if - let phone = StorageService.sharedInstance.phone, + func update(pushToken: String) { + if let phone = StorageService.sharedInstance.phone, let token = StorageService.sharedInstance.tokenAsNSData, let clientID = StorageService.sharedInstance.clientId { + let model = AuthModel(type: .PUSH, phoneNumber: phone, userID: nil, @@ -63,7 +63,7 @@ extension MQTTService { token: token, smsCode: nil, languages: nil, - pushID: push_token) + pushID: pushToken) publish(model: model) } } diff --git a/Nynja/Services/MQTT/MQTTServiceChat.swift b/Nynja/Services/MQTT/API/MQTTServiceChat.swift similarity index 100% rename from Nynja/Services/MQTT/MQTTServiceChat.swift rename to Nynja/Services/MQTT/API/MQTTServiceChat.swift diff --git a/Nynja/Services/MQTT/MQTTServiceFriend.swift b/Nynja/Services/MQTT/API/MQTTServiceFriend.swift similarity index 100% rename from Nynja/Services/MQTT/MQTTServiceFriend.swift rename to Nynja/Services/MQTT/API/MQTTServiceFriend.swift diff --git a/Nynja/Services/MQTT/MQTTServiceLink.swift b/Nynja/Services/MQTT/API/MQTTServiceLink.swift similarity index 100% rename from Nynja/Services/MQTT/MQTTServiceLink.swift rename to Nynja/Services/MQTT/API/MQTTServiceLink.swift diff --git a/Nynja/Services/MQTT/MQTTServiceProfile.swift b/Nynja/Services/MQTT/API/MQTTServiceProfile.swift similarity index 100% rename from Nynja/Services/MQTT/MQTTServiceProfile.swift rename to Nynja/Services/MQTT/API/MQTTServiceProfile.swift diff --git a/Nynja/Services/MQTT/MQTTServiceSchedule.swift b/Nynja/Services/MQTT/API/MQTTServiceSchedule.swift similarity index 100% rename from Nynja/Services/MQTT/MQTTServiceSchedule.swift rename to Nynja/Services/MQTT/API/MQTTServiceSchedule.swift diff --git a/Nynja/Services/MQTT/MQTTServiceStars.swift b/Nynja/Services/MQTT/API/MQTTServiceStars.swift similarity index 100% rename from Nynja/Services/MQTT/MQTTServiceStars.swift rename to Nynja/Services/MQTT/API/MQTTServiceStars.swift diff --git a/Nynja/Services/MQTT/MQTTServiceWallet.swift b/Nynja/Services/MQTT/API/MQTTServiceWallet.swift similarity index 100% rename from Nynja/Services/MQTT/MQTTServiceWallet.swift rename to Nynja/Services/MQTT/API/MQTTServiceWallet.swift diff --git a/Nynja/Services/MQTT/Entities/Host.swift b/Nynja/Services/MQTT/Entities/Host.swift new file mode 100644 index 000000000..1ce1d6f21 --- /dev/null +++ b/Nynja/Services/MQTT/Entities/Host.swift @@ -0,0 +1,19 @@ +// +// Host.swift +// Nynja +// +// Created by Volodymyr Hryhoriev on 11/9/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + +struct Host { + let url: String + let port: UInt16 + + init() { + url = Bundle.main.serverUrl + port = Bundle.main.serverPort + } +} diff --git a/Nynja/Services/MQTT/MQTTServiceHelper.swift b/Nynja/Services/MQTT/Extensions/MQTTService+Helper.swift similarity index 80% rename from Nynja/Services/MQTT/MQTTServiceHelper.swift rename to Nynja/Services/MQTT/Extensions/MQTTService+Helper.swift index fd07ad5bd..052f20245 100644 --- a/Nynja/Services/MQTT/MQTTServiceHelper.swift +++ b/Nynja/Services/MQTT/Extensions/MQTTService+Helper.swift @@ -1,5 +1,5 @@ // -// MQTTServiceHelper.swift +// MQTTService+Helper.swift // Nynja // // Created by Anton Makarov on 07.09.2017. @@ -11,19 +11,8 @@ import CocoaMQTT extension MQTTService { - func publish(model: BaseMQTTModel) { - publishingQueue.async { [weak self] in - guard let mqtt = self?.mqtt, mqtt.connState == .connected else { - return - } - - let finalModel: CocoaMQTTMessage = model.getMessage() - mqtt.publish(finalModel) - } - } - func printMessage(msg: CocoaMQTTMessage, isSent: Bool) { - loggingQueue.async { [weak self] in + queuePool.loggingQueue.async { [weak self] in guard let `self` = self else { return } let desc = self.prepareOutputMessage(msg: msg, isSent: isSent) diff --git a/Nynja/Services/MQTT/Extensions/MQTTService+QueuePool.swift b/Nynja/Services/MQTT/Extensions/MQTTService+QueuePool.swift new file mode 100644 index 000000000..0f29b39d9 --- /dev/null +++ b/Nynja/Services/MQTT/Extensions/MQTTService+QueuePool.swift @@ -0,0 +1,28 @@ +// +// MQTTService+QueuePool.swift +// Nynja +// +// Created by Volodymyr Hryhoriev on 11/9/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + +extension MQTTService { + + struct QueuePool { + let subscribersQueue = DispatchQueue( + label: String.label(withSuffix: "mqtt-service.subscribers-queue"), + attributes: .concurrent) + + let loggingQueue = DispatchQueue(label: String.label(withSuffix: "mqtt-service.logging-queue")) + let processingQueue = DispatchQueue(label: String.label(withSuffix: "mqtt-service.processing-queue")) + let receivingQueue = DispatchQueue( + label: String.label(withSuffix: "mqtt-service.receiving-queue"), + qos: .utility) + + let publishingQueue = DispatchQueue( + label: String.label(withSuffix: "mqtt-service.publishing-queue"), + qos: .utility) + } +} diff --git a/Nynja/Services/MQTT/MQTTService.swift b/Nynja/Services/MQTT/MQTTService.swift index 5ffe2ffb2..3696cde3f 100644 --- a/Nynja/Services/MQTT/MQTTService.swift +++ b/Nynja/Services/MQTT/MQTTService.swift @@ -9,45 +9,21 @@ import Foundation import CocoaMQTT -struct Host { - let url: String - let port: UInt16 - - init() { - url = Bundle.main.serverUrl - port = Bundle.main.serverPort - } -} - final class MQTTService: NSObject, CocoaMQTTDelegate, ConnectionServiceDelegate { - enum ConnectionState { - case connected - case disconnected - } - - static let version = Bundle.main.modelsVersion - let host = Host() - - var mqtt: CocoaMQTT? - var push: String? - - let deviceId = UIDevice.current.persistentIdentifier - private let semaphore = DispatchSemaphore(value: 1) - + private var mqtt: CocoaMQTT? var wasRunApp: Bool = false - typealias MQTTServiceSubscribers = [WeakRef] - private var subscribers = MQTTServiceSubscribers() - private let subscribersQueue = DispatchQueue(label: String.label(withSuffix: "mqtt-service.subscribers-queue"), attributes: .concurrent) - - let loggingQueue = DispatchQueue(label: String.label(withSuffix: "mqtt-service.logging-queue")) - private let receivingQueue = DispatchQueue(label: String.label(withSuffix: "mqtt-service.receiving-queue"), qos: .utility) - let publishingQueue = DispatchQueue(label: String.label(withSuffix: "mqtt-service.publishing-queue"), qos: .utility) - - let showHandlers : Set = [] + let queuePool = QueuePool() + let showHandlers: Set = [] + + var deviceId: String { + return UIDevice.current.persistentIdentifier + } - static let sharedInstance = MQTTService() + var storageService: StorageService { + return .sharedInstance + } private var autoReconnectTimeInterval: UInt16? { willSet { @@ -81,65 +57,38 @@ final class MQTTService: NSObject, CocoaMQTTDelegate, ConnectionServiceDelegate } } } - - // MARK: - Session State - private let userDefaults: UserDefaults = .standard + // MARK: - Subscribers - enum SessionState { - case notDetermined - case notAuthenticated(isLoggedOutFromServer: Bool) - - enum Keys { - static let state = "SessionState.AuthenticationState" - static let isLoggedOutFromServer = "SessionState.isLoggedOutFromServer" - } - } + typealias MQTTServiceSubscribers = [WeakRef] + private var subscribers = MQTTServiceSubscribers() - var state: SessionState = .notDetermined { - didSet { - switch state { - case .notDetermined: - break - case let .notAuthenticated(isLoggedOutFromServer): - let value: [String: Bool] = [ - SessionState.Keys.isLoggedOutFromServer: isLoggedOutFromServer - ] - userDefaults.set(value, forKey: SessionState.Keys.state) - } - } - } - var isAuthenticated: Bool { - return StorageService.sharedInstance.hasToken - } + // MARK: - Singeton + static let sharedInstance = MQTTService() - // MARK: - Init + private override init() {} - override init () { - super.init() - state = fetchCurrentState() - } + + // MARK: - Setup func initialize() { - self.setup() - myConnect() - } - - func myConnect() { - semaphore.wait() - mqtt?.connect() - semaphore.signal() + queuePool.processingQueue.async { [unowned self] in + self.setup() + self.mqtt?.connect() + } } - func setup() { + private func setup() { // NOTE: Don't subscribe for ConnectionService because of the bug found there. None of the subscribers will be // invoked on conenction change, which might introduce regressions // ConnectionService.shared.addSubscriber(self) - if let token = StorageService.sharedInstance.token, let clientId = StorageService.sharedInstance.clientId, !token.isEmpty { + let host = Host() + + if let token = storageService.token, let clientId = storageService.clientId, !token.isEmpty { mqtt?.delegate = nil mqtt = CocoaMQTT(clientID: clientId, host: host.url, port: host.port) mqtt?.password = token @@ -152,8 +101,9 @@ final class MQTTService: NSObject, CocoaMQTTDelegate, ConnectionServiceDelegate mqtt?.username = "api" mqtt?.cleanSession = false } + mqtt?.enableSSL = Bundle.main.isServerConnectionSecure - mqtt?.willMessage = CocoaMQTTWill(topic: "version/\(MQTTService.version)", message: "") + mqtt?.willMessage = CocoaMQTTWill(topic: "version/\(Bundle.main.modelsVersion)", message: "") mqtt?.keepAlive = 0 mqtt?.delegate = self @@ -164,107 +114,124 @@ final class MQTTService: NSObject, CocoaMQTTDelegate, ConnectionServiceDelegate } } - func mqtt(_ mqtt: CocoaMQTT, didConnectAck ack: CocoaMQTTConnAck) { - if ack == .badUsernameOrPassword { - if let actualToken = StorageService.sharedInstance.token { - if actualToken == mqtt.password { - LogService.log(topic: .MQTT) { return "Bad Username or Password" } - #if !SHARE_EXTENSION - LogService.log(topic: .db) { return "Clear storage: bad username" } - StorageService.sharedInstance.clearStorage() - - self.state = .notAuthenticated(isLoggedOutFromServer: true) - - IoHandler.delegate { $0.sessionNotFound() } - notifySubscribers { (delegate) in - delegate.mqttServiceDidReceiveAuthenticationFailure(self) - } - self.reconnect() - #endif - } - } else { - LogService.log(topic: .MQTT) { return "Bad protocol version" } - isBadProtocolVersion = true - notifySubscribers { (delegate) in - delegate.mqttServiceDidReceiveWrongServerVersion() - } + func tryReconnect() { + queuePool.processingQueue.async { [unowned self] in + let states: [CocoaMQTTConnState] = [.initial, .disconnected] + if let connState = self.mqtt?.connState, states.contains(connState) { + self.reconnect() } } - if ack == .accept { - LogService.log(topic: .MQTT) { return "clientID: \(mqtt.clientID ) & password: \(mqtt.password ?? "") & clearSession: \(mqtt.cleanSession) state: \(mqtt.connState)" } - - isConnectedSuccess = true - - #if !SHARE_EXTENSION - if (mqtt.password ?? "") != "" && self.push != nil { - self.updatePushToken(push_token: self.push!) + } + + func reconnect() { + LogService.log(topic: .MQTT) { return "Reconnect" } + initialize() + } + + func disconnect(shouldRemoveConnectionSubscriber: Bool = true) { + queuePool.processingQueue.async { [unowned self] in + LogService.log(topic: .MQTT) { return "Disconnect" } + self.mqtt?.disconnect() + } + } + + + // MARK: - Publish + + func publish(model: BaseMQTTModel) { + queuePool.publishingQueue.async { [weak self] in + guard let mqtt = self?.mqtt, mqtt.connState == .connected else { + return } - #endif + + let finalModel: CocoaMQTTMessage = model.getMessage() + mqtt.publish(finalModel) } } - private func fetchCurrentState() -> SessionState { - guard let sessionState = userDefaults.dictionary(forKey: SessionState.Keys.state), - let isLoggedOutFromServer = sessionState[SessionState.Keys.isLoggedOutFromServer] as? Bool else { - return .notDetermined + + // MARK: - CocoaMQTTDelegate + + func mqtt(_ mqtt: CocoaMQTT, didConnectAck ack: CocoaMQTTConnAck) { + queuePool.processingQueue.async { [unowned self] in + if ack == .badUsernameOrPassword { + self.handleBadUsernameOrPassword(mqtt) + } + if ack == .accept { + self.handleAccept(mqtt) + } } - return self.isAuthenticated ? .notDetermined : .notAuthenticated(isLoggedOutFromServer: isLoggedOutFromServer) } - private var shouldResendLogout: Bool { - let storage = StorageService.sharedInstance - if !storage.hasToken, case let .notAuthenticated(isLoggedOutFromServer) = self.state, !isLoggedOutFromServer { - return true + private func handleBadUsernameOrPassword(_ mqtt: CocoaMQTT) { + if let actualToken = storageService.token, actualToken == mqtt.password { + handleInvalidToken(mqtt) + } else { + handleBadProtocolVersion(mqtt) } - return false } - func tryReconnect() { - let states: [CocoaMQTTConnState] = [.initial, .disconnected] - if let connState = mqtt?.connState, states.contains(connState) { - reconnect() + private func handleInvalidToken(_ mqtt: CocoaMQTT) { + LogService.log(topic: .MQTT) { return "Bad Username or Password" } + #if !SHARE_EXTENSION + LogService.log(topic: .db) { return "Clear storage: bad username" } + storageService.clearStorage() + + IoHandler.delegate { $0.sessionNotFound() } + notifySubscribers { (delegate) in + delegate.mqttServiceDidReceiveAuthenticationFailure(self) } + self.reconnect() + #endif } - func reconnect() { - LogService.log(topic: .MQTT) { return "Reconnect" } - self.setup() - myConnect() + private func handleBadProtocolVersion(_ mqtt: CocoaMQTT) { + LogService.log(topic: .MQTT) { return "Bad protocol version" } + isBadProtocolVersion = true + notifySubscribers { (delegate) in + delegate.mqttServiceDidReceiveWrongServerVersion() + } } - func disconnect(shouldRemoveConnectionSubscriber: Bool = true) { - LogService.log(topic: .MQTT) { return "Disconnect" } - // NOTE: Don't unsubscribe for ConnectionService because of the bug found there. None of the subscribers will be - // invoked on conenction change, which might introduce regressions -// if shouldRemoveConnectionSubscriber { -// ConnectionService.shared.removeSubscriber(self) -// } - mqtt?.disconnect() + private func handleAccept(_ mqtt: CocoaMQTT) { + LogService.log(topic: .MQTT) { return "clientID: \(mqtt.clientID ) & password: \(mqtt.password ?? "") & clearSession: \(mqtt.cleanSession) state: \(mqtt.connState)" } + + isConnectedSuccess = true + + #if !SHARE_EXTENSION + if let pushToken = storageService.pushToken, (mqtt.password ?? "") != "" { + self.update(pushToken: pushToken) + } + #endif } func mqtt(_ mqtt: CocoaMQTT, didPublishMessage message: CocoaMQTTMessage, id: UInt16) { printMessage(msg: message, isSent: true) } - func mqtt(_ mqtt: CocoaMQTT, didReceiveMessage message: CocoaMQTTMessage, id: UInt16 ) { + func mqtt(_ mqtt: CocoaMQTT, didReceiveMessage message: CocoaMQTTMessage, id: UInt16) { printMessage(msg: message, isSent: false) - receivingQueue.async { [weak self] in + queuePool.receivingQueue.async { [weak self] in HandlerService.handle(response: message) } } func mqttDidDisconnect(_ mqtt: CocoaMQTT, withError err: Error?) { - LogService.log(topic: .MQTT) { return "clientID: \(mqtt.clientID ) & password: \(mqtt.password ?? "") & clearSession: \(mqtt.cleanSession) state: \(mqtt.connState) & error: \(err?.localizedDescription)" } - - if let error = err as NSError?, error.code == 7 { - LogService.log(topic: .MQTT) { return "Something went wrong" } + queuePool.processingQueue.async { [unowned self] in + LogService.log(topic: .MQTT) { return "clientID: \(mqtt.clientID ) & password: \(mqtt.password ?? "") & clearSession: \(mqtt.cleanSession) state: \(mqtt.connState) & error: \(err?.localizedDescription)" } + + if let error = err as NSError?, error.code == 7 { + LogService.log(topic: .MQTT) { return "Something went wrong" } + } + + self.isConnectedSuccess = false } - - isConnectedSuccess = false } func mqtt(_ mqtt: CocoaMQTT, didConnect host: String, port: Int) { - isConnectedSuccess = true + queuePool.processingQueue.async { [unowned self] in + self.isConnectedSuccess = true + } } func mqtt(_ mqtt: CocoaMQTT, didSubscribeTopic topic: String) {} @@ -273,31 +240,29 @@ final class MQTTService: NSObject, CocoaMQTTDelegate, ConnectionServiceDelegate func mqttDidPing(_ mqtt: CocoaMQTT) {} func mqttDidReceivePong(_ mqtt: CocoaMQTT) {} + // MARK: Subscribers + func addSubscriber(_ subscriber: MQTTServiceDelegate) { - subscribersQueue.async(flags: .barrier) { [weak self] in - guard let `self` = self else { return } + queuePool.subscribersQueue.async(flags: .barrier) { [unowned self] in self.subscribers.append(WeakRef(value: subscriber)) } } func removeSubscriber(_ subscriber: MQTTServiceDelegate) { - subscribersQueue.async(flags: .barrier) { [weak self] in - guard let `self` = self else { return } + queuePool.subscribersQueue.async(flags: .barrier) { [unowned self] in self.subscribers = self.subscribers.filter { $0.value !== subscriber } } } func removeAllSubscribers() { - subscribersQueue.async(flags: .barrier) { [weak self] in - guard let `self` = self else { return } + queuePool.subscribersQueue.async(flags: .barrier) { [unowned self] in self.subscribers.removeAll() } } private func notifySubscribers(with eventClosure: (MQTTServiceDelegate) -> Void) { - subscribersQueue.sync { [weak self] in - guard let `self` = self else { return } + queuePool.subscribersQueue.sync { [unowned self] in self.subscribers.forEach { (weak) in if let delegate = weak.value as? MQTTServiceDelegate { eventClosure(delegate) @@ -309,30 +274,20 @@ final class MQTTService: NSObject, CocoaMQTTDelegate, ConnectionServiceDelegate // MARK: - ConnectionServiceDelegate - func connectionStatusChanged(_ sender: ConnectionService, service: ConnectionService.Service, oldValue: ConnectionService.ConnectionServiceState) { - if service == .networking { - guard let networkStatus = sender.state[.networking] else { return } - switch networkStatus { - case .connected: myConnect() - case .disconnected: disconnect(shouldRemoveConnectionSubscriber: false) - case .switched: reconnect() - default: break + func connectionStatusChanged( + _ sender: ConnectionService, + service: ConnectionService.Service, oldValue: ConnectionService.ConnectionServiceState) { + + queuePool.processingQueue.async { [unowned self] in + if service == .networking { + guard let networkStatus = sender.state[.networking] else { return } + switch networkStatus { + case .connected: self.mqtt?.connect() + case .disconnected: self.disconnect(shouldRemoveConnectionSubscriber: false) + case .switched: self.reconnect() + default: break + } } } } - -} - -protocol MQTTServiceDelegate: class { - func mqttServiceDidConnect(_ mqttService: MQTTService) - func mqttServiceDidDisconnect(_ mqttService: MQTTService) - func mqttServiceDidReceiveAuthenticationFailure(_ mqttService: MQTTService) - func mqttServiceDidReceiveWrongServerVersion() -} - -extension MQTTServiceDelegate { - func mqttServiceDidConnect(_ mqttService: MQTTService) {} - func mqttServiceDidDisconnect(_ mqttService: MQTTService) {} - func mqttServiceDidReceiveAuthenticationFailure(_ mqttService: MQTTService) {} - func mqttServiceDidReceiveWrongServerVersion() {} } diff --git a/Nynja/Services/MQTT/MQTTServiceDelegate.swift b/Nynja/Services/MQTT/MQTTServiceDelegate.swift new file mode 100644 index 000000000..9a2ab23ff --- /dev/null +++ b/Nynja/Services/MQTT/MQTTServiceDelegate.swift @@ -0,0 +1,23 @@ +// +// MQTTServiceDelegate.swift +// Nynja +// +// Created by Volodymyr Hryhoriev on 11/9/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + +protocol MQTTServiceDelegate: class { + func mqttServiceDidConnect(_ mqttService: MQTTService) + func mqttServiceDidDisconnect(_ mqttService: MQTTService) + func mqttServiceDidReceiveAuthenticationFailure(_ mqttService: MQTTService) + func mqttServiceDidReceiveWrongServerVersion() +} + +extension MQTTServiceDelegate { + func mqttServiceDidConnect(_ mqttService: MQTTService) {} + func mqttServiceDidDisconnect(_ mqttService: MQTTService) {} + func mqttServiceDidReceiveAuthenticationFailure(_ mqttService: MQTTService) {} + func mqttServiceDidReceiveWrongServerVersion() {} +} diff --git a/Nynja/Services/PushService.swift b/Nynja/Services/PushService.swift index 5ec5a47a0..0cc420b9b 100644 --- a/Nynja/Services/PushService.swift +++ b/Nynja/Services/PushService.swift @@ -49,8 +49,7 @@ final class PushService: NSObject, PKPushRegistryDelegate, UserSettingsRespondab func pushRegistry(_ registry: PKPushRegistry, didUpdate credentials: PKPushCredentials, for type: PKPushType) { let voipPushToken = credentials.token let token = voipPushToken.map { String(format: "%02.2hhx", $0) }.joined() - //MQTTService.sharedInstance.updatePushToken(push_token: token) - MQTTService.sharedInstance.push = token + storageService.pushToken = token LogService.log(topic: .system) { return "Register push token: \(token)" } } @@ -79,8 +78,8 @@ final class PushService: NSObject, PKPushRegistryDelegate, UserSettingsRespondab case historyDelete = "history_delete" case updateHistory = "history_update" case messageDelete = "message_delete" - case messageEdit = "message_edit" - case undefined = "" + case messageEdit = "message_edit" + case undefined = "" } // MARK: - Handle Notifications diff --git a/Nynja/StorageService+UserInfo.swift b/Nynja/StorageService+UserInfo.swift index e567eb45e..6571c3e06 100644 --- a/Nynja/StorageService+UserInfo.swift +++ b/Nynja/StorageService+UserInfo.swift @@ -45,6 +45,11 @@ extension StorageService: UserInfo { return tokenData.flatMap { NSData(data: $0) } } + var pushToken: String? { + get { return value(forId: .pushToken) } + set { set(newValue, forId: .pushToken) } + } + var phone: String? { get { return value(forId: .phone) } set { set(newValue, forId: .phone) } diff --git a/Nynja/UserInfo.swift b/Nynja/UserInfo.swift index 50d70c707..e42e56006 100644 --- a/Nynja/UserInfo.swift +++ b/Nynja/UserInfo.swift @@ -10,6 +10,8 @@ import Foundation enum UserIdentifiers: String { case token + case pushToken + case phone case rosterId case clientId = "clientID" @@ -22,6 +24,8 @@ protocol UserInfo: class { var token: String? { get set } var tokenAsNSData: NSData? { get } + var pushToken: String? { get set } + var phone: String? { get set } var rosterId: Int64? { get set } var clientId: String? { get set } diff --git a/Shared/Services/Handlers/IoHandler/IoHandler.swift b/Shared/Services/Handlers/IoHandler/IoHandler.swift index 61aa420c3..4ee13a7eb 100644 --- a/Shared/Services/Handlers/IoHandler/IoHandler.swift +++ b/Shared/Services/Handlers/IoHandler/IoHandler.swift @@ -73,8 +73,6 @@ final class IoHandler: BaseHandler, StaticDelegating { LogService.log(topic: .db) { return "Clear storage: IoHandler" } storageService.clearStorage() - mqttService.state = .notAuthenticated(isLoggedOutFromServer: true) - mqttService.disconnect() mqttService.reconnect() delegate { $0.logout() } -- GitLab From f8fb388f8c622aa42d032f8ba6a9559798ceb1c1 Mon Sep 17 00:00:00 2001 From: Volodymyr Hryhoriev Date: Fri, 9 Nov 2018 22:05:24 +0200 Subject: [PATCH 45/68] Update usage of `UserInfo` inside `StorageService` in order to remove `wasRunApp` from `MQTTService`. --- Nynja.xcodeproj/project.pbxproj | 12 +- Nynja/AppDelegate.swift | 2 - Nynja/Services/MQTT/API/MQTTServiceAuth.swift | 8 +- Nynja/Services/MQTT/MQTTService.swift | 1 - Nynja/Services/Models/AuthModel.swift | 6 +- Nynja/Services/StorageService.swift | 111 +++++++++++++++++- Nynja/UserInfo.swift | 2 +- ...vice+UserInfo.swift => UserInfoImpl.swift} | 84 ++++++------- .../Services/UserInfo/UserInfoTest.swift | 3 +- 9 files changed, 166 insertions(+), 63 deletions(-) rename Nynja/{StorageService+UserInfo.swift => UserInfoImpl.swift} (50%) diff --git a/Nynja.xcodeproj/project.pbxproj b/Nynja.xcodeproj/project.pbxproj index 91aef5bb2..aea6a9616 100644 --- a/Nynja.xcodeproj/project.pbxproj +++ b/Nynja.xcodeproj/project.pbxproj @@ -550,6 +550,8 @@ 4B030F372195CD9B00F293B7 /* MQTTService+QueuePool.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B030F352195CD9B00F293B7 /* MQTTService+QueuePool.swift */; }; 4B030F3B2195CF8100F293B7 /* Host.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B030F3A2195CF8100F293B7 /* Host.swift */; }; 4B030F3C2195CF8100F293B7 /* Host.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B030F3A2195CF8100F293B7 /* Host.swift */; }; + 4B030F3E2195D88100F293B7 /* UserInfoImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B030F3D2195D88100F293B7 /* UserInfoImpl.swift */; }; + 4B030F3F2195D88100F293B7 /* UserInfoImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B030F3D2195D88100F293B7 /* UserInfoImpl.swift */; }; 4B052CB0203614D400BC2A9B /* StringAtomExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B052CAF203614D400BC2A9B /* StringAtomExtension.swift */; }; 4B052CB12036193900BC2A9B /* StringAtomExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B052CAF203614D400BC2A9B /* StringAtomExtension.swift */; }; 4B058F03204EA928004C7D9F /* DAOProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B058F02204EA928004C7D9F /* DAOProtocol.swift */; }; @@ -1506,8 +1508,6 @@ A4330A6F2109EBA70060BD93 /* CountriesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4330A6D2109EBA70060BD93 /* CountriesProvider.swift */; }; A4330A712109EBB30060BD93 /* CountriesProviding.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4330A702109EBB30060BD93 /* CountriesProviding.swift */; }; A4330A722109EBB30060BD93 /* CountriesProviding.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4330A702109EBB30060BD93 /* CountriesProviding.swift */; }; - A4330A742109F0D40060BD93 /* StorageService+UserInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4330A732109F0D40060BD93 /* StorageService+UserInfo.swift */; }; - A4330A752109F0D40060BD93 /* StorageService+UserInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4330A732109F0D40060BD93 /* StorageService+UserInfo.swift */; }; A433D9A120A5C18C00C946F9 /* ContactsProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = A433D9A020A5C18C00C946F9 /* ContactsProvider.swift */; }; A433D9A320A5C19600C946F9 /* ContactsProviding.swift in Sources */ = {isa = PBXBuildFile; fileRef = A433D9A220A5C19600C946F9 /* ContactsProviding.swift */; }; A438DB9220763AFB00AA86A2 /* Contact+Desc.swift in Sources */ = {isa = PBXBuildFile; fileRef = A438DB9120763AFB00AA86A2 /* Contact+Desc.swift */; }; @@ -2854,6 +2854,7 @@ 4B030F312195CD4500F293B7 /* MQTTServiceDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MQTTServiceDelegate.swift; sourceTree = ""; }; 4B030F352195CD9B00F293B7 /* MQTTService+QueuePool.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MQTTService+QueuePool.swift"; sourceTree = ""; }; 4B030F3A2195CF8100F293B7 /* Host.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Host.swift; sourceTree = ""; }; + 4B030F3D2195D88100F293B7 /* UserInfoImpl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserInfoImpl.swift; sourceTree = ""; }; 4B052CAF203614D400BC2A9B /* StringAtomExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringAtomExtension.swift; sourceTree = ""; }; 4B058EFD204EA751004C7D9F /* ProfileDAO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileDAO.swift; sourceTree = ""; }; 4B058F00204EA7F5004C7D9F /* DBManagerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DBManagerProtocol.swift; sourceTree = ""; }; @@ -3624,7 +3625,6 @@ A4330A692109EA850060BD93 /* DatabaseManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatabaseManager.swift; sourceTree = ""; }; A4330A6D2109EBA70060BD93 /* CountriesProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CountriesProvider.swift; sourceTree = ""; }; A4330A702109EBB30060BD93 /* CountriesProviding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CountriesProviding.swift; sourceTree = ""; }; - A4330A732109F0D40060BD93 /* StorageService+UserInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StorageService+UserInfo.swift"; sourceTree = ""; }; A433D9A020A5C18C00C946F9 /* ContactsProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactsProvider.swift; sourceTree = ""; }; A433D9A220A5C19600C946F9 /* ContactsProviding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactsProviding.swift; sourceTree = ""; }; A438DB9120763AFB00AA86A2 /* Contact+Desc.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Contact+Desc.swift"; sourceTree = ""; }; @@ -10156,7 +10156,7 @@ isa = PBXGroup; children = ( A4330A642109DFA00060BD93 /* UserInfo.swift */, - A4330A732109F0D40060BD93 /* StorageService+UserInfo.swift */, + 4B030F3D2195D88100F293B7 /* UserInfoImpl.swift */, ); name = UserInfo; sourceTree = ""; @@ -14725,6 +14725,7 @@ A415132420DBD5C400C2C01F /* Link.swift in Sources */, 35B1AB8E1FA3470500E65233 /* NavigateProtocol.swift in Sources */, A42CE55C20692EDB000889CC /* error.swift in Sources */, + 4B030F3F2195D88100F293B7 /* UserInfoImpl.swift in Sources */, 267D465C20AB4CD500D42242 /* AuthExtension.swift in Sources */, A42CE5F420692EDB000889CC /* Loc_Spec.swift in Sources */, A42CE57A20692EDB000889CC /* serviceTask.swift in Sources */, @@ -14811,7 +14812,6 @@ A42CE60820692EDB000889CC /* task_Spec.swift in Sources */, A43B25B220AB1E2600FF8107 /* ImagePlaceholderField.swift in Sources */, A43B25B120AB1E1B00FF8107 /* NynjaSearchField.swift in Sources */, - A4330A752109F0D40060BD93 /* StorageService+UserInfo.swift in Sources */, 26C0C1CA2073C93400C530DA /* ForwardContent.swift in Sources */, 263529132075725200DC6FBD /* SendJob.swift in Sources */, 35B98F9B1F9B956F009B8DEC /* StringExtensions.swift in Sources */, @@ -15512,7 +15512,6 @@ 8502DB512061030100613C8C /* WheelPositionPickerProtocols.swift in Sources */, 00F7B3402029DD6200E443E1 /* TextItemView.swift in Sources */, E77764BE1FBDA9B60042541D /* ImageWheelItemView.swift in Sources */, - A4330A742109F0D40060BD93 /* StorageService+UserInfo.swift in Sources */, FBCE841520E525A6003B7558 /* URLRequestConvertible.swift in Sources */, 264638271FFFE7F9002590E6 /* RepliesPresenter.swift in Sources */, E7C36C391FC46A9E00740630 /* ServiceExtension.swift in Sources */, @@ -15921,6 +15920,7 @@ F18AEAFD20C15792004FE01C /* SelectAvatarCoordinator.swift in Sources */, 85D66A1220BD965300FBD803 /* UserMentionTableViewCell.swift in Sources */, 2657BE51201233E300F21935 /* ImageFilledItemModel.swift in Sources */, + 4B030F3E2195D88100F293B7 /* UserInfoImpl.swift in Sources */, 85BA176120BEA7BD001EF8AC /* StickerPreviewContainerView.swift in Sources */, 85CB25DF20D7325500D5E565 /* StickerPackExtension.swift in Sources */, 00E8513B2021E96E007DC792 /* GApiResponse.swift in Sources */, diff --git a/Nynja/AppDelegate.swift b/Nynja/AppDelegate.swift index 332ecf388..7ce25040d 100644 --- a/Nynja/AppDelegate.swift +++ b/Nynja/AppDelegate.swift @@ -125,8 +125,6 @@ private extension AppDelegate { } private func wipeStorage() { - MQTTService.sharedInstance.wasRunApp = storageService.wasRun - if !storageService.wasRun { LogService.log(topic: .db) { return "Clear storage: AppDelegate - if it is first runs" } storageService.clearStorage() diff --git a/Nynja/Services/MQTT/API/MQTTServiceAuth.swift b/Nynja/Services/MQTT/API/MQTTServiceAuth.swift index bdca505a5..6c67a190c 100644 --- a/Nynja/Services/MQTT/API/MQTTServiceAuth.swift +++ b/Nynja/Services/MQTT/API/MQTTServiceAuth.swift @@ -11,7 +11,7 @@ import Foundation extension MQTTService { func logout() { - let token = StorageService.sharedInstance.tokenAsNSData + let token = StorageService.sharedInstance.tokenData let userID = StorageService.sharedInstance.phone let model = AuthModel(type: .LOGOUT, phoneNumber: nil, @@ -24,7 +24,7 @@ extension MQTTService { } func login(number: String) { - if let token = StorageService.sharedInstance.tokenAsNSData { + if let token = StorageService.sharedInstance.tokenData { let model = AuthModel(type: .LOGIN, phoneNumber: number, userID: nil, @@ -51,7 +51,7 @@ extension MQTTService { func update(pushToken: String) { if let phone = StorageService.sharedInstance.phone, - let token = StorageService.sharedInstance.tokenAsNSData, + let token = StorageService.sharedInstance.tokenData, let clientID = StorageService.sharedInstance.clientId { let model = AuthModel(type: .PUSH, @@ -74,6 +74,8 @@ extension MQTTService { } func checkSMS(code: String, phone: String) { + let wasRunApp = storageService.wasRun + let model = AuthModel(type: .VERIFY, phoneNumber: phone, userID: nil, diff --git a/Nynja/Services/MQTT/MQTTService.swift b/Nynja/Services/MQTT/MQTTService.swift index 3696cde3f..0a0e3c3dd 100644 --- a/Nynja/Services/MQTT/MQTTService.swift +++ b/Nynja/Services/MQTT/MQTTService.swift @@ -12,7 +12,6 @@ import CocoaMQTT final class MQTTService: NSObject, CocoaMQTTDelegate, ConnectionServiceDelegate { private var mqtt: CocoaMQTT? - var wasRunApp: Bool = false let queuePool = QueuePool() let showHandlers: Set = [] diff --git a/Nynja/Services/Models/AuthModel.swift b/Nynja/Services/Models/AuthModel.swift index d66ec20bb..d4e04abcd 100644 --- a/Nynja/Services/Models/AuthModel.swift +++ b/Nynja/Services/Models/AuthModel.swift @@ -38,7 +38,7 @@ class AuthModel: BaseMQTTModel { var smsCode: String? var languages: String? var pushID: String? - var token: NSData? + var token: Data? var settings: [Feature]? init(type: AuthType, @@ -47,7 +47,7 @@ class AuthModel: BaseMQTTModel { clientID : String? = nil, deviceToken: String? = nil, settings: [Feature]? = nil, - token: NSData? = nil, + token: Data? = nil, smsCode: String? = nil, languages: String? = nil, pushID: String? = nil) { @@ -89,7 +89,7 @@ class AuthModel: BaseMQTTModel { let pID = Bert.getBin(pushID) var tok: BertObject = BertNil() - if let toke = self.token { + if let toke = self.token.flatMap({ NSData(data: $0) }) { tok = BertBinary(fromNSData: toke) } diff --git a/Nynja/Services/StorageService.swift b/Nynja/Services/StorageService.swift index 0d214a471..3eef65d49 100644 --- a/Nynja/Services/StorageService.swift +++ b/Nynja/Services/StorageService.swift @@ -17,6 +17,10 @@ class StorageService { let userDefaults = UserDefaults(suiteName: Bundle.main.appGroupName) let keychain = KeychainService.standard + private(set) lazy var userInfo: UserInfo = { + return UserInfoImpl( + dependencies: .init(userDefaults: userDefaults)) + }() private let countriesProvider = CountriesProvider() @@ -28,9 +32,7 @@ class StorageService { } #endif - let isolationQueue = DispatchQueue( - label: String.label(withSuffix: "storage-service.isolation-queue"), - attributes: .concurrent) + private let isolationQueue = DispatchQueue(label: String.label(withSuffix: "storage-service.isolation-queue")) // MARK: - Properties @@ -52,7 +54,7 @@ class StorageService { // MARK: - Setup func setupDatabase(with name: String, application: UIApplication) { - isolationQueue.sync(flags: .barrier) { [weak self] in + isolationQueue.sync { [weak self] in self?._setupDatabase(with: name, application: application) } } @@ -113,7 +115,7 @@ class StorageService { // MARK: - Clear func clearStorage() { - isolationQueue.sync(flags: .barrier) { [weak self] in + isolationQueue.sync { [weak self] in self?._clearStorage() } } @@ -163,3 +165,102 @@ extension StorageService: DBManagerProtocol { } } #endif + + +// MARK: - UserInfo + +extension StorageService: UserInfo { + + var token: String? { + get { + return value(forKeyPath: \UserInfo.token) } + set { + isolationQueue.async { [weak self] in + self?.userInfo.token = newValue + } + } + } + + var tokenData: Data? { + get { + var data: Data? + isolationQueue.sync { [weak self] in + data = self?.userInfo.tokenData + } + return data + } + } + + var pushToken: String? { + get { return value(forKeyPath: \UserInfo.pushToken) } + set { + isolationQueue.async { [weak self] in + self?.userInfo.pushToken = newValue + } + } + } + + var phone: String? { + get { return value(forKeyPath: \UserInfo.phone) } + set { + isolationQueue.async { [weak self] in + self?.userInfo.phone = newValue + } + } + } + + var rosterId: Int64? { + get { return value(forKeyPath: \UserInfo.rosterId) } + set { + isolationQueue.async { [weak self] in + self?.userInfo.rosterId = newValue + } + } + } + + var clientId: String? { + get { return value(forKeyPath: \UserInfo.clientId) } + set { + isolationQueue.async { [weak self] in + self?.userInfo.clientId = newValue + } + } + } + + var wasLogined: Bool { + get { return value(forKeyPath: \UserInfo.wasLogined) } + set { + isolationQueue.async { [weak self] in + self?.userInfo.wasLogined = newValue + } + } + } + + var wasRun: Bool { + get { return value(forKeyPath: \UserInfo.wasRun) } + set { + isolationQueue.async { [weak self] in + self?.userInfo.wasRun = newValue + } + } + } + + private func value(forKeyPath keyPath: WritableKeyPath) -> T { + var value: T? + isolationQueue.sync { [weak self] in + guard let `self` = self else { return } + value = self.userInfo[keyPath: keyPath] + } + return value! + } + + func setupAuth(clientId: String, token: String) { + self.clientId = clientId + self.token = token + } + + func setupUserInfo(from roster: Roster) { + phone = roster.phone + rosterId = roster.id + } +} diff --git a/Nynja/UserInfo.swift b/Nynja/UserInfo.swift index e42e56006..c04b6179c 100644 --- a/Nynja/UserInfo.swift +++ b/Nynja/UserInfo.swift @@ -22,7 +22,7 @@ enum UserIdentifiers: String { protocol UserInfo: class { var token: String? { get set } - var tokenAsNSData: NSData? { get } + var tokenData: Data? { get } var pushToken: String? { get set } diff --git a/Nynja/StorageService+UserInfo.swift b/Nynja/UserInfoImpl.swift similarity index 50% rename from Nynja/StorageService+UserInfo.swift rename to Nynja/UserInfoImpl.swift index 6571c3e06..87dd926df 100644 --- a/Nynja/StorageService+UserInfo.swift +++ b/Nynja/UserInfoImpl.swift @@ -1,20 +1,34 @@ // -// StorageService+UserInfo.swift +// UserInfoImpl.swift // Nynja // -// Created by Volodymyr Hryhoriev on 7/26/18. +// Created by Volodymyr Hryhoriev on 11/9/18. // Copyright © 2018 TecSynt Solutions. All rights reserved. // import Foundation -extension StorageService: UserInfo { +class UserInfoImpl: UserInfo, InitializeInjectable { + + let userDefaults: UserDefaults? + + private var mqttService: MQTTService { + return .sharedInstance + } + + required init(dependencies: Dependencies) { + userDefaults = dependencies.userDefaults + _wasRun = value(forId: .wasRun) ?? false + } + + + // MARK: - UserInfo private var encoding: String.Encoding { return .utf8 } - private var tokenData: Data? { + var tokenData: Data? { return value(forId: .token) } @@ -25,26 +39,18 @@ extension StorageService: UserInfo { return token } set { - isolationQueue.async(flags: .barrier) { [weak self] in - guard let `self` = self else { return } - - guard let token = newValue, let data = token.data(using: self.encoding) else { - self.userDefaults?.removeObject(forKey: UserIdentifiers.token.rawValue) - self.userDefaults?.synchronize() - return - } - - self.set(data as NSData, forId: .token, isAsync: false) - LogService.log(topic: .userDefaults) { return "Save token: \(token)" } - MQTTService.sharedInstance.reconnect() + guard let token = newValue, let data = token.data(using: self.encoding) else { + userDefaults?.removeObject(forKey: UserIdentifiers.token.rawValue) + userDefaults?.synchronize() + return } + + set(data as NSData, forId: .token) + LogService.log(topic: .userDefaults) { return "Save token: \(token)" } + MQTTService.sharedInstance.reconnect() // FIXME: I don't think it is proper place to do such thing } } - var tokenAsNSData: NSData? { - return tokenData.flatMap { NSData(data: $0) } - } - var pushToken: String? { get { return value(forId: .pushToken) } set { set(newValue, forId: .pushToken) } @@ -70,8 +76,10 @@ extension StorageService: UserInfo { set { set(newValue, forId: .wasLogined) } } + private var _wasRun: Bool = false + var wasRun: Bool { - get { return value(forId: .wasRun) ?? false } + get { return _wasRun } set { set(newValue, forId: .wasRun) } } @@ -86,27 +94,21 @@ extension StorageService: UserInfo { } private func value(forId id: UserIdentifiers) -> T? { - var value: T? - isolationQueue.sync { [weak self] in - value = self?.userDefaults?.value(forKey: id.rawValue) as? T - } - return value + return userDefaults?.value(forKey: id.rawValue) as? T } - private func set(_ value: Any?, forId id: UserIdentifiers, isAsync: Bool = true) { - let set = { [weak self] (value: Any?, id: UserIdentifiers) in - guard let `self` = self else { return } - - self.userDefaults?.set(value, forKey: id.rawValue) - self.userDefaults?.synchronize() - } - - if isAsync { - isolationQueue.async { - set(value, id) - } - } else { - set(value, id) - } + private func set(_ value: Any?, forId id: UserIdentifiers) { + userDefaults?.set(value, forKey: id.rawValue) + userDefaults?.synchronize() + } +} + + +// MARK: - InitializeInjectable + +extension UserInfoImpl { + + struct Dependencies { + let userDefaults: UserDefaults? } } diff --git a/NynjaUnitTests/Services/UserInfo/UserInfoTest.swift b/NynjaUnitTests/Services/UserInfo/UserInfoTest.swift index f2ba00aa2..33f8f8054 100644 --- a/NynjaUnitTests/Services/UserInfo/UserInfoTest.swift +++ b/NynjaUnitTests/Services/UserInfo/UserInfoTest.swift @@ -110,7 +110,8 @@ private extension UserInfoTest { class UserInfoMock: UserInfo { var token: String? - var tokenAsNSData: NSData? + var tokenData: Data? + var pushToken: String? var phone: String? var rosterId: Int64? var clientId: String? -- GitLab From 11c192d4117b9f61f2a5216401a234dab538410a Mon Sep 17 00:00:00 2001 From: Volodymyr Hryhoriev Date: Fri, 9 Nov 2018 22:08:19 +0200 Subject: [PATCH 46/68] Refactor `MQTTService` extensions in order to use `storageService` property instead of `StorageService.sharedInstance`. --- Nynja/Services/MQTT/API/MQTTServiceAuth.swift | 14 +++++++------- Nynja/Services/MQTT/API/MQTTServiceChat.swift | 6 +++--- Nynja/Services/MQTT/API/MQTTServiceFriend.swift | 10 +++++----- Nynja/Services/MQTT/API/MQTTServiceProfile.swift | 4 ++-- Nynja/Services/MQTT/API/MQTTServiceStars.swift | 2 +- Nynja/Services/MQTT/API/MQTTServiceWallet.swift | 4 ++-- 6 files changed, 20 insertions(+), 20 deletions(-) diff --git a/Nynja/Services/MQTT/API/MQTTServiceAuth.swift b/Nynja/Services/MQTT/API/MQTTServiceAuth.swift index 6c67a190c..a495ab33a 100644 --- a/Nynja/Services/MQTT/API/MQTTServiceAuth.swift +++ b/Nynja/Services/MQTT/API/MQTTServiceAuth.swift @@ -11,8 +11,8 @@ import Foundation extension MQTTService { func logout() { - let token = StorageService.sharedInstance.tokenData - let userID = StorageService.sharedInstance.phone + let token = storageService.tokenData + let userID = storageService.phone let model = AuthModel(type: .LOGOUT, phoneNumber: nil, userID: userID, @@ -24,7 +24,7 @@ extension MQTTService { } func login(number: String) { - if let token = StorageService.sharedInstance.tokenData { + if let token = storageService.tokenData { let model = AuthModel(type: .LOGIN, phoneNumber: number, userID: nil, @@ -50,9 +50,9 @@ extension MQTTService { } func update(pushToken: String) { - if let phone = StorageService.sharedInstance.phone, - let token = StorageService.sharedInstance.tokenData, - let clientID = StorageService.sharedInstance.clientId { + if let phone = storageService.phone, + let token = storageService.tokenData, + let clientID = storageService.clientId { let model = AuthModel(type: .PUSH, phoneNumber: phone, @@ -69,7 +69,7 @@ extension MQTTService { } func removeTokenAndLogin(number: String) { - StorageService.sharedInstance.dropUserInfo() + storageService.dropUserInfo() login(number: number) } diff --git a/Nynja/Services/MQTT/API/MQTTServiceChat.swift b/Nynja/Services/MQTT/API/MQTTServiceChat.swift index f1129894f..5fa321e5f 100644 --- a/Nynja/Services/MQTT/API/MQTTServiceChat.swift +++ b/Nynja/Services/MQTT/API/MQTTServiceChat.swift @@ -168,7 +168,7 @@ extension MQTTService { func block(_ isBlock: Bool, to: String) { let friend = Friend() - friend.phone_id = StorageService.sharedInstance.phoneId + friend.phone_id = storageService.phoneId friend.friend_id = to friend.settings = [] friend.status = isBlock ? ("ban" as AnyObject) : ("unban" as AnyObject) @@ -187,7 +187,7 @@ extension MQTTService { func updateSettings(_ features: [Feature], to: String) { let friend = Friend() - friend.phone_id = StorageService.sharedInstance.phoneId + friend.phone_id = storageService.phoneId friend.friend_id = to friend.settings = features friend.status = "update" as AnyObject @@ -210,7 +210,7 @@ extension MQTTService { func ignore(_ contact: Contact) { let friend = Friend() - friend.phone_id = StorageService.sharedInstance.phoneId + friend.phone_id = storageService.phoneId friend.friend_id = contact.phoneId friend.settings = [] friend.status = ("ignore" as AnyObject) diff --git a/Nynja/Services/MQTT/API/MQTTServiceFriend.swift b/Nynja/Services/MQTT/API/MQTTServiceFriend.swift index 7d67421c6..29f24c2ab 100644 --- a/Nynja/Services/MQTT/API/MQTTServiceFriend.swift +++ b/Nynja/Services/MQTT/API/MQTTServiceFriend.swift @@ -11,7 +11,7 @@ import Foundation extension MQTTService { func searchContactByUsername(nick : String) { - guard let rosterId = StorageService.sharedInstance.rosterId else { + guard let rosterId = storageService.rosterId else { return } let model = SearchModel(rosterID: rosterId, numbers: [nick], reference: .USERNAME, type: .EQUALS, status: .CONTACT, field: "nick") @@ -19,7 +19,7 @@ extension MQTTService { } func tryFindContact(number: String, modelReference: SearchModelReference = .PHONE) { - guard let rosterId = StorageService.sharedInstance.rosterId else { + guard let rosterId = storageService.rosterId else { return } let model = SearchModel(rosterID: rosterId, numbers: [number], reference: modelReference, type: .EQUALS, status: .CONTACT) @@ -27,7 +27,7 @@ extension MQTTService { } func searchContacts(numbers: [String]) { - guard let rosterId = StorageService.sharedInstance.rosterId else { + guard let rosterId = storageService.rosterId else { return } let model = SearchModel(rosterID: rosterId, numbers: numbers, reference: .PHONEBOOK, type: .EQUALS, status: .CONTACT) @@ -35,7 +35,7 @@ extension MQTTService { } func friendRequest(friendPhoneId : String) { - guard let phoneId = StorageService.sharedInstance.phoneId else { + guard let phoneId = storageService.phoneId else { return } let model = FriendRequstModel(phoneId: phoneId, friendPhoneId: friendPhoneId, status: "request") @@ -43,7 +43,7 @@ extension MQTTService { } func confirmFriend(friendPhoneId : String) { - guard let phoneId = StorageService.sharedInstance.phoneId else { + guard let phoneId = storageService.phoneId else { return } let model = FriendRequstModel(phoneId: phoneId, friendPhoneId: friendPhoneId, status: "confirm") diff --git a/Nynja/Services/MQTT/API/MQTTServiceProfile.swift b/Nynja/Services/MQTT/API/MQTTServiceProfile.swift index 48f53411f..79296cb83 100644 --- a/Nynja/Services/MQTT/API/MQTTServiceProfile.swift +++ b/Nynja/Services/MQTT/API/MQTTServiceProfile.swift @@ -11,7 +11,7 @@ import Foundation extension MQTTService { func updateAccount(id: Int64, name: String, surname: String, phone: String) { - if let _ = StorageService.sharedInstance.token { + if let _ = storageService.token { var model:UpdateRosterModel! if surname != "" { model = UpdateRosterModel(id: id, name: name, surname: surname, avatar: nil, email: nil,nick: nil) @@ -23,7 +23,7 @@ extension MQTTService { } func updateAvatar(id: Int64, link: String) { - if let _ = StorageService.sharedInstance.token { + if let _ = storageService.token { let model = UpdateRosterModel(id: id, name: nil, surname: nil, avatar: link, email: nil,nick: nil) publish(model: model) } diff --git a/Nynja/Services/MQTT/API/MQTTServiceStars.swift b/Nynja/Services/MQTT/API/MQTTServiceStars.swift index 4c3bafde5..c5cb5d2e5 100644 --- a/Nynja/Services/MQTT/API/MQTTServiceStars.swift +++ b/Nynja/Services/MQTT/API/MQTTServiceStars.swift @@ -10,7 +10,7 @@ import Foundation extension MQTTService { func getFavorites() { - guard StorageService.sharedInstance.token != nil else { + guard storageService.token != nil else { return } let model = GetExtendedStarsModel() diff --git a/Nynja/Services/MQTT/API/MQTTServiceWallet.swift b/Nynja/Services/MQTT/API/MQTTServiceWallet.swift index fefee8208..600599ade 100644 --- a/Nynja/Services/MQTT/API/MQTTServiceWallet.swift +++ b/Nynja/Services/MQTT/API/MQTTServiceWallet.swift @@ -9,7 +9,7 @@ extension MQTTService { func addWalletToProfile(walletAddress: String, balance: NYNMoney, password: String) { - guard let phoneId = StorageService.sharedInstance.phone else { + guard let phoneId = storageService.phone else { return } @@ -23,7 +23,7 @@ extension MQTTService { } func changeProfileBalance(walletAddress: String, balance: NYNMoney, password: String) { - guard let phoneId = StorageService.sharedInstance.phone else { + guard let phoneId = storageService.phone else { return } -- GitLab From c1478ff1aeacbd34d7d3bc9fc5119107125a78a8 Mon Sep 17 00:00:00 2001 From: Volodymyr Hryhoriev Date: Mon, 12 Nov 2018 11:10:21 +0200 Subject: [PATCH 47/68] Make requested changes. --- Nynja.xcodeproj/project.pbxproj | 72 ++-- Nynja.xcworkspace/contents.xcworkspacedata | 3 + Nynja/BadgeNumberService.swift | 16 +- Nynja/BadgeNumberServiceProtocol.swift | 3 - Nynja/ContactDAO.swift | 26 +- Nynja/ContactDAOFetchingArgs.swift | 27 -- Nynja/ContactDAOProtocol.swift | 4 +- Nynja/ContactsProvider.swift | 13 +- Nynja/ContactsProviding.swift | 1 - Nynja/ConversationsProvider.swift | 41 +-- Nynja/ConversationsProviding.swift | 14 +- .../DB/Extensions/TypedRequestExtension.swift | 20 +- ...elProtocol.swift => DBExtendedModel.swift} | 4 +- .../{DBModelProtocol.swift => DBModel.swift} | 10 +- Nynja/DB/Models/Base/FeedProtocol.swift | 2 +- Nynja/DB/Models/DBChatCheckpoint.swift | 4 +- Nynja/DB/Models/DBContact.swift | 23 +- Nynja/DB/Models/DBConvertMessage.swift | 4 +- Nynja/DB/Models/DBDesc.swift | 2 +- Nynja/DB/Models/DBJob.swift | 25 +- Nynja/DB/Models/DBJobMessage.swift | 2 +- Nynja/DB/Models/DBLink.swift | 2 +- Nynja/DB/Models/DBMember.swift | 33 +- Nynja/DB/Models/DBMessage.swift | 2 +- Nynja/DB/Models/DBMessageAction.swift | 4 +- Nynja/DB/Models/DBMessageEditAction.swift | 4 +- Nynja/DB/Models/DBMessageLink.swift | 4 +- Nynja/DB/Models/DBMuc.swift | 4 +- Nynja/DB/Models/DBP2p.swift | 4 +- Nynja/DB/Models/DBProfile.swift | 2 +- Nynja/DB/Models/DBRecentSticker.swift | 2 +- Nynja/DB/Models/DBRoom.swift | 208 +++++++---- Nynja/DB/Models/DBRoomMember.swift | 2 +- Nynja/DB/Models/DBRoster.swift | 7 +- Nynja/DB/Models/DBService.swift | 2 +- Nynja/DB/Models/DBStar.swift | 14 +- Nynja/DB/Models/DBStarAction.swift | 2 +- Nynja/DB/Models/DBStarMessage.swift | 5 +- Nynja/DB/Models/DBStickerPack.swift | 4 +- Nynja/DB/Models/DBSyncFile.swift | 2 +- Nynja/DB/Protocols/DBModelConvertible.swift | 2 +- Nynja/DB/QueryArgs.swift | 26 +- Nynja/DB/RosterRelatedQueryArgs.swift | 27 ++ Nynja/DB/Tables/DAO.swift | 18 + Nynja/DBJobQueryArgs.swift | 20 - Nynja/DBManagerProtocol.swift | 4 +- Nynja/DBObserver.swift | 341 +++++++++++------- Nynja/DatabaseManager.swift | 4 +- .../Models/Contact/Contact+DB.swift | 2 +- Nynja/Extensions/Models/Job+DB.swift | 2 +- .../Models/Member/MemberExtension.swift | 2 +- .../Models/Message/Message+DB.swift | 2 +- .../Extensions/Models/ProfileExtension.swift | 2 +- Nynja/Extensions/Models/Room/Room+DB.swift | 2 +- Nynja/Extensions/Models/Roster+DB.swift | 2 +- Nynja/Extensions/Models/RosterExtension.swift | 2 +- Nynja/Extensions/Models/StarExtension.swift | 2 +- .../Models/StickerPack/StickerPack+DB.swift | 2 +- ...able.swift => FullNameRepresentable.swift} | 10 +- Nynja/JobDAO.swift | 2 +- Nynja/Modules/Call/CallCreatorMediator.swift | 1 - .../Interactor/ChannelsListInteractor.swift | 36 +- .../WireFrame/ChannelsListWireFrame.swift | 2 + .../Interactor/ChatsListInteractor.swift | 4 +- .../Presenter/FavoritesPresenter.swift | 2 +- .../ForwardSelectorInteractor.swift | 22 +- .../WireFrame/ForwardSelectorWireFrame.swift | 8 +- .../Interactor/GroupsListInteractor.swift | 4 +- .../View/MainViewController+Recents.swift | 15 +- .../HomeDataProviderImpl.swift | 24 +- Nynja/RoomDAO.swift | 22 +- Nynja/RoomDAOProtocol.swift | 3 - .../LogService/LogService/LogService.swift | 2 +- .../Services/Debug/LogService/LogWriter.swift | 2 +- .../HandleServices/ProfileHandler.swift | 2 +- .../Services/HandleServices/RoomHandler.swift | 2 +- Nynja/Services/StorageService.swift | 4 +- Nynja/StarDAO.swift | 30 +- Nynja/StarDAOFetchingArgs.swift | 28 -- .../Models/Contact/ContactExtension.swift | 6 +- 80 files changed, 695 insertions(+), 621 deletions(-) delete mode 100644 Nynja/ContactDAOFetchingArgs.swift rename Nynja/DB/Models/Base/{DBExtendedModelProtocol.swift => DBExtendedModel.swift} (55%) rename Nynja/DB/Models/Base/{DBModelProtocol.swift => DBModel.swift} (75%) create mode 100644 Nynja/DB/RosterRelatedQueryArgs.swift create mode 100644 Nynja/DB/Tables/DAO.swift delete mode 100644 Nynja/DBJobQueryArgs.swift rename Nynja/{FullNameable.swift => FullNameRepresentable.swift} (79%) delete mode 100644 Nynja/StarDAOFetchingArgs.swift diff --git a/Nynja.xcodeproj/project.pbxproj b/Nynja.xcodeproj/project.pbxproj index 614866c94..5d6a13bb8 100644 --- a/Nynja.xcodeproj/project.pbxproj +++ b/Nynja.xcodeproj/project.pbxproj @@ -647,15 +647,13 @@ 4B7E93382170D1BC001558CF /* RootNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B7E93372170D1BC001558CF /* RootNavigationController.swift */; }; 4B7E933B2170D410001558CF /* ForwardSelectorWireFrame.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B7E933A2170D410001558CF /* ForwardSelectorWireFrame.swift */; }; 4B7E933E2170D4FF001558CF /* ServiceFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B7E933D2170D4FF001558CF /* ServiceFactory.swift */; }; - 4B87712D2192F2920014AD09 /* StarDAOFetchingArgs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B87712C2192F2920014AD09 /* StarDAOFetchingArgs.swift */; }; 4B87712F2192F6940014AD09 /* ColumnDefinitionExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B87712E2192F6940014AD09 /* ColumnDefinitionExtension.swift */; }; 4B877131219312570014AD09 /* ColumnExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B877130219312570014AD09 /* ColumnExtension.swift */; }; 4B877133219314D50014AD09 /* TypedRequestExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B877132219314D50014AD09 /* TypedRequestExtension.swift */; }; - 4B8771352193154E0014AD09 /* DBExtendedModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8771342193154E0014AD09 /* DBExtendedModelProtocol.swift */; }; + 4B8771352193154E0014AD09 /* DBExtendedModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8771342193154E0014AD09 /* DBExtendedModel.swift */; }; 4B877137219315770014AD09 /* QueryInterfaceRequestExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B877136219315770014AD09 /* QueryInterfaceRequestExtension.swift */; }; 4B877139219315AA0014AD09 /* SercerModelConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B877138219315AA0014AD09 /* SercerModelConvertible.swift */; }; 4B87713B219328780014AD09 /* QueryArgs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B87713A219328780014AD09 /* QueryArgs.swift */; }; - 4B87713E219350940014AD09 /* DBJobQueryArgs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B87713D219350940014AD09 /* DBJobQueryArgs.swift */; }; 4B8996C8204ECE9B00DCB183 /* ContactDAO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8996C7204ECE9B00DCB183 /* ContactDAO.swift */; }; 4B8996CA204ECEA700DCB183 /* ContactDAOProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8996C9204ECEA700DCB183 /* ContactDAOProtocol.swift */; }; 4B8996CD204ED33400DCB183 /* StarDAOProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8996CC204ED33400DCB183 /* StarDAOProtocol.swift */; }; @@ -691,7 +689,8 @@ 4BB0EFBB2151347900704136 /* AlertImageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB0EFB82151347900704136 /* AlertImageViewController.swift */; }; 4BB0EFBC2151347900704136 /* AlertImageViewControllerConstraints.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB0EFB92151347900704136 /* AlertImageViewControllerConstraints.swift */; }; 4BB0EFBD2151347900704136 /* AlertManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB0EFBA2151347900704136 /* AlertManager.swift */; }; - 4BC8B38A2191A1120086DC6C /* ContactDAOFetchingArgs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BC8B3892191A1120086DC6C /* ContactDAOFetchingArgs.swift */; }; + 4BB35E21219AF42B0007C18E /* DAO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB35E20219AF42A0007C18E /* DAO.swift */; }; + 4BB35E23219AF46E0007C18E /* RosterRelatedQueryArgs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB35E22219AF46E0007C18E /* RosterRelatedQueryArgs.swift */; }; 4BC8B38D2191AC360086DC6C /* ContactsProvidingFetchingArgs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BC8B38C2191AC360086DC6C /* ContactsProvidingFetchingArgs.swift */; }; 4BD53BF4202C8BCA00569C1A /* AVURLAsset+Duration.swift in Sources */ = {isa = PBXBuildFile; fileRef = E77FBDDC1FFE828400BDB255 /* AVURLAsset+Duration.swift */; }; 4BDC7E61203492CA00BCD381 /* TopSwipable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BDC7E60203492CA00BCD381 /* TopSwipable.swift */; }; @@ -731,8 +730,8 @@ 4BF2C3E42188BABC00E59F6C /* UIDeviceExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = E7A77FD71FACC360004AE609 /* UIDeviceExtension.swift */; }; 4BF2C3E92189B49500E59F6C /* Localizable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D485DE41F0AD96D00E12FB1 /* Localizable.swift */; }; 4BF2C3F02189F58F00E59F6C /* ServerSignal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BF2C3EF2189F58F00E59F6C /* ServerSignal.swift */; }; - 4BF2C3FC218AFE9D00E59F6C /* FullNameable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BF2C3FB218AFE9D00E59F6C /* FullNameable.swift */; }; - 4BF2C3FD218AFF6300E59F6C /* FullNameable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BF2C3FB218AFE9D00E59F6C /* FullNameable.swift */; }; + 4BF2C3FC218AFE9D00E59F6C /* FullNameRepresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BF2C3FB218AFE9D00E59F6C /* FullNameRepresentable.swift */; }; + 4BF2C3FD218AFF6300E59F6C /* FullNameRepresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BF2C3FB218AFE9D00E59F6C /* FullNameRepresentable.swift */; }; 4C5EEA13EBC6A8398F08DCD1 /* MainWireframe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65AAB8F770774CE3AE3FD6E1 /* MainWireframe.swift */; }; 4D53FE7454959323B1CCFD96 /* ProfileViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D270F638DBB2D8FC1BDEB633 /* ProfileViewController.swift */; }; 4DAEBCF361B86B0AD3C98749 /* EditUsernameInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBE3BAC9B7EA418FB463EF04 /* EditUsernameInteractor.swift */; }; @@ -1963,7 +1962,7 @@ E7598F6C1FA1D8B90082FBE7 /* ProfileContactCellLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = E7598F651FA1D8B80082FBE7 /* ProfileContactCellLayout.swift */; }; E761A0D91F8B8CF000C088E0 /* EditProfileViewControllerLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = E761A0D81F8B8CF000C088E0 /* EditProfileViewControllerLayout.swift */; }; E761A0DC1F8B8F3900C088E0 /* NynjaButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = E761A0DB1F8B8F3900C088E0 /* NynjaButton.swift */; }; - E76462891FCD64790091FC2E /* DBModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = E76462881FCD64790091FC2E /* DBModelProtocol.swift */; }; + E76462891FCD64790091FC2E /* DBModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E76462881FCD64790091FC2E /* DBModel.swift */; }; E764628C1FCD67AC0091FC2E /* DBModelConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = E764628B1FCD67AC0091FC2E /* DBModelConvertible.swift */; }; E76491961F7A529D001E741C /* WheelContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E76491951F7A529D001E741C /* WheelContainer.swift */; }; E764919B1F7A5485001E741C /* MainWheelContainerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E76491991F7A5485001E741C /* MainWheelContainerDelegate.swift */; }; @@ -2929,15 +2928,13 @@ 4B7E93372170D1BC001558CF /* RootNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootNavigationController.swift; sourceTree = ""; }; 4B7E933A2170D410001558CF /* ForwardSelectorWireFrame.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ForwardSelectorWireFrame.swift; sourceTree = ""; }; 4B7E933D2170D4FF001558CF /* ServiceFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServiceFactory.swift; sourceTree = ""; }; - 4B87712C2192F2920014AD09 /* StarDAOFetchingArgs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StarDAOFetchingArgs.swift; sourceTree = ""; }; 4B87712E2192F6940014AD09 /* ColumnDefinitionExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColumnDefinitionExtension.swift; sourceTree = ""; }; 4B877130219312570014AD09 /* ColumnExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColumnExtension.swift; sourceTree = ""; }; 4B877132219314D50014AD09 /* TypedRequestExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TypedRequestExtension.swift; sourceTree = ""; }; - 4B8771342193154E0014AD09 /* DBExtendedModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DBExtendedModelProtocol.swift; sourceTree = ""; }; + 4B8771342193154E0014AD09 /* DBExtendedModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DBExtendedModel.swift; sourceTree = ""; }; 4B877136219315770014AD09 /* QueryInterfaceRequestExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueryInterfaceRequestExtension.swift; sourceTree = ""; }; 4B877138219315AA0014AD09 /* SercerModelConvertible.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SercerModelConvertible.swift; sourceTree = ""; }; 4B87713A219328780014AD09 /* QueryArgs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueryArgs.swift; sourceTree = ""; }; - 4B87713D219350940014AD09 /* DBJobQueryArgs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DBJobQueryArgs.swift; sourceTree = ""; }; 4B8996C7204ECE9B00DCB183 /* ContactDAO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactDAO.swift; sourceTree = ""; }; 4B8996C9204ECEA700DCB183 /* ContactDAOProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactDAOProtocol.swift; sourceTree = ""; }; 4B8996CC204ED33400DCB183 /* StarDAOProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StarDAOProtocol.swift; sourceTree = ""; }; @@ -2967,7 +2964,8 @@ 4BB0EFB82151347900704136 /* AlertImageViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlertImageViewController.swift; sourceTree = ""; }; 4BB0EFB92151347900704136 /* AlertImageViewControllerConstraints.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlertImageViewControllerConstraints.swift; sourceTree = ""; }; 4BB0EFBA2151347900704136 /* AlertManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlertManager.swift; sourceTree = ""; }; - 4BC8B3892191A1120086DC6C /* ContactDAOFetchingArgs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactDAOFetchingArgs.swift; sourceTree = ""; }; + 4BB35E20219AF42A0007C18E /* DAO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = DAO.swift; path = /Users/volodymyrhryhoriev/Desktop/Work/Nynja/Nynja/DB/Tables/DAO.swift; sourceTree = ""; }; + 4BB35E22219AF46E0007C18E /* RosterRelatedQueryArgs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RosterRelatedQueryArgs.swift; sourceTree = ""; }; 4BC8B38C2191AC360086DC6C /* ContactsProvidingFetchingArgs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactsProvidingFetchingArgs.swift; sourceTree = ""; }; 4BDC7E60203492CA00BCD381 /* TopSwipable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TopSwipable.swift; sourceTree = ""; }; 4BDC7E62203494C000BCD381 /* ScheduleButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScheduleButton.swift; sourceTree = ""; }; @@ -2990,7 +2988,7 @@ 4BF090CB21635FDC00DCCA5C /* Message+Type.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Message+Type.swift"; sourceTree = ""; }; 4BF2C3DF2188B27500E59F6C /* Injectable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Injectable.swift; sourceTree = ""; }; 4BF2C3EF2189F58F00E59F6C /* ServerSignal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerSignal.swift; sourceTree = ""; }; - 4BF2C3FB218AFE9D00E59F6C /* FullNameable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FullNameable.swift; sourceTree = ""; }; + 4BF2C3FB218AFE9D00E59F6C /* FullNameRepresentable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FullNameRepresentable.swift; sourceTree = ""; }; 4CDA2BE900351F21464CE687 /* DateTimePickerInteractor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DateTimePickerInteractor.swift; sourceTree = ""; }; 4D247CBC45C1C1267BBBB289 /* QRCodeReaderInteractor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = QRCodeReaderInteractor.swift; sourceTree = ""; }; 4F7C039B61A0663D43BE5AE5 /* SelectCountryProtocols.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = SelectCountryProtocols.swift; sourceTree = ""; }; @@ -4034,7 +4032,7 @@ E7598F651FA1D8B80082FBE7 /* ProfileContactCellLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProfileContactCellLayout.swift; sourceTree = ""; }; E761A0D81F8B8CF000C088E0 /* EditProfileViewControllerLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditProfileViewControllerLayout.swift; sourceTree = ""; }; E761A0DB1F8B8F3900C088E0 /* NynjaButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NynjaButton.swift; sourceTree = ""; }; - E76462881FCD64790091FC2E /* DBModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DBModelProtocol.swift; sourceTree = ""; }; + E76462881FCD64790091FC2E /* DBModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DBModel.swift; sourceTree = ""; }; E764628B1FCD67AC0091FC2E /* DBModelConvertible.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DBModelConvertible.swift; sourceTree = ""; }; E76491951F7A529D001E741C /* WheelContainer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WheelContainer.swift; sourceTree = ""; }; E76491991F7A5485001E741C /* MainWheelContainerDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainWheelContainerDelegate.swift; sourceTree = ""; }; @@ -5991,7 +5989,7 @@ 266AE8C2203496B60096A12C /* AsyncOperation.swift */, F112B18F20E0FBE800B06E3E /* AsyncBlockOperation.swift */, 263C04E82132E2FF00B8F0BE /* WrappedTaskOperation.swift */, - 4BF2C3FB218AFE9D00E59F6C /* FullNameable.swift */, + 4BF2C3FB218AFE9D00E59F6C /* FullNameRepresentable.swift */, ); name = Library; sourceTree = ""; @@ -6975,26 +6973,9 @@ path = ServiceFactory; sourceTree = ""; }; - 4B87712B2192F27C0014AD09 /* StarDAO */ = { - isa = PBXGroup; - children = ( - 4B87712C2192F2920014AD09 /* StarDAOFetchingArgs.swift */, - ); - name = StarDAO; - sourceTree = ""; - }; - 4B87713C219350830014AD09 /* Entity */ = { - isa = PBXGroup; - children = ( - 4B87713D219350940014AD09 /* DBJobQueryArgs.swift */, - ); - name = Entity; - sourceTree = ""; - }; 4B8996C6204ECE8500DCB183 /* Contact */ = { isa = PBXGroup; children = ( - 4BC8B3882191A0FE0086DC6C /* Entity */, 4B8996C9204ECEA700DCB183 /* ContactDAOProtocol.swift */, 4B8996C7204ECE9B00DCB183 /* ContactDAO.swift */, ); @@ -7004,7 +6985,6 @@ 4B8996CB204ED30B00DCB183 /* Star */ = { isa = PBXGroup; children = ( - 4B87712B2192F27C0014AD09 /* StarDAO */, 4B8996CC204ED33400DCB183 /* StarDAOProtocol.swift */, 4B8996CE204ED33D00DCB183 /* StarDAO.swift */, ); @@ -7014,7 +6994,6 @@ 4B8996D6204EDA5E00DCB183 /* Job */ = { isa = PBXGroup; children = ( - 4B87713C219350830014AD09 /* Entity */, 4B8996D7204EDA7700DCB183 /* JobDAOProtocol.swift */, 4B8996D9204EDA9B00DCB183 /* JobDAO.swift */, ); @@ -7122,14 +7101,6 @@ path = AlertImageViewController; sourceTree = ""; }; - 4BC8B3882191A0FE0086DC6C /* Entity */ = { - isa = PBXGroup; - children = ( - 4BC8B3892191A1120086DC6C /* ContactDAOFetchingArgs.swift */, - ); - name = Entity; - sourceTree = ""; - }; 4BC8B38B2191AC200086DC6C /* Entity */ = { isa = PBXGroup; children = ( @@ -12002,7 +11973,9 @@ E74FD69B1FC5D04A00656611 /* Extensions */, E73CEF0F1FC2F52200246066 /* Models */, E7417E971FBED90600E5C124 /* Tables */, + 4BB35E20219AF42A0007C18E /* DAO.swift */, 4B87713A219328780014AD09 /* QueryArgs.swift */, + 4BB35E22219AF46E0007C18E /* RosterRelatedQueryArgs.swift */, ); path = DB; sourceTree = ""; @@ -12193,8 +12166,8 @@ children = ( E7C9CEC91FCC27A30090C2E0 /* FeedProtocol.swift */, E7AE41651FCC498800C3ED5D /* FeedType.swift */, - E76462881FCD64790091FC2E /* DBModelProtocol.swift */, - 4B8771342193154E0014AD09 /* DBExtendedModelProtocol.swift */, + E76462881FCD64790091FC2E /* DBModel.swift */, + 4B8771342193154E0014AD09 /* DBExtendedModel.swift */, ); path = Base; sourceTree = ""; @@ -14844,7 +14817,7 @@ A4330A6F2109EBA70060BD93 /* CountriesProvider.swift in Sources */, A42CE54820692EDB000889CC /* reader.swift in Sources */, A42CE60A20692EDB000889CC /* Index_Spec.swift in Sources */, - 4BF2C3FD218AFF6300E59F6C /* FullNameable.swift in Sources */, + 4BF2C3FD218AFF6300E59F6C /* FullNameRepresentable.swift in Sources */, 26352916207572AA00DC6FBD /* JobExtension.swift in Sources */, 8566BB12215BC39D00320E15 /* FetchType.swift in Sources */, 8520040120D4672E007C0036 /* StickerPack.swift in Sources */, @@ -14998,7 +14971,6 @@ 0008E92420347A8E003E316E /* DBJobMessage.swift in Sources */, 850FC5EC2032F21E00832D87 /* ForwardSelectorProtocols.swift in Sources */, 4BAB9CE62035CB3800385520 /* ScheduleDisplayInfo.swift in Sources */, - 4BC8B38A2191A1120086DC6C /* ContactDAOFetchingArgs.swift in Sources */, E77764C11FBDA9BD0042541D /* ImageFullWheelItemModel.swift in Sources */, 3A8045D21F60C8E200AED866 /* MQTTServiceFriend.swift in Sources */, F117872420ACF2DB007A9A1B /* PhotoQuality.swift in Sources */, @@ -15669,7 +15641,7 @@ 8ED0F3D01FBC5CF2004916AB /* GroupsListTableDS.swift in Sources */, 260225DD20F379EF004FC238 /* MessageConvertionView.swift in Sources */, A45F111F20B4218D00F45004 /* InfoView.swift in Sources */, - E76462891FCD64790091FC2E /* DBModelProtocol.swift in Sources */, + E76462891FCD64790091FC2E /* DBModel.swift in Sources */, A418DA3120ED092F00FE780B /* InfoChannelView.swift in Sources */, E71AB31E1F70188C00A0CF5A /* WheelItemViewFactory.swift in Sources */, 8580BACE20BD98CF00239D9D /* UpdateResult.swift in Sources */, @@ -15803,6 +15775,7 @@ F105C6BE20A1347E0091786A /* PhotoPreviewInteractor.swift in Sources */, F18AEAFD20C15792004FE01C /* SelectAvatarCoordinator.swift in Sources */, 85D66A1220BD965300FBD803 /* UserMentionTableViewCell.swift in Sources */, + 4BB35E23219AF46E0007C18E /* RosterRelatedQueryArgs.swift in Sources */, 2657BE51201233E300F21935 /* ImageFilledItemModel.swift in Sources */, 85BA176120BEA7BD001EF8AC /* StickerPreviewContainerView.swift in Sources */, 85CB25DF20D7325500D5E565 /* StickerPackExtension.swift in Sources */, @@ -16028,7 +16001,7 @@ 00102F42202C914400A877A9 /* NynjaCalendarCell.swift in Sources */, 853FB0662049B193000996C5 /* SupportPresenter.swift in Sources */, 267BE2BA1FE13AB600C47E18 /* ParticipantsWireframe.swift in Sources */, - 4B8771352193154E0014AD09 /* DBExtendedModelProtocol.swift in Sources */, + 4B8771352193154E0014AD09 /* DBExtendedModel.swift in Sources */, F11786CC20A8E4FD007A9A1B /* CameraVideoPreviewViewController.swift in Sources */, A408A0BA20C174040029F54B /* ChannelsListPresenter.swift in Sources */, 850FC60F203310D200832D87 /* SelectionAvatarView.swift in Sources */, @@ -16122,7 +16095,7 @@ 8524C4D6217772C8003BF374 /* Member+Construct.swift in Sources */, A44B4D5A20CE9BDF00CA700A /* SwitchCell.swift in Sources */, F1607B3020B2FD5A00BDF60A /* QRNotificationVIew.swift in Sources */, - 4BF2C3FC218AFE9D00E59F6C /* FullNameable.swift in Sources */, + 4BF2C3FC218AFE9D00E59F6C /* FullNameRepresentable.swift in Sources */, FEA655CB2167777E00B44029 /* SeedVerificationWalletPresenter.swift in Sources */, 260313A420A0A4BA009AC66D /* DirectableActionCellViewModel.swift in Sources */, 859773232087965700B03B4A /* NynjaControlContainerView.swift in Sources */, @@ -16246,7 +16219,6 @@ A42D51B2206A361400EEB952 /* p2p.swift in Sources */, FEA655FB2167777F00B44029 /* TransferDetailsWireFrame.swift in Sources */, FEA655E32167777E00B44029 /* SeedBackupWalletCollectionViewCell.swift in Sources */, - 4B87713E219350940014AD09 /* DBJobQueryArgs.swift in Sources */, A4679B8720B2DA550021FE9C /* Array+ItemModels.swift in Sources */, A45F112720B4218D00F45004 /* MessageRepliedView.swift in Sources */, 85788C3C204422FB003600C9 /* BuildNumberProtocols.swift in Sources */, @@ -16602,6 +16574,7 @@ 8520040720D4F436007C0036 /* StickerPreviewConfig.swift in Sources */, F1607B1D20B20F7800BDF60A /* GridView.swift in Sources */, F11786F320AC7A6E007A9A1B /* VideoPreviewView.swift in Sources */, + 4BB35E21219AF42B0007C18E /* DAO.swift in Sources */, FEA655ED2167777E00B44029 /* TransferHistoryHeaderView.swift in Sources */, 26534B25210B4BE70003B9BC /* DBMessage+Extension.swift in Sources */, 2F7C7F7837BDE6F5767A3A8C /* GroupStorageViewController.swift in Sources */, @@ -16610,7 +16583,6 @@ C921738220BADAFC00519A2D /* TextInputValidationService.swift in Sources */, 9763CCDFE5AF7B58C21CDED9 /* GroupStoragePresenter.swift in Sources */, 16A903BE16E0899FD3E5D232 /* GroupStorageInteractor.swift in Sources */, - 4B87712D2192F2920014AD09 /* StarDAOFetchingArgs.swift in Sources */, 85D66A0820BD963C00FBD803 /* MentionInputFilter.swift in Sources */, 8514F17A20EA219F00883513 /* ContextMenuArrowView.swift in Sources */, B3D0F59E1E7BDB7E485AE662 /* GroupStorageWireframe.swift in Sources */, diff --git a/Nynja.xcworkspace/contents.xcworkspacedata b/Nynja.xcworkspace/contents.xcworkspacedata index 13dce7e5a..070d68e56 100644 --- a/Nynja.xcworkspace/contents.xcworkspacedata +++ b/Nynja.xcworkspace/contents.xcworkspacedata @@ -1,6 +1,9 @@ + + diff --git a/Nynja/BadgeNumberService.swift b/Nynja/BadgeNumberService.swift index 51fd37560..f86d18afc 100644 --- a/Nynja/BadgeNumberService.swift +++ b/Nynja/BadgeNumberService.swift @@ -6,21 +6,27 @@ // Copyright © 2018 TecSynt Solutions. All rights reserved. // -class BadgeNumberService: BadgeNumberServiceProtocol, StorageSubscriber { +final class BadgeNumberService: BadgeNumberServiceProtocol, StorageSubscriber { typealias BadgeHandler = (_ badgeNumber: Int64) -> Void + // MARK: - Singleton + static let shared = BadgeNumberService() + // MARK: - Properties - private(set) var badgeNumber: Int64 = 0 + + private var badgeNumber: Int64 = 0 private var counters: [String: Int64] = [:] private var subscribers: [AnyWeakSubscriber] = [] private let conversationsProvider = ServiceFactory().makeConversationsProvider() + // MARK: - Init & deinit + private init() { subscribe() } @@ -33,7 +39,9 @@ class BadgeNumberService: BadgeNumberServiceProtocol, StorageSubscriber { unsubscribe() } + // MARK: - Subscription + private func subscribe() { subscribeTypes.forEach { DBObserver.default.register(subscriber: self, type: $0) @@ -44,8 +52,10 @@ class BadgeNumberService: BadgeNumberServiceProtocol, StorageSubscriber { DBObserver.default.unregister(subscriber: self) } + // MARK: - Register subscribers // TODO: sync + func observeBadgeNumber(_ object: AnyObject, notifyImmediately: Bool = true, handler: @escaping BadgeHandler) { let subscriber = AnyWeakSubscriber(object: object, handler: handler) subscribers.append(subscriber) @@ -61,6 +71,7 @@ class BadgeNumberService: BadgeNumberServiceProtocol, StorageSubscriber { func clean() { badgeNumber = 0 + counters = [:] subscribers.forEach { $0.handler(badgeNumber) } } @@ -106,5 +117,4 @@ class BadgeNumberService: BadgeNumberServiceProtocol, StorageSubscriber { subscribers.forEach { $0.handler(badgeNumber) } } - } diff --git a/Nynja/BadgeNumberServiceProtocol.swift b/Nynja/BadgeNumberServiceProtocol.swift index 83255461a..f7021e3e7 100644 --- a/Nynja/BadgeNumberServiceProtocol.swift +++ b/Nynja/BadgeNumberServiceProtocol.swift @@ -12,9 +12,6 @@ typealias BadgeHandler = (_ badgeNumber: Int64) -> Void /// Service which provide access to badge number and capability to observe changes protocol BadgeNumberServiceProtocol { - /// Represents badge number which should show in the app icon - var badgeNumber: Int64 { get } - // MARK: - Register subscribers /// Add subscriber which observe changes of 'badgeNumber' property func observeBadgeNumber(_ object: AnyObject, notifyImmediately: Bool, handler: @escaping BadgeHandler) diff --git a/Nynja/ContactDAO.swift b/Nynja/ContactDAO.swift index 2136e19e6..a14579f80 100644 --- a/Nynja/ContactDAO.swift +++ b/Nynja/ContactDAO.swift @@ -62,7 +62,7 @@ class ContactDAO: ContactDAOProtocol { return dbManager.fetch { db -> [DBContact] in return try DBContact .filter(Column.rosterId == rosterId) - .fetchAll(from: db) + .fetchAll(db) }.serverModels } @@ -85,7 +85,7 @@ class ContactDAO: ContactDAOProtocol { predicate = !predicate } - return try DBContact.filter(predicate).fetchAll(from: db) + return try DBContact.filter(predicate).fetchAll(db) }.serverModels } @@ -95,41 +95,35 @@ class ContactDAO: ContactDAOProtocol { return dbManager.fetch { db -> [DBContact] in return try DBContact .filter(statuses.contains(Column.status)) - .fetchAll(from: db) + .fetchAll(db) }.serverModels } - static func fetchContacts(with args: ContactDAOFetchingArgs) -> [Contact] { + static func fetchContacts(with args: RosterRelatedQueryArgs) -> [Contact] { return dbManager.fetch { db in - return try makeQuery(with: args).fetchAll(db) + return try args.makeTypedRequest().fetchAllConstructed(db) }.serverModels } static func fetchContacts( - with args: ContactDAOFetchingArgs, + with args: RosterRelatedQueryArgs, excludingIds ids: [String], excludingStatuses statuses: [Contact.Status]?) -> [Contact] { return dbManager.fetch { db in - return try makeQuery(with: args) + return try args + .makeTypedRequest() .filter(!ids.contains(Column.phoneId)) .performIfValueExists(statuses) { $0.filter(!$1.strings.contains(Column.status)) } - .fetchAll(from: db) + .fetchAll(db) }.serverModels } - private static func makeQuery(with args: ContactDAOFetchingArgs) -> QueryInterfaceRequest { - return DBContact - .filter(Column.rosterId == args.rosterId) - .performIfValueExists(args.limit) { $0.limit($1, offset: args.offset) } - .performIfValueExists(args.orderColumns) { $0.order($1) } - } - static func fetchChats(with args: DBContact.RequestArgs) -> [Contact] { return dbManager.fetch { db in return try DBContact .request(with: args) - .fetchAll(from: db) + .fetchAllConstructed(db) }.serverModels } diff --git a/Nynja/ContactDAOFetchingArgs.swift b/Nynja/ContactDAOFetchingArgs.swift deleted file mode 100644 index a6949b548..000000000 --- a/Nynja/ContactDAOFetchingArgs.swift +++ /dev/null @@ -1,27 +0,0 @@ -// -// ContactDAOFetchingArgs.swift -// Nynja -// -// Created by Volodymyr Hryhoriev on 11/6/18. -// Copyright © 2018 TecSynt Solutions. All rights reserved. -// - -import GRDBCipher - -struct ContactDAOFetchingArgs { - let rosterId: Int64 - let limit: Int? - let offset: Int? - let orderColumns: [SQLOrderingTerm]? - - init(rosterId: Int64, - limit: Int? = nil, - offset: Int? = nil, - orderColumns: [SQLOrderingTerm]? = nil) { - - self.rosterId = rosterId - self.limit = limit - self.offset = offset - self.orderColumns = orderColumns - } -} diff --git a/Nynja/ContactDAOProtocol.swift b/Nynja/ContactDAOProtocol.swift index 7eacb84e0..0442ec4a4 100644 --- a/Nynja/ContactDAOProtocol.swift +++ b/Nynja/ContactDAOProtocol.swift @@ -27,8 +27,8 @@ protocol ContactDAOProtocol: DAOProtocol { static func fetchContacts(with phoneIds: [String], isExcluded: Bool) -> [Contact] static func fetchContacts(with statuses: [Contact.Status]) -> [Contact] - static func fetchContacts(with args: ContactDAOFetchingArgs) -> [Contact] - static func fetchContacts(with args: ContactDAOFetchingArgs, + static func fetchContacts(with args: RosterRelatedQueryArgs) -> [Contact] + static func fetchContacts(with args: RosterRelatedQueryArgs, excludingIds ids: [String], excludingStatuses statuses: [Contact.Status]?) -> [Contact] diff --git a/Nynja/ContactsProvider.swift b/Nynja/ContactsProvider.swift index d2d32e5f6..48ecf9345 100644 --- a/Nynja/ContactsProvider.swift +++ b/Nynja/ContactsProvider.swift @@ -8,23 +8,23 @@ import GRDBCipher -class ContactsProvider: ContactsProviding { +final class ContactsProvider: ContactsProviding { typealias FetchingArgs = ContactsProvidingFetchingArgs private let orderColumns = [Column.created.desc] func fetchHistory(with args: FetchingArgs) -> [Contact] { - let daoArgs = makeDAOFetchingArgs(from: args) + let daoArgs = makeRosterRelatedQueryArgs(from: args) return ContactDAO.fetchContacts(with: daoArgs, excludingIds: [args.phoneId], excludingStatuses: args.statuses) } - private func makeDAOFetchingArgs(from args: FetchingArgs) -> ContactDAOFetchingArgs { - return ContactDAOFetchingArgs( + private func makeRosterRelatedQueryArgs(from args: FetchingArgs) -> RosterRelatedQueryArgs { + return RosterRelatedQueryArgs( rosterId: args.rosterId, - limit: args.limit, - orderColumns: orderColumns) + args: .init(limit: args.limit, + orderingTerms: orderColumns)) } func fetchFriends() -> [Contact] { @@ -38,5 +38,4 @@ class ContactsProvider: ContactsProviding { .fetchContacts(with: [.friend]) .withoutSelf } - } diff --git a/Nynja/ContactsProviding.swift b/Nynja/ContactsProviding.swift index 0d44e9650..2913cd0f6 100644 --- a/Nynja/ContactsProviding.swift +++ b/Nynja/ContactsProviding.swift @@ -12,5 +12,4 @@ protocol ContactsProviding { func fetchFriends() -> [Contact] func fetchFriendsWithoutBlocked() -> [Contact] - } diff --git a/Nynja/ConversationsProvider.swift b/Nynja/ConversationsProvider.swift index 9ca5b34ad..13e1a5026 100644 --- a/Nynja/ConversationsProvider.swift +++ b/Nynja/ConversationsProvider.swift @@ -9,20 +9,21 @@ import GRDBCipher class ConversationsProvider: ConversationsProviding, InitializeInjectable { - typealias FetchingArgs = ConversationsProvidingFetchingArgs - let dbManager: DBManagerProtocol + private let dbManager: DBManagerProtocol required init(dependencies: Dependencies) { self.dbManager = dependencies.dbManager } + // MARK: - Chats func fetchChats(with args: FetchingArgs) -> [Contact] { return ContactDAO.fetchChats(with: args) } + // MARK: - Groups func fetchGroups(with args: FetchingArgs) -> [Room] { @@ -30,40 +31,36 @@ class ConversationsProvider: ConversationsProviding, InitializeInjectable { return RoomDAO.fetchRooms(with: groupRequestArgs) } - private func makeGroupsRequestArgs(from args: FetchingArgs, kind: Room.Kind) -> DBRoom.RequestArgs { + + // MARK: - Channels + + func fetchChannels(with args: ConversationsProviding.FetchingArgs) -> [Room] { + let channelRequestArgs = makeGroupsRequestArgs(from: args, kind: .channel) + return RoomDAO.fetchRooms(with: channelRequestArgs) + } + + func fetchMyChannels(with args: ConversationsProviding.FetchingArgs, ownerId: String) -> [Room] { + let channelRequestArgs = makeGroupsRequestArgs(from: args, kind: .channel, ownerId: ownerId) + return RoomDAO.fetchRooms(with: channelRequestArgs) + } + + private func makeGroupsRequestArgs(from args: FetchingArgs, kind: Room.Kind, ownerId: String? = nil) -> DBRoom.RequestArgs { return .init( rosterId: args.rosterId, type: kind.rawValue, onlyUnread: args.onlyUnread, + ownerId: ownerId, limit: args.limit, offset: args.offset) } - - // MARK: - Channels - - func fetchChannels() -> [Room] { - return RoomDAO - .fetchRooms(kind: .channel) - .sorted(by: comparator) - } - - func fetchMyChannels() -> [Room] { - guard let phoneId = StorageService.sharedInstance.phoneId else { - return [] - } - - return RoomDAO - .fetchUserRooms(with: phoneId, isAdmin: true, kind: .channel) - .sorted(by: comparator) - } - func comparator(lhs: ChatModel, rhs: ChatModel) -> Bool { let created1 = lhs.last_msg?.created ?? 0 let created2 = rhs.last_msg?.created ?? 0 return created1 > created2 } + // MARK: - All func fetchUnreadMessagesCount() -> Int64 { diff --git a/Nynja/ConversationsProviding.swift b/Nynja/ConversationsProviding.swift index 4f8a9240d..10bf1bb95 100644 --- a/Nynja/ConversationsProviding.swift +++ b/Nynja/ConversationsProviding.swift @@ -7,17 +7,15 @@ // -typealias ConversationsProvidingFetchingArgs = DBContact.RequestArgs - protocol ConversationsProviding { - var dbManager: DBManagerProtocol { get } - - func fetchChats(with args: ConversationsProvidingFetchingArgs) -> [Contact] + typealias FetchingArgs = DBContact.RequestArgs + + func fetchChats(with args: FetchingArgs) -> [Contact] - func fetchGroups(with args: ConversationsProvidingFetchingArgs) -> [Room] + func fetchGroups(with args: FetchingArgs) -> [Room] - func fetchChannels() -> [Room] - func fetchMyChannels() -> [Room] + func fetchChannels(with args: FetchingArgs) -> [Room] + func fetchMyChannels(with args: FetchingArgs, ownerId: String) -> [Room] func comparator(lhs: ChatModel, rhs: ChatModel) -> Bool diff --git a/Nynja/DB/Extensions/TypedRequestExtension.swift b/Nynja/DB/Extensions/TypedRequestExtension.swift index 4a55d9ee7..8f527dc78 100644 --- a/Nynja/DB/Extensions/TypedRequestExtension.swift +++ b/Nynja/DB/Extensions/TypedRequestExtension.swift @@ -10,22 +10,30 @@ import GRDBCipher enum DBModelFetchingKind { case plain - case full + case constructed } -extension TypedRequest where RowDecoder: DBModelProtocol { +extension TypedRequest where RowDecoder: DBModel { - func fetchAll(kind: DBModelFetchingKind = .full, from db: Database) throws -> [RowDecoder] { + func fetchAllConstructed(_ db: Database) throws -> [RowDecoder] { + return try fetchAll(db, kind: .constructed) + } + + func fetchOneConstructed(_ db: Database) throws -> RowDecoder? { + return try fetchOne(db, kind: .constructed) + } + + func fetchAll(_ db: Database, kind: DBModelFetchingKind) throws -> [RowDecoder] { let models = try self.fetchAll(db) - if kind == .full { + if kind == .constructed { try models.construct(db) } return models } - func fetchOne(kind: DBModelFetchingKind = .full, from db: Database) throws -> RowDecoder? { + func fetchOne(_ db: Database, kind: DBModelFetchingKind) throws -> RowDecoder? { let model = try self.fetchOne(db) - if kind == .full { + if kind == .constructed { try model?.construct(db) } return model diff --git a/Nynja/DB/Models/Base/DBExtendedModelProtocol.swift b/Nynja/DB/Models/Base/DBExtendedModel.swift similarity index 55% rename from Nynja/DB/Models/Base/DBExtendedModelProtocol.swift rename to Nynja/DB/Models/Base/DBExtendedModel.swift index 1090e9f0c..a07fd4c48 100644 --- a/Nynja/DB/Models/Base/DBExtendedModelProtocol.swift +++ b/Nynja/DB/Models/Base/DBExtendedModel.swift @@ -1,11 +1,11 @@ // -// DBExtendedModelProtocol.swift +// DBExtendedModel.swift // Nynja // // Created by Volodymyr Hryhoriev on 11/7/18. // Copyright © 2018 TecSynt Solutions. All rights reserved. // -protocol DBExtendedModelProtocol: DBModelProtocol, ServerModelConvertible { +protocol DBExtendedModel: DBModel, ServerModelConvertible { } diff --git a/Nynja/DB/Models/Base/DBModelProtocol.swift b/Nynja/DB/Models/Base/DBModel.swift similarity index 75% rename from Nynja/DB/Models/Base/DBModelProtocol.swift rename to Nynja/DB/Models/Base/DBModel.swift index f88a8db58..c6dabaf59 100644 --- a/Nynja/DB/Models/Base/DBModelProtocol.swift +++ b/Nynja/DB/Models/Base/DBModel.swift @@ -1,5 +1,5 @@ // -// DBModelProtocol.swift +// DBModel.swift // Nynja // // Created by Volodymyr Hryhoriev on 11/28/17. @@ -8,7 +8,7 @@ import GRDBCipher -protocol DBModelProtocol: Persistable, RowConvertible { +protocol DBModel: Persistable, RowConvertible { func saveAggregate(_ db: Database) throws @@ -18,7 +18,7 @@ protocol DBModelProtocol: Persistable, RowConvertible { func construct(_ db: Database) throws } -extension DBModelProtocol { +extension DBModel { func saveAggregate(_ db: Database) throws { try save(db) @@ -33,9 +33,9 @@ extension DBModelProtocol { func construct(_ db: Database) throws {} } -extension Array where Element: DBModelProtocol { +extension Array where Element: DBModel { func construct(_ db: Database) throws { - try self.forEach { try $0.construct(db) } + try forEach { try $0.construct(db) } } } diff --git a/Nynja/DB/Models/Base/FeedProtocol.swift b/Nynja/DB/Models/Base/FeedProtocol.swift index fd843bbac..bd113fc65 100644 --- a/Nynja/DB/Models/Base/FeedProtocol.swift +++ b/Nynja/DB/Models/Base/FeedProtocol.swift @@ -8,7 +8,7 @@ import GRDBCipher -protocol FeedProtocol: DBModelProtocol { +protocol FeedProtocol: DBModel { var id: Int64? { get set } var type: FeedType { get } diff --git a/Nynja/DB/Models/DBChatCheckpoint.swift b/Nynja/DB/Models/DBChatCheckpoint.swift index a43295c04..330867461 100644 --- a/Nynja/DB/Models/DBChatCheckpoint.swift +++ b/Nynja/DB/Models/DBChatCheckpoint.swift @@ -8,7 +8,7 @@ import GRDBCipher -class DBChatCheckpoint : Record, DBModelProtocol { +final class DBChatCheckpoint : Record, DBModel { var feedId: Int64 var feedType: Int @@ -49,7 +49,7 @@ class DBChatCheckpoint : Record, DBModelProtocol { container[ChatCheckpointTable.Column.topOffset.title] = topOffset } - // MARK: - DBModelProtocol + // MARK: - DBModel func saveAggregate(_ db: Database) throws { try self.save(db) } diff --git a/Nynja/DB/Models/DBContact.swift b/Nynja/DB/Models/DBContact.swift index 08167226f..e9f681f79 100644 --- a/Nynja/DB/Models/DBContact.swift +++ b/Nynja/DB/Models/DBContact.swift @@ -8,7 +8,7 @@ import GRDBCipher -class DBContact: Record, DBExtendedModelProtocol { +final class DBContact: Record, DBExtendedModel { var phoneId: String var avatar: String? @@ -25,7 +25,11 @@ class DBContact: Record, DBExtendedModelProtocol { var messageId: String? var rosterId: Int64? - var message: DBMessage? + var message: DBMessage? { + didSet { + messageId = message?.localId + } + } var features: [DBFeature] = [] var services: [DBService] = [] @@ -50,7 +54,7 @@ class DBContact: Record, DBExtendedModelProtocol { if let message = contact.message { self.message = DBMessage(message: message) - self.messageId = message.msg_id + self.messageId = self.message?.localId } else { self.messageId = contact.lastMessageId } @@ -134,11 +138,6 @@ class DBContact: Record, DBExtendedModelProtocol { // MARK: - Modification func saveAggregate(_ db: Database) throws { try message?.saveAggregate(db) - - if message != nil || messageId == nil { - messageId = message?.localId - } - try save(db) try features.forEach { try $0.save(db) } @@ -147,13 +146,13 @@ class DBContact: Record, DBExtendedModelProtocol { @discardableResult func deleteAggregate(_ db: Database) throws -> Bool { - try DBContact.request(targetId: self.phoneId).deleteAll(db) - - try DBService.request(targetId: self.phoneId, targetType: .contact).deleteAll(db) + try DBContact.request(targetId: phoneId).deleteAll(db) + try DBService.request(targetId: phoneId, targetType: .contact).deleteAll(db) if let phoneId = StorageService.sharedInstance.phoneId { - try DBP2p.delete(db, firstId: self.phoneId, secondId: phoneId) + try DBP2p.delete(db, firstId: phoneId, secondId: phoneId) } + return try self.delete(db) } diff --git a/Nynja/DB/Models/DBConvertMessage.swift b/Nynja/DB/Models/DBConvertMessage.swift index e64ac6c90..b68e1d05f 100644 --- a/Nynja/DB/Models/DBConvertMessage.swift +++ b/Nynja/DB/Models/DBConvertMessage.swift @@ -8,7 +8,7 @@ import GRDBCipher -class DBConvertMessage: Record, DBModelProtocol { +final class DBConvertMessage: Record, DBModel { var messageId: String var type: Int @@ -75,7 +75,7 @@ class DBConvertMessage: Record, DBModelProtocol { } - //MARK: - DBModelProtocol + //MARK: - DBModel func saveAggregate(_ db: Database) throws { try self.save(db) } diff --git a/Nynja/DB/Models/DBDesc.swift b/Nynja/DB/Models/DBDesc.swift index 3871fba2b..b8884b2eb 100644 --- a/Nynja/DB/Models/DBDesc.swift +++ b/Nynja/DB/Models/DBDesc.swift @@ -8,7 +8,7 @@ import GRDBCipher -final class DBDesc: Record, DBModelProtocol { +final class DBDesc: Record, DBModel { let serverId: String let mime: String diff --git a/Nynja/DB/Models/DBJob.swift b/Nynja/DB/Models/DBJob.swift index f70e69738..74b6205f2 100644 --- a/Nynja/DB/Models/DBJob.swift +++ b/Nynja/DB/Models/DBJob.swift @@ -9,7 +9,7 @@ import Foundation import GRDBCipher -class DBJob: Record, DBExtendedModelProtocol { +final class DBJob: Record, DBExtendedModel { enum JobType: Int { case schedule = 0 @@ -90,17 +90,17 @@ class DBJob: Record, DBExtendedModelProtocol { // MARK: - Fetch static func job(from db: Database, rowId: Int64) throws -> DBJob? { - return try DBJob.filter(Column.rowID == rowId).fetchOne(from: db) + return try DBJob.filter(Column.rowID == rowId).fetchOneConstructed(db) } static func job(_ db: Database, serverId: Int64) throws -> DBJob? { - return try DBJob.filter(Column.serverId == serverId).fetchOne(from: db) + return try DBJob.filter(Column.serverId == serverId).fetchOneConstructed(db) } static func jobs(_ db: Database, filter: SQLExpressible? = nil) throws -> [DBJob] { return try (filter == nil ? - DBJob.all().fetchAll(from: db) : // TODO: need to think - DBJob.filter(filter!).fetchAll(from: db)) + DBJob.all().fetchAllConstructed(db) : // TODO: need to think + DBJob.filter(filter!).fetchAllConstructed(db)) } static func jobs(_ db: Database, type: JobType) throws -> [DBJob] { @@ -201,3 +201,18 @@ class DBJob: Record, DBExtendedModelProtocol { return sql } } + + +extension DBJob { + + struct QueryArgs: CompositeQueryArgs { + let type: DBJob.JobType + let args: AnyQueryArgs + + func makeTypedRequest() -> QueryInterfaceRequest { + return AnyQueryArgs(args: args) + .filter { $0.filter(Column.type == self.type.rawValue) } + .makeTypedRequest() + } + } +} diff --git a/Nynja/DB/Models/DBJobMessage.swift b/Nynja/DB/Models/DBJobMessage.swift index c779dc881..fd146f3c0 100644 --- a/Nynja/DB/Models/DBJobMessage.swift +++ b/Nynja/DB/Models/DBJobMessage.swift @@ -9,7 +9,7 @@ import Foundation import GRDBCipher -final class DBJobMessage: Record, DBModelProtocol { +final class DBJobMessage: Record, DBModel { var id: Int64? var container: String? diff --git a/Nynja/DB/Models/DBLink.swift b/Nynja/DB/Models/DBLink.swift index a10c2009f..2df604df6 100644 --- a/Nynja/DB/Models/DBLink.swift +++ b/Nynja/DB/Models/DBLink.swift @@ -52,7 +52,7 @@ final class DBLink: Codable { } -extension DBLink: DBModelProtocol { +extension DBLink: DBModel { static var databaseTableName: String { return LinkTable.name diff --git a/Nynja/DB/Models/DBMember.swift b/Nynja/DB/Models/DBMember.swift index 10cc75857..52aa7fb66 100644 --- a/Nynja/DB/Models/DBMember.swift +++ b/Nynja/DB/Models/DBMember.swift @@ -8,7 +8,7 @@ import GRDBCipher -class DBMember: Record, DBModelProtocol { +final class DBMember: Record, DBModel { var id: Int64 var container: String? @@ -145,15 +145,6 @@ class DBMember: Record, DBModelProtocol { try member.construct(db) return member } - - static func all(from db: Database) throws -> [DBMember] { - let members = try DBMember.fetchAll(db) - try members.forEach { - try $0.construct(db) - } - - return members - } static func member(from db: Database, id: Int64) throws -> DBMember? { guard let member = try DBMember.fetchOne(db, key: id) else { @@ -164,16 +155,16 @@ class DBMember: Record, DBModelProtocol { return member } + static func member(from db: Database, roomId: String, phoneId: String) throws -> DBMember? { + return try requestMember(roomId: roomId, phoneIds: [phoneId]).fetchOneConstructed(db) + } + static func members(from db: Database, roomId: String, isAdmin: Bool = false) throws -> [DBMember] { - let members = try requestMember(roomId: roomId, isAdmin: isAdmin).fetchAll(db) - try members.forEach { try $0.construct(db) } - return members + return try requestMember(roomId: roomId, isAdmin: isAdmin).fetchAllConstructed(db) } - static func member(from db: Database, roomId: String, phoneId: String) throws -> DBMember? { - let member = try requestMember(roomId: roomId, phoneId: phoneId).fetchOne(db) - try member?.construct(db) - return member + static func members(from db: Database, roomId: String, phoneIds: [String]) throws -> [DBMember] { + return try requestMember(roomId: roomId, phoneIds: phoneIds).fetchAllConstructed(db) } func construct(_ db: Database) throws { @@ -206,17 +197,21 @@ class DBMember: Record, DBModelProtocol { return DBFeature.request(targetId: targetId, targetType: DBFeature.TargetType.member) } - static private func requestMember(roomId: String, phoneId: String) -> AnyTypedRequest { + static private func requestMember(roomId: String, phoneIds: [String]) -> AnyTypedRequest { let memberTable = MemberTable.name let roomMemberTable = RoomMemberTable.name + let phoneIds = phoneIds + .map { "'\($0)'" } + .joinedByComma() + let sql = """ SELECT \(memberTable).* FROM \(roomMemberTable) LEFT JOIN \(memberTable) ON \(roomMemberTable).\(RoomMemberTable.Column.memberId.title) = \(memberTable).\(MemberTable.Column.id.title) WHERE \(RoomMemberTable.Column.roomId.title) = '\(roomId)' - AND \(MemberTable.Column.phoneId.title) = '\(phoneId)' + AND \(MemberTable.Column.phoneId.title) in (\(phoneIds)) """ return SQLRequest(sql).asRequest(of: DBMember.self) diff --git a/Nynja/DB/Models/DBMessage.swift b/Nynja/DB/Models/DBMessage.swift index b40740424..9c0ec7a6b 100644 --- a/Nynja/DB/Models/DBMessage.swift +++ b/Nynja/DB/Models/DBMessage.swift @@ -10,7 +10,7 @@ import GRDBCipher private let tableName = MessageTable.name -final class DBMessage: Record, DBModelProtocol { +final class DBMessage: Record, DBModel { var container: String? var feedId: Int64? diff --git a/Nynja/DB/Models/DBMessageAction.swift b/Nynja/DB/Models/DBMessageAction.swift index fa3756603..099a5176b 100644 --- a/Nynja/DB/Models/DBMessageAction.swift +++ b/Nynja/DB/Models/DBMessageAction.swift @@ -8,7 +8,7 @@ import GRDBCipher -final class DBMessageAction: Record, DBModelProtocol { +final class DBMessageAction: Record, DBModel { enum Action: String { case delete = "delete" @@ -51,7 +51,7 @@ final class DBMessageAction: Record, DBModelProtocol { } - // MARK: - DBModelProtocol + // MARK: - DBModel func saveAggregate(_ db: Database) throws { try self.save(db) diff --git a/Nynja/DB/Models/DBMessageEditAction.swift b/Nynja/DB/Models/DBMessageEditAction.swift index ee2ff754d..0e1bb65e2 100644 --- a/Nynja/DB/Models/DBMessageEditAction.swift +++ b/Nynja/DB/Models/DBMessageEditAction.swift @@ -8,7 +8,7 @@ import GRDBCipher -final class DBMessageEditAction: Record, DBModelProtocol { +final class DBMessageEditAction: Record, DBModel { var messageId: MessageServerId var payload: String @@ -45,7 +45,7 @@ final class DBMessageEditAction: Record, DBModelProtocol { } - // MARK: - DBModelProtocol + // MARK: - DBModel func saveAggregate(_ db: Database) throws { try save(db) diff --git a/Nynja/DB/Models/DBMessageLink.swift b/Nynja/DB/Models/DBMessageLink.swift index 307000ddd..12f3236b1 100644 --- a/Nynja/DB/Models/DBMessageLink.swift +++ b/Nynja/DB/Models/DBMessageLink.swift @@ -8,7 +8,7 @@ import GRDBCipher -class DBMessageLink: Record, DBModelProtocol { +final class DBMessageLink: Record, DBModel { var value: String? var feedId: String? @@ -56,7 +56,7 @@ class DBMessageLink: Record, DBModelProtocol { container[MessageLinkTable.Column.feedType.title] = feedType } - // MARK: - DBModelProtocol + // MARK: - DBModel func saveAggregate(_ db: Database) throws { try self.save(db) } diff --git a/Nynja/DB/Models/DBMuc.swift b/Nynja/DB/Models/DBMuc.swift index a2be04a71..3d35b1dd4 100644 --- a/Nynja/DB/Models/DBMuc.swift +++ b/Nynja/DB/Models/DBMuc.swift @@ -8,7 +8,7 @@ import GRDBCipher -class DBMuc: Record, FeedProtocol { +final class DBMuc: Record, FeedProtocol { var id: Int64? var name: String @@ -49,7 +49,7 @@ class DBMuc: Record, FeedProtocol { container[MucTable.Column.name.title] = name } - // MARK: - DBModelProtocol + // MARK: - DBModel func saveAggregate(_ db: Database) throws { let nameColumn = Column(MucTable.Column.name.title) diff --git a/Nynja/DB/Models/DBP2p.swift b/Nynja/DB/Models/DBP2p.swift index b57d36ea6..1742c46ca 100644 --- a/Nynja/DB/Models/DBP2p.swift +++ b/Nynja/DB/Models/DBP2p.swift @@ -8,7 +8,7 @@ import GRDBCipher -class DBP2p: Record, FeedProtocol { +final class DBP2p: Record, FeedProtocol { var id: Int64? var from: String @@ -53,7 +53,7 @@ class DBP2p: Record, FeedProtocol { container[P2pTable.Column.to.title] = to } - // MARK: - DBModelProtocol + // MARK: - DBModel func saveAggregate(_ db: Database) throws { let fromColumn = Column(P2pTable.Column.from.title) let toColumn = Column(P2pTable.Column.to.title) diff --git a/Nynja/DB/Models/DBProfile.swift b/Nynja/DB/Models/DBProfile.swift index 7c7ef4ff5..bc714e10b 100644 --- a/Nynja/DB/Models/DBProfile.swift +++ b/Nynja/DB/Models/DBProfile.swift @@ -8,7 +8,7 @@ import GRDBCipher -class DBProfile: Record, DBModelProtocol { +final class DBProfile: Record, DBModel { var phone: String var update: Int64 diff --git a/Nynja/DB/Models/DBRecentSticker.swift b/Nynja/DB/Models/DBRecentSticker.swift index 8c1f0315b..07fa83dc4 100644 --- a/Nynja/DB/Models/DBRecentSticker.swift +++ b/Nynja/DB/Models/DBRecentSticker.swift @@ -8,7 +8,7 @@ import GRDBCipher -final class DBRecentSticker: Codable, DBModelProtocol { +final class DBRecentSticker: Codable, DBModel { var id: Int64? var stickerId: String diff --git a/Nynja/DB/Models/DBRoom.swift b/Nynja/DB/Models/DBRoom.swift index ede2aa874..87d8890e8 100644 --- a/Nynja/DB/Models/DBRoom.swift +++ b/Nynja/DB/Models/DBRoom.swift @@ -8,7 +8,7 @@ import GRDBCipher -class DBRoom: Record, DBExtendedModelProtocol { +final class DBRoom: Record, DBExtendedModel { var id: String var name: String @@ -32,7 +32,11 @@ class DBRoom: Record, DBExtendedModelProtocol { var rosterId: Int64? var messageId: String? - var message: DBMessage? + var message: DBMessage? { + didSet { + messageId = message?.localId + } + } var members: [DBMember] = [] var admins: [DBMember] = [] @@ -67,7 +71,7 @@ class DBRoom: Record, DBExtendedModelProtocol { // Message if let message = room.message { self.message = DBMessage(message: message) - self.messageId = room.message?.msg_id + self.messageId = self.message?.localId } else { self.messageId = room.lastMessageId } @@ -133,7 +137,7 @@ class DBRoom: Record, DBExtendedModelProtocol { // MARK: - Modification func saveAggregate(_ db: Database) throws { - try saveMessage(db) + try message?.saveAggregate(db) reader = calculateReader() try save(db) @@ -157,14 +161,6 @@ class DBRoom: Record, DBExtendedModelProtocol { return readers.max(except: selfReader) } - private func saveMessage(_ db: Database) throws { - try message?.saveAggregate(db) - - if message != nil || messageId == nil { - messageId = message?.localId - } - } - private func saveMembers(_ db: Database) throws { try admins.forEach { try $0.saveAggregate(db) @@ -189,9 +185,7 @@ class DBRoom: Record, DBExtendedModelProtocol { // MARK: - Fetch static func room(from db: Database, rowId: Int64) throws -> DBRoom? { - let room = try DBRoom.filter(Column.rowID == rowId).fetchOne(db) - try room?.construct(db) - return room + return try DBRoom.filter(Column.rowID == rowId).fetchOneConstructed(db) } static func room(from db: Database, id: String, fullModel: Bool = true) throws -> DBRoom? { @@ -203,13 +197,6 @@ class DBRoom: Record, DBExtendedModelProtocol { return room } - static func rooms(_ db: Database, contactId: String, isAdmin: Bool, type: String?) throws -> [DBRoom] { - let sql = sqlContactRooms(contactId, isAdmin: isAdmin, type: type) - let rooms = try SQLRequest(sql).asRequest(of: DBRoom.self).fetchAll(db) - try rooms.forEach { try $0.construct(db) } - return rooms - } - static func rooms(_ db: Database, rosterId: Int64, type: String?) throws -> [DBRoom] { let rosterIdColumn = Column(RoomTable.Column.rosterId.title) return try rooms(db, type: type, predicate: rosterIdColumn == rosterId) @@ -217,10 +204,14 @@ class DBRoom: Record, DBExtendedModelProtocol { static func rooms(_ db: Database, ids: [String], type: String?) throws -> [DBRoom] { let idColumn = Column(RoomTable.Column.id.title) - return try rooms(db, type: type, predicate: ids.contains(idColumn)) + return try rooms( + db, + type: type, + predicate: ids.contains(idColumn), + onlySenderAndSelf: true) } - private static func rooms(_ db: Database, type: String?, predicate: SQLExpressible) throws -> [DBRoom] { + private static func rooms(_ db: Database, type: String?, predicate: SQLExpressible, onlySenderAndSelf: Bool = false) throws -> [DBRoom] { let typeColumn = Column(RoomTable.Column.type.title) let statusColumn = Column(RoomTable.Column.status.title) @@ -230,59 +221,64 @@ class DBRoom: Record, DBExtendedModelProtocol { } let rooms = try DBRoom.filter(predicate).fetchAll(db) - try rooms.forEach { try $0.construct(db) } + try rooms.forEach { try $0.construct(db, onlySenderAndSelf: onlySenderAndSelf) } + return rooms + } + + static func rooms(_ db: Database, args: RequestArgs) throws -> [DBRoom] { + let rooms = try request(with: args).fetchAll(db) + try rooms.forEach { try $0.construct(db, onlySenderAndSelf: true) } return rooms } func construct(_ db: Database) throws { + try construct(db, onlySenderAndSelf: false) + } + + private func construct(_ db: Database, onlySenderAndSelf: Bool) throws { if let messageId = self.messageId { self.message = try DBMessage.message(db, localId: messageId) } - self.admins = try DBMember.members(from: db, roomId: id, isAdmin: true) - self.members = try DBMember.members(from: db, roomId: id) + var phoneIds: [String] = [] + if onlySenderAndSelf { + message?.from.map { phoneIds.append($0) } + StorageService.sharedInstance.phoneId.map { phoneIds.append($0) } + } + + try constructMembers(db, phoneIds: phoneIds) self.features = (try? DBRoom.requestFeature(targetId: id).fetchAll(db)) ?? [] self.files = DBRoom.fetchDescs(db, roomId: id) self.links = (try? DBLink.links(db, roomId: id)) ?? [] } - static func fetchDescs(_ db: Database, roomId: String) -> [DBDesc] { - return (try? descs(targetId: roomId, db: db)) ?? [] - } - - private static func sqlContactRooms(_ contactId: String, isAdmin: Bool, type: String?) -> String { - let roomTable = RoomTable.name - let roomMemberTable = RoomMemberTable.name - let memberTable = MemberTable.name + private func constructMembers(_ db: Database, phoneIds: [String]) throws { + guard !phoneIds.isEmpty else { + self.admins = try DBMember.members(from: db, roomId: id, isAdmin: true) + self.members = try DBMember.members(from: db, roomId: id) + return + } - let roomIdColumn = "\(roomTable).\(RoomTable.Column.id.title)" - let roomMemberRoomIdColumn = "\(roomMemberTable).\(RoomMemberTable.Column.roomId.title)" + let members = try DBMember.members(from: db, roomId: id, phoneIds: phoneIds) - let memberIdColumn = "\(memberTable).\(MemberTable.Column.id.title)" - let rommMemberMemberIdColumn = "\(roomMemberTable).\(RoomMemberTable.Column.memberId.title)" - - let memberPhoneIdColumn = "\(memberTable).\(MemberTable.Column.phoneId.title)" + let statuses: [MemberStatus] = [.admin, .owner] + let statusStrings = statuses.map { $0.rawValue } - var sql = """ - select \(roomTable).* - from \(roomTable) - left join \(roomMemberTable) on \(roomIdColumn) == \(roomMemberRoomIdColumn) - left join \(memberTable) on \(memberIdColumn) == \(rommMemberMemberIdColumn) - where \(memberPhoneIdColumn) == '\(contactId)' - """ + self.admins = [] + self.members = [] - let memberStatusColumn = "\(memberTable).\(MemberTable.Column.status.title)" - if isAdmin { - sql.append(contentsOf: "and \(memberStatusColumn) in ('admin', 'owner')") + members.forEach { member in + if let status = member.status, statusStrings.contains(status) { + self.admins.append(member) + } else { + self.members.append(member) + } } - - let roomTypeColumn = "\(roomTable).\(RoomTable.Column.type.title)" - if let type = type { - sql.append(contentsOf: "and \(roomTypeColumn) == '\(type)'") - } - - return sql + } + + static func fetchDescs(_ db: Database, roomId: String) -> [DBDesc] { + return (try? descs(targetId: roomId, db: db)) ?? [] } @@ -296,30 +292,22 @@ class DBRoom: Record, DBExtendedModelProtocol { return try DBDesc.descs(targetId: targetId, targetType: .room, db: db) } - static func request(with args: RequestArgs) -> AnyTypedRequest { - + private static func request(with args: RequestArgs) -> AnyTypedRequest { let roomTable = RoomTable.name let messageTable = MessageTable.name - - let messageIdColumn = "\(roomTable).\(Column.messageId)" - let localIdColumn = "\(messageTable).\(Column.localId)" - - let rosterIdColumn = "\(roomTable).\(Column.rosterId)" - let typeColumn = "\(roomTable).\(Column.type)" - let unreadColumn = "\(roomTable).\(Column.unread)" - - let createdColumn = "\(messageTable).\(Column.created)" - - let unreadCondition = args.onlyUnread ? "and \(unreadColumn) > 0" : "" + let memberTable = MemberTable.name var sql = """ select \(roomTable).* - from \(roomTable) left join \(messageTable) - on \(messageIdColumn) == \(localIdColumn) - where \(rosterIdColumn) == \(args.rosterId) - and \(typeColumn) == '\(args.type)' - \(unreadCondition) - order by \(createdColumn) desc + from \(roomTable) + \(messageJoin(messageTable: messageTable, roomTable: roomTable)) + \(memberJoin(ownerId: args.ownerId, memberTable: memberTable, roomTable: roomTable)) + where + \(condition(rosterId: args.rosterId, roomTable: roomTable)) + and \(condition(type: args.type, roomTable: roomTable)) + \(condition(onlyUnread: args.onlyUnread, roomTable: roomTable)) + \(condition(ownerId: args.ownerId)) + \(orderBy(messageTable: messageTable)) """ if let limit = args.limit { @@ -332,6 +320,69 @@ class DBRoom: Record, DBExtendedModelProtocol { return SQLRequest(sql).asRequest(of: DBRoom.self) } + + private static func messageJoin(messageTable: String, roomTable: String) -> String { + let messageIdColumn = "\(roomTable).\(Column.messageId)" + let localIdColumn = "\(messageTable).\(Column.localId)" + + return "left join \(messageTable) on \(messageIdColumn) == \(localIdColumn)" + } + + private static func memberJoin(ownerId: String?, memberTable: String, roomTable: String) -> String { + guard ownerId != nil else { + return "" + } + + let roomMemberTable = RoomMemberTable.name + + let roomIdColumn = "\(roomTable).\(RoomTable.Column.id.title)" + let roomMemberRoomIdColumn = "\(roomMemberTable).\(RoomMemberTable.Column.roomId.title)" + + let memberIdColumn = "\(memberTable).\(MemberTable.Column.id.title)" + let rommMemberMemberIdColumn = "\(roomMemberTable).\(RoomMemberTable.Column.memberId.title)" + + + return """ + left join \(roomMemberTable) on \(roomIdColumn) == \(roomMemberRoomIdColumn) + left join \(memberTable) on \(memberIdColumn) == \(rommMemberMemberIdColumn) + """ + } + + private static func condition(rosterId: Int64, roomTable: String) -> String { + let rosterIdColumn = "\(roomTable).\(Column.rosterId)" + return "\(rosterIdColumn) == \(rosterId)" + } + + private static func condition(type: String, roomTable: String) -> String { + let typeColumn = "\(roomTable).\(Column.type)" + return "\(typeColumn) == '\(type)'" + } + + private static func condition(onlyUnread: Bool, roomTable: String) -> String { + let unreadColumn = "\(roomTable).\(Column.unread)" + return onlyUnread ? "and \(unreadColumn) > 0" : "" + } + + private static func condition(ownerId: String?) -> String { + guard let ownerId = ownerId else { + return "" + } + + let memberTable = MemberTable.name + + let memberPhoneIdColumn = "\(memberTable).\(MemberTable.Column.phoneId.title)" + let memberStatusColumn = "\(memberTable).\(MemberTable.Column.status.title)" + + return """ + and \(memberPhoneIdColumn) == '\(ownerId)'" + and \(memberStatusColumn) in ('admin', 'owner') + """ + } + + private static func orderBy(messageTable: String) -> String { + let createdColumn = "\(messageTable).\(Column.created)" + return "order by \(createdColumn) desc" + } } @@ -343,17 +394,20 @@ extension DBRoom { let rosterId: Int64 let type: String let onlyUnread: Bool + let ownerId: String? let limit: Int? let offset: Int? init(rosterId: Int64, type: String, onlyUnread: Bool = false, + ownerId: String? = nil, limit: Int? = nil, offset: Int? = nil) { self.rosterId = rosterId self.type = type self.onlyUnread = onlyUnread + self.ownerId = ownerId self.limit = limit self.offset = offset } diff --git a/Nynja/DB/Models/DBRoomMember.swift b/Nynja/DB/Models/DBRoomMember.swift index 95d150221..064920885 100644 --- a/Nynja/DB/Models/DBRoomMember.swift +++ b/Nynja/DB/Models/DBRoomMember.swift @@ -8,7 +8,7 @@ import GRDBCipher -class DBRoomMember: Record { +final class DBRoomMember: Record { var roomId: String var memberId: Int64 diff --git a/Nynja/DB/Models/DBRoster.swift b/Nynja/DB/Models/DBRoster.swift index a2203ad58..ecf212027 100644 --- a/Nynja/DB/Models/DBRoster.swift +++ b/Nynja/DB/Models/DBRoster.swift @@ -8,7 +8,7 @@ import GRDBCipher -class DBRoster: Record, DBModelProtocol { +final class DBRoster: Record, DBModel { var id: Int64 var names: String @@ -122,8 +122,7 @@ class DBRoster: Record, DBModelProtocol { } @discardableResult - func deleteAggragete(_ db: Database) throws -> Bool { + func deleteAggregate(_ db: Database) throws -> Bool { return try self.delete(db) - } - + } } diff --git a/Nynja/DB/Models/DBService.swift b/Nynja/DB/Models/DBService.swift index 0a6eafa86..d098688d1 100644 --- a/Nynja/DB/Models/DBService.swift +++ b/Nynja/DB/Models/DBService.swift @@ -8,7 +8,7 @@ import GRDBCipher -class DBService: Codable { +final class DBService: Codable { var id: String var type: String diff --git a/Nynja/DB/Models/DBStar.swift b/Nynja/DB/Models/DBStar.swift index e833f1856..3551483ed 100644 --- a/Nynja/DB/Models/DBStar.swift +++ b/Nynja/DB/Models/DBStar.swift @@ -9,11 +9,15 @@ import Foundation import GRDBCipher -final class DBStar: Record, DBExtendedModelProtocol { +final class DBStar: Record, DBExtendedModel { var id: Int64? var clientId: String? var rosterId: Int64? - var message: DBStarMessage? + var message: DBStarMessage? { + didSet { + messageID = message?.localId + } + } var messageID: String? var status: String? @@ -34,6 +38,7 @@ final class DBStar: Record, DBExtendedModelProtocol { self.status = StringAtom.string(star.status) if let msg = star.message { self.message = DBStarMessage(message: msg) + self.messageID = message?.localId } super.init() } @@ -75,11 +80,10 @@ final class DBStar: Record, DBExtendedModelProtocol { } - // MARK: - DBModelProtocol + // MARK: - DBModel func saveAggregate(_ db: Database) throws { try message?.saveAggregate(db) - messageID = message?.localId try self.save(db) } @@ -102,7 +106,7 @@ final class DBStar: Record, DBExtendedModelProtocol { return try DBStar .filter(Column.rosterId == rosterId) .filter(Column.status !== Star.Status.remove.rawValue) - .fetchAll(from: db) + .fetchAllConstructed(db) } } diff --git a/Nynja/DB/Models/DBStarAction.swift b/Nynja/DB/Models/DBStarAction.swift index db9561f7a..7a65d5b46 100644 --- a/Nynja/DB/Models/DBStarAction.swift +++ b/Nynja/DB/Models/DBStarAction.swift @@ -8,7 +8,7 @@ import GRDBCipher -final class DBStarAction: Record, DBModelProtocol { +final class DBStarAction: Record, DBModel { enum Action: String { case delete = "delete" } diff --git a/Nynja/DB/Models/DBStarMessage.swift b/Nynja/DB/Models/DBStarMessage.swift index 3ec6dfd44..e75a2c273 100644 --- a/Nynja/DB/Models/DBStarMessage.swift +++ b/Nynja/DB/Models/DBStarMessage.swift @@ -9,7 +9,7 @@ import Foundation import GRDBCipher -final class DBStarMessage: Record, DBModelProtocol { +final class DBStarMessage: Record, DBModel { var container: String? var feedId: Int64? @@ -68,7 +68,6 @@ final class DBStarMessage: Record, DBModelProtocol { self.status = message.statusString self.files = (message.files ?? []).compactMap { DBDesc(desc: $0, targetId: nil, targetType: .star) } -// self.repliedBy = message.repliedby?.myJoined(separator: DBMessage.separator) self.mentioned = message.mentioned?.joinedByComma() super.init() @@ -264,7 +263,9 @@ final class DBStarMessage: Record, DBModelProtocol { } } + // MARK: - Delete + @discardableResult func deleteAggregate(_ db: Database) throws -> Bool { try DBDesc.deleteAll(db, targetId: localId, targetType: .star) diff --git a/Nynja/DB/Models/DBStickerPack.swift b/Nynja/DB/Models/DBStickerPack.swift index 393c303bb..05d4e4c36 100644 --- a/Nynja/DB/Models/DBStickerPack.swift +++ b/Nynja/DB/Models/DBStickerPack.swift @@ -8,7 +8,7 @@ import GRDBCipher -final class DBStickerPack: Record, DBModelProtocol { +final class DBStickerPack: Record, DBModel { // MARK: - Fields @@ -74,7 +74,7 @@ final class DBStickerPack: Record, DBModelProtocol { } - // MARK: - DBModelProtocol + // MARK: - DBModel func saveAggregate(_ db: Database) throws { try save(db) diff --git a/Nynja/DB/Models/DBSyncFile.swift b/Nynja/DB/Models/DBSyncFile.swift index 943111b1f..bd291d4c0 100644 --- a/Nynja/DB/Models/DBSyncFile.swift +++ b/Nynja/DB/Models/DBSyncFile.swift @@ -8,7 +8,7 @@ import GRDBCipher -class DBSyncFile: Record { +final class DBSyncFile: Record { var id: Int? var serverLink: String? diff --git a/Nynja/DB/Protocols/DBModelConvertible.swift b/Nynja/DB/Protocols/DBModelConvertible.swift index 658c15362..b2e844f40 100644 --- a/Nynja/DB/Protocols/DBModelConvertible.swift +++ b/Nynja/DB/Protocols/DBModelConvertible.swift @@ -8,6 +8,6 @@ protocol DBModelConvertible { - var databaseModel: DBModelProtocol? { get } + var databaseModel: DBModel? { get } } diff --git a/Nynja/DB/QueryArgs.swift b/Nynja/DB/QueryArgs.swift index ea1417aea..31a59b4da 100644 --- a/Nynja/DB/QueryArgs.swift +++ b/Nynja/DB/QueryArgs.swift @@ -12,24 +12,25 @@ protocol QueryRequestMakeable { func makeRequest() -> Request } -protocol TypedQueryRequestMakeable { - associatedtype Model: DBModelProtocol - func makeTypedRequest() -> QueryInterfaceRequest -} - protocol QueryArgs: QueryRequestMakeable { var limit: Int? { get } var offset: Int? { get } var orderingTerms: [SQLOrderingTerm]? { get } } -typealias QueryFilter = (QueryInterfaceRequest) -> QueryInterfaceRequest +protocol TypedQueryRequestMakeable { + associatedtype Model: DBModel + func makeTypedRequest() -> QueryInterfaceRequest +} protocol CompositeQueryArgs: TypedQueryRequestMakeable { + // TODO: add filter func... var args: AnyQueryArgs { get } } -struct AnyQueryArgs: QueryArgs, TypedQueryRequestMakeable { +typealias QueryFilter = (QueryInterfaceRequest) -> QueryInterfaceRequest + +struct AnyQueryArgs: QueryArgs, TypedQueryRequestMakeable { let limit: Int? let offset: Int? let orderingTerms: [SQLOrderingTerm]? @@ -75,14 +76,3 @@ struct AnyQueryArgs: QueryArgs, TypedQueryRequestMakeable { return makeTypedRequest() } } - -class DAO { - let dbManager = StorageService.sharedInstance - - func fetch(with maker: U, kind: DBModelFetchingKind = .full) -> [T] where U.Model == T { - return dbManager.fetch { db in - return try maker.makeTypedRequest().fetchAll(kind: kind, from: db) - } - } -} - diff --git a/Nynja/DB/RosterRelatedQueryArgs.swift b/Nynja/DB/RosterRelatedQueryArgs.swift new file mode 100644 index 000000000..c71278066 --- /dev/null +++ b/Nynja/DB/RosterRelatedQueryArgs.swift @@ -0,0 +1,27 @@ +// +// RosterRelatedQueryArgs.swift +// Nynja +// +// Created by Volodymyr Hryhoriev on 11/13/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import GRDBCipher + +struct RosterRelatedQueryArgs: CompositeQueryArgs { + let rosterId: Int64 + let args: AnyQueryArgs + + init(rosterId: Int64) { + self.init(rosterId: rosterId, args: AnyQueryArgs()) + } + + init(rosterId: Int64, args: AnyQueryArgs) { + self.rosterId = rosterId + self.args = args + } + + func makeTypedRequest() -> QueryInterfaceRequest { + return args.makeTypedRequest().filter(Column.rosterId == rosterId) + } +} diff --git a/Nynja/DB/Tables/DAO.swift b/Nynja/DB/Tables/DAO.swift new file mode 100644 index 000000000..d9b7fe91c --- /dev/null +++ b/Nynja/DB/Tables/DAO.swift @@ -0,0 +1,18 @@ +// +// DAO.swift +// Nynja +// +// Created by Volodymyr Hryhoriev on 11/13/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + + +class DAO { + let dbManager = StorageService.sharedInstance + + func fetch(with maker: U, kind: DBModelFetchingKind = .constructed) -> [T] where U.Model == T { + return dbManager.fetch { db in + return try maker.makeTypedRequest().fetchAll(db, kind: kind) + } + } +} diff --git a/Nynja/DBJobQueryArgs.swift b/Nynja/DBJobQueryArgs.swift deleted file mode 100644 index 84bf392c6..000000000 --- a/Nynja/DBJobQueryArgs.swift +++ /dev/null @@ -1,20 +0,0 @@ -// -// DBJobQueryArgs.swift -// Nynja -// -// Created by Volodymyr Hryhoriev on 11/7/18. -// Copyright © 2018 TecSynt Solutions. All rights reserved. -// - -import GRDBCipher - -struct DBJobQueryArgs: CompositeQueryArgs { - let type: DBJob.JobType - let args: AnyQueryArgs - - func makeTypedRequest() -> QueryInterfaceRequest { - return AnyQueryArgs(args: args) - .filter { $0.filter(Column.type == self.type.rawValue) } - .makeTypedRequest() - } -} diff --git a/Nynja/DBManagerProtocol.swift b/Nynja/DBManagerProtocol.swift index cce9695e9..07c5c2434 100644 --- a/Nynja/DBManagerProtocol.swift +++ b/Nynja/DBManagerProtocol.swift @@ -27,9 +27,9 @@ protocol DBManagerProtocol { // MARK: - Perform Actions func perform(action: DatabaseAction, with model: DBModelConvertible) throws - func perform(action: DatabaseAction, with model: DBModelProtocol) throws + func perform(action: DatabaseAction, with model: DBModel) throws func perform(action: DatabaseAction, with models: [DBModelConvertible]) throws - func perform(action: DatabaseAction, with models: [DBModelProtocol]) throws + func perform(action: DatabaseAction, with models: [DBModel]) throws } diff --git a/Nynja/DBObserver.swift b/Nynja/DBObserver.swift index 366674dd8..37e28e9b0 100644 --- a/Nynja/DBObserver.swift +++ b/Nynja/DBObserver.swift @@ -8,7 +8,7 @@ import GRDBCipher -class DBObserver: StorageObserver, TransactionObserver { +final class DBObserver: StorageObserver, TransactionObserver { static let `default` = DBObserver() @@ -22,9 +22,9 @@ class DBObserver: StorageObserver, TransactionObserver { var subscribers: [SubscribeType: [StorageSubscriberReference]] = [:] private var allChanges: Dictionary = [:] - - private var tables: [Table.Type] { - return [ + + private let tableNames: [String] = { + let tables: [Table.Type] = [ JobTable.self, MessageTable.self, ContactTable.self, @@ -36,11 +36,8 @@ class DBObserver: StorageObserver, TransactionObserver { RecentStickerTable.self, StickerPackTable.self ] - } - - private var tableNames: [String] { return tables.map { $0.name } - } + }() // MARK: - TransactionObserver func observes(eventsOfKind eventKind: DatabaseEventKind) -> Bool { @@ -58,155 +55,219 @@ class DBObserver: StorageObserver, TransactionObserver { } func databaseDidChange(with event: DatabaseEvent) { - processingQueue.async { - var temp = self.allChanges[event.tableName] ?? [] - temp.append(ChangeInfo(event: event)) - self.allChanges[event.tableName] = temp + processingQueue.async { [unowned self] in + self.handleDidChange(with: event) } } + private func handleDidChange(with event: DatabaseEvent) { + var temp = allChanges[event.tableName] ?? [] + temp.append(ChangeInfo(event: event)) + allChanges[event.tableName] = temp + } + func databaseWillCommit() throws { - processingQueue.async { - self.allChanges.forEach { (tableName, changes) in - changes.forEach { info in - guard info.kind != .insert else { return } - info.oldValue = self.fetchValue(for: tableName, event: info.event) - } + processingQueue.async { [unowned self] in + self.handleWillCommit() + } + } + + private func handleWillCommit() { + allChanges.forEach { (tableName, changes) in + changes.forEach { info in + guard info.kind != .insert else { return } + info.oldValue = fetchValue(for: tableName, event: info.event) } } } func databaseDidCommit(_ db: Database) { - processingQueue.async { - self.allChanges.forEach { (tableName, changes) in - var storageChanges: [StorageChange] = [] + processingQueue.async { [unowned self] in + self.handleDidCommit(db) + } + } + + private func handleDidCommit(_ db: Database) { + allChanges.forEach { (tableName, changes) in + switch tableName { + case JobTable.name: + handleJobDidCommit(changes: changes) + case MessageTable.name: + handleMessageDidCommit(changes: changes) + case ContactTable.name: + handleContactDidCommit(changes: changes) + case RoomTable.name: + handleRoomDidCommit(changes: changes) + case MemberTable.name: + handleMemberDidCommit(changes: changes) + case RosterTable.name: + handleRosterDidCommit(changes: changes) + case StarTable.name: + handleStarDidCommit(changes: changes) + case ProfileTable.name: + handleProfileDidCommit(changes: changes) + case RecentStickerTable.name: + handleRecentStickerDidCommit(changes: changes) + case StickerPackTable.name: + handleStickerPackDidCommit(changes: changes) + default: + break + } + } + + clear() + } + + private func handleJobDidCommit(changes: [ChangeInfo]) { + var storageChanges: [StorageChange] = [] + + changes.forEach { info in + let change = storageChange(from: info) + storageChanges.append(change) + + let job = change.entity as? Job + notify(with: [change], type: .job(job?.id)) + } + + notify(with: storageChanges, type: .job(nil)) + } + + private func handleMessageDidCommit(changes: [ChangeInfo]) { + func handle(changes: [ChangeInfo]) { + changes.forEach { info in + let message = changedValue(from: info) as? Message - switch tableName { - case JobTable.name: - changes.forEach { info in - let change = self.storageChange(from: info) - storageChanges.append(change) - - let job = change.entity as? Job - self.notify(with: [change], type: .job(job?.id)) - } - - self.notify(with: storageChanges, type: .job(nil)) - case MessageTable.name: - func handle(changes: [ChangeInfo]) { - changes.forEach { info in - let message = self.changedValue(from: info) as? Message - - if let receiver = message?.p2pFeed?.opponentId { - self.notify(with: info.event, entity: message, type: .chat(receiver)) - } else if let roomId = message?.mucFeed?.name { - self.notify(with: info.event, entity: message, type: .chat(roomId)) - } - - self.notify(with: info.event, entity: message, type: .reply(message?.id)) - } - } - - if changes.count == 2, - case let firstInfo = changes[0], - case let secondInfo = changes[1], - let firstMessage = self.changedValue(from: firstInfo) as? Message, - let secondMessage = self.changedValue(from: secondInfo) as? Message, - case let isFirstReply = firstMessage.isReply && firstMessage.linkedId == secondMessage.id, - case let isSecondReply = secondMessage.isReply && secondMessage.linkedId == firstMessage.id, - isFirstReply || isSecondReply { - handle(changes: [firstInfo]) - handle(changes: [secondInfo]) - } else if changes.count > 1, let info = changes.first, let message = self.changedValue(from: info) as? Message { - if let receiver = message.p2pFeed?.opponentId { - self.notify(with: [], type: .chat(receiver)) - } else if let roomId = message.mucFeed?.name { - self.notify(with: [], type: .chat(roomId)) - } - } else { - handle(changes: changes) - } - case ContactTable.name: - if changes.count == 1, let info = changes.first { - let contact = self.changedValue(from: info) as? DBContact - self.notify(with: info.event, entity: contact, type: .contact(nil)) - } else { - self.notify(with: [], type: .contact(nil)) - } - - changes.forEach { info in - let contact = self.changedValue(from: info) as? DBContact - self.notify(with: info.event, entity: contact, type: .contact(contact?.phoneId)) - } - case RoomTable.name: - changes.forEach { info in - let change = self.storageChange(from: info) - storageChanges.append(change) - - let room = change.entity as? DBRoom - self.notify(with: [change], type: .room(room?.id)) - } - - self.notify(with: storageChanges, type: .room(nil)) - case MemberTable.name: - changes.forEach { info in - let member = self.changedValue(from: info) as? DBMember - self.notify(with: info.event, entity: member, type: .member((member?.feed as? DBMuc)?.name)) - } - case RosterTable.name: - changes.forEach { info in - let change = self.storageChange(from: info) - storageChanges.append(change) - - let roster = self.changedValue(from: info) as? DBRoster - self.notify(with: [change], type: .roster(roster?.id)) - } - - self.notify(with: storageChanges, type: .roster(nil)) - case StarTable.name: - changes.forEach { info in - let change = self.storageChange(from: info) - storageChanges.append(change) - - let star = self.changedValue(from: info) as? DBStar - self.notify(with: [change], type: .star(star?.clientId)) - } - - self.notify(with: storageChanges, type: .star(nil)) - case ProfileTable.name: - changes.forEach { info in - let profile = self.changedValue(from: info) as? DBProfile - self.notify(with: info.event, entity: profile, type: .profile) - } - case RecentStickerTable.name: - changes.forEach { info in - let change = self.storageChange(from: info) - storageChanges.append(change) - - let recentSticker = self.changedValue(from: info) as? DBRecentSticker - self.notify(with: [change], type: .recentSticker(recentSticker?.id)) - } - self.notify(with: storageChanges, type: .recentSticker(nil)) - case StickerPackTable.name: - changes.forEach { info in - let change = self.storageChange(from: info) - storageChanges.append(change) - - let stickerPack = self.changedValue(from: info) as? StickerPack - self.notify(with: [change], type: .stickerPack(stickerPack?.id)) - } - self.notify(with: storageChanges, type: .stickerPack(nil)) - default: - break + if let receiver = message?.p2pFeed?.opponentId { + notify(with: info.event, entity: message, type: .chat(receiver)) + } else if let roomId = message?.mucFeed?.name { + notify(with: info.event, entity: message, type: .chat(roomId)) } + + notify(with: info.event, entity: message, type: .reply(message?.id)) } + } + + if changes.count == 2, + case let firstInfo = changes[0], + case let secondInfo = changes[1], + let firstMessage = changedValue(from: firstInfo) as? Message, + let secondMessage = changedValue(from: secondInfo) as? Message, + case let isFirstReply = firstMessage.isReply && firstMessage.linkedId == secondMessage.id, + case let isSecondReply = secondMessage.isReply && secondMessage.linkedId == firstMessage.id, + isFirstReply || isSecondReply { + handle(changes: [firstInfo]) + handle(changes: [secondInfo]) + } else if changes.count > 1, let info = changes.first, let message = changedValue(from: info) as? Message { + if let receiver = message.p2pFeed?.opponentId { + notify(with: [], type: .chat(receiver)) + } else if let roomId = message.mucFeed?.name { + notify(with: [], type: .chat(roomId)) + } + } else { + handle(changes: changes) + } + } + + private func handleContactDidCommit(changes: [ChangeInfo]) { + if changes.count == 1, let info = changes.first { + let contact = changedValue(from: info) as? DBContact + notify(with: info.event, entity: contact, type: .contact(nil)) + } else { + notify(with: [], type: .contact(nil)) + } + + changes.forEach { info in + let contact = changedValue(from: info) as? DBContact + notify(with: info.event, entity: contact, type: .contact(contact?.phoneId)) + } + } + + private func handleRoomDidCommit(changes: [ChangeInfo]) { + var storageChanges: [StorageChange] = [] + + changes.forEach { info in + let change = storageChange(from: info) + storageChanges.append(change) - self.clear() + let room = change.entity as? DBRoom + notify(with: [change], type: .room(room?.id)) + } + + notify(with: storageChanges, type: .room(nil)) + } + + private func handleMemberDidCommit(changes: [ChangeInfo]) { + changes.forEach { info in + let member = changedValue(from: info) as? DBMember + notify(with: info.event, entity: member, type: .member((member?.feed as? DBMuc)?.name)) } } + private func handleRosterDidCommit(changes: [ChangeInfo]) { + var storageChanges: [StorageChange] = [] + + changes.forEach { info in + let change = storageChange(from: info) + storageChanges.append(change) + + let roster = changedValue(from: info) as? DBRoster + notify(with: [change], type: .roster(roster?.id)) + } + + notify(with: storageChanges, type: .roster(nil)) + } + + private func handleStarDidCommit(changes: [ChangeInfo]) { + var storageChanges: [StorageChange] = [] + + changes.forEach { info in + let change = storageChange(from: info) + storageChanges.append(change) + + let star = changedValue(from: info) as? DBStar + notify(with: [change], type: .star(star?.clientId)) + } + + notify(with: storageChanges, type: .star(nil)) + } + + private func handleProfileDidCommit(changes: [ChangeInfo]) { + changes.forEach { info in + let profile = changedValue(from: info) as? DBProfile + notify(with: info.event, entity: profile, type: .profile) + } + } + + private func handleRecentStickerDidCommit(changes: [ChangeInfo]) { + var storageChanges: [StorageChange] = [] + + changes.forEach { info in + let change = storageChange(from: info) + storageChanges.append(change) + + let recentSticker = changedValue(from: info) as? DBRecentSticker + notify(with: [change], type: .recentSticker(recentSticker?.id)) + } + notify(with: storageChanges, type: .recentSticker(nil)) + } + + private func handleStickerPackDidCommit(changes: [ChangeInfo]) { + var storageChanges: [StorageChange] = [] + + changes.forEach { info in + let change = storageChange(from: info) + storageChanges.append(change) + + let stickerPack = changedValue(from: info) as? StickerPack + notify(with: [change], type: .stickerPack(stickerPack?.id)) + } + notify(with: storageChanges, type: .stickerPack(nil)) + } + func databaseDidRollback(_ db: Database) { - clear() + processingQueue.async { + self.clear() + } } // MARK: - Private diff --git a/Nynja/DatabaseManager.swift b/Nynja/DatabaseManager.swift index 315b4c233..83c0dc9d7 100644 --- a/Nynja/DatabaseManager.swift +++ b/Nynja/DatabaseManager.swift @@ -191,7 +191,7 @@ final class DatabaseManager: DBManagerProtocol { try perform(action: action, with: dbModel) } - func perform(action: DatabaseAction, with model: DBModelProtocol) throws { + func perform(action: DatabaseAction, with model: DBModel) throws { try perform(action: action, with: [model]) } @@ -200,7 +200,7 @@ final class DatabaseManager: DBManagerProtocol { try perform(action: action, with: models) } - func perform(action: DatabaseAction, with models: [DBModelProtocol]) throws { + func perform(action: DatabaseAction, with models: [DBModel]) throws { try writeInTransaction { db in try models.forEach { model in switch action { diff --git a/Nynja/Extensions/Models/Contact/Contact+DB.swift b/Nynja/Extensions/Models/Contact/Contact+DB.swift index 47b678451..0aff9a2eb 100644 --- a/Nynja/Extensions/Models/Contact/Contact+DB.swift +++ b/Nynja/Extensions/Models/Contact/Contact+DB.swift @@ -38,7 +38,7 @@ extension Contact { extension Contact: DBModelConvertible { - var databaseModel: DBModelProtocol? { + var databaseModel: DBModel? { return DBContact(contact: self, rosterId: StorageService.sharedInstance.rosterId) } diff --git a/Nynja/Extensions/Models/Job+DB.swift b/Nynja/Extensions/Models/Job+DB.swift index d01e59b57..e3b94670f 100644 --- a/Nynja/Extensions/Models/Job+DB.swift +++ b/Nynja/Extensions/Models/Job+DB.swift @@ -8,7 +8,7 @@ extension Job: DBModelConvertible { - var databaseModel: DBModelProtocol? { + var databaseModel: DBModel? { return DBJob(job: self) } diff --git a/Nynja/Extensions/Models/Member/MemberExtension.swift b/Nynja/Extensions/Models/Member/MemberExtension.swift index 5af8936a1..c6cfa1f5d 100644 --- a/Nynja/Extensions/Models/Member/MemberExtension.swift +++ b/Nynja/Extensions/Models/Member/MemberExtension.swift @@ -93,7 +93,7 @@ extension Member { } extension Member: DBModelConvertible { - var databaseModel: DBModelProtocol? { + var databaseModel: DBModel? { return DBMember(member: self) } } diff --git a/Nynja/Extensions/Models/Message/Message+DB.swift b/Nynja/Extensions/Models/Message/Message+DB.swift index 7f4df71cf..87770aade 100644 --- a/Nynja/Extensions/Models/Message/Message+DB.swift +++ b/Nynja/Extensions/Models/Message/Message+DB.swift @@ -121,7 +121,7 @@ extension Message: DBModelConvertible { return DBMessage(message: self) } - var databaseModel: DBModelProtocol? { + var databaseModel: DBModel? { return dbMessage } } diff --git a/Nynja/Extensions/Models/ProfileExtension.swift b/Nynja/Extensions/Models/ProfileExtension.swift index b2d001483..6b633fe57 100644 --- a/Nynja/Extensions/Models/ProfileExtension.swift +++ b/Nynja/Extensions/Models/ProfileExtension.swift @@ -34,7 +34,7 @@ extension Profile { extension Profile: DBModelConvertible { - var databaseModel: DBModelProtocol? { + var databaseModel: DBModel? { return DBProfile(profile: self) } } diff --git a/Nynja/Extensions/Models/Room/Room+DB.swift b/Nynja/Extensions/Models/Room/Room+DB.swift index 7bb5c5631..d229126a0 100644 --- a/Nynja/Extensions/Models/Room/Room+DB.swift +++ b/Nynja/Extensions/Models/Room/Room+DB.swift @@ -46,7 +46,7 @@ extension Room: DBModelConvertible { return DBRoom(room: self, rosterId: StorageService.sharedInstance.rosterId) } - var databaseModel: DBModelProtocol? { + var databaseModel: DBModel? { return dbRoom } } diff --git a/Nynja/Extensions/Models/Roster+DB.swift b/Nynja/Extensions/Models/Roster+DB.swift index f7485cc7a..075433a68 100644 --- a/Nynja/Extensions/Models/Roster+DB.swift +++ b/Nynja/Extensions/Models/Roster+DB.swift @@ -30,7 +30,7 @@ extension Roster { // MARK: - DBModelConvertible extension Roster: DBModelConvertible { - var databaseModel: DBModelProtocol? { + var databaseModel: DBModel? { guard let profileId = StorageService.sharedInstance.phone else { return nil } diff --git a/Nynja/Extensions/Models/RosterExtension.swift b/Nynja/Extensions/Models/RosterExtension.swift index 1f6df0177..ccb1e2c3b 100644 --- a/Nynja/Extensions/Models/RosterExtension.swift +++ b/Nynja/Extensions/Models/RosterExtension.swift @@ -6,7 +6,7 @@ // Copyright © 2017 TecSynt Solutions. All rights reserved. // -extension Roster: FullNameable { +extension Roster: FullNameRepresentable { // Returns self contact var myContact: Contact? { diff --git a/Nynja/Extensions/Models/StarExtension.swift b/Nynja/Extensions/Models/StarExtension.swift index 95cbaf53a..3317259a7 100644 --- a/Nynja/Extensions/Models/StarExtension.swift +++ b/Nynja/Extensions/Models/StarExtension.swift @@ -116,7 +116,7 @@ extension Star { extension Star: DBModelConvertible { - var databaseModel: DBModelProtocol? { + var databaseModel: DBModel? { return DBStar(star: self) } diff --git a/Nynja/Extensions/Models/StickerPack/StickerPack+DB.swift b/Nynja/Extensions/Models/StickerPack/StickerPack+DB.swift index 34ce67e99..6b46e3f5a 100644 --- a/Nynja/Extensions/Models/StickerPack/StickerPack+DB.swift +++ b/Nynja/Extensions/Models/StickerPack/StickerPack+DB.swift @@ -23,7 +23,7 @@ extension StickerPack: DBModelConvertible { downloaded = stickerPack.downloaded } - var databaseModel: DBModelProtocol? { + var databaseModel: DBModel? { return DBStickerPack(stickerPack: self) } } diff --git a/Nynja/FullNameable.swift b/Nynja/FullNameRepresentable.swift similarity index 79% rename from Nynja/FullNameable.swift rename to Nynja/FullNameRepresentable.swift index 41687f81e..78560d73c 100644 --- a/Nynja/FullNameable.swift +++ b/Nynja/FullNameRepresentable.swift @@ -1,18 +1,18 @@ // -// FullNameable.swift +// FullNameRepresentable.swift // Nynja // // Created by Volodymyr Hryhoriev on 11/1/18. // Copyright © 2018 TecSynt Solutions. All rights reserved. // -protocol FullNameable { +protocol FullNameRepresentable { var names: String? { get set } var surnames: String? { get set } var fullName: String? { get } } -extension FullNameable { +extension FullNameRepresentable { var fullName: String? { if let name = self.names, let surname = self.surnames { @@ -29,4 +29,8 @@ extension FullNameable { return nil } + + var hasName: Bool { + return names != nil && names != "" + } } diff --git a/Nynja/JobDAO.swift b/Nynja/JobDAO.swift index 89694a89f..485286a44 100644 --- a/Nynja/JobDAO.swift +++ b/Nynja/JobDAO.swift @@ -40,7 +40,7 @@ class JobDAO: JobDAOProtocol { }.serverModels } - static func fetchJobs(with args: DBJobQueryArgs) -> [Job] { + static func fetchJobs(with args: DBJob.QueryArgs) -> [Job] { return DAO().fetch(with: args).serverModels } diff --git a/Nynja/Modules/Call/CallCreatorMediator.swift b/Nynja/Modules/Call/CallCreatorMediator.swift index e82b7d1e3..c8832cf34 100644 --- a/Nynja/Modules/Call/CallCreatorMediator.swift +++ b/Nynja/Modules/Call/CallCreatorMediator.swift @@ -71,5 +71,4 @@ class CallCreatorMediator { return false } - } diff --git a/Nynja/Modules/ChannelsList/Interactor/ChannelsListInteractor.swift b/Nynja/Modules/ChannelsList/Interactor/ChannelsListInteractor.swift index 69b2c0e4f..dea84c7ea 100644 --- a/Nynja/Modules/ChannelsList/Interactor/ChannelsListInteractor.swift +++ b/Nynja/Modules/ChannelsList/Interactor/ChannelsListInteractor.swift @@ -13,24 +13,19 @@ final class ChannelsListInteractor: BaseInteractor, ChannelsListInteractorInputP weak var presenter: ChannelsListInteractorOutputProtocol! private var conversationsProvider: ConversationsProviding + private var storageService: StorageService private var mode: ChannelsListMode private var channels: [Room] = [] private var searchText: String = "" - // MARK: - InitializeInjectable - - struct Dependencies { - let presenter: ChannelsListInteractorOutputProtocol - let conversationsProvider: ConversationsProviding - - let mode: ChannelsListMode - } + // MARK: - Init required init(dependencies: Dependencies) { presenter = dependencies.presenter conversationsProvider = dependencies.conversationsProvider + storageService = dependencies.storageService mode = dependencies.mode } @@ -79,10 +74,16 @@ final class ChannelsListInteractor: BaseInteractor, ChannelsListInteractorInputP //MARK: - Private private func loadChannels() { + guard let rosterId = storageService.rosterId else { + return + } + + let args = ConversationsProviding.FetchingArgs(rosterId: rosterId) + if mode == .all { - channels = conversationsProvider.fetchChannels() - } else { - channels = conversationsProvider.fetchMyChannels() + channels = conversationsProvider.fetchChannels(with: args) + } else if let phoneId = StorageService.sharedInstance.phoneId { + channels = conversationsProvider.fetchMyChannels(with: args, ownerId: phoneId) } } @@ -121,3 +122,16 @@ final class ChannelsListInteractor: BaseInteractor, ChannelsListInteractorInputP } } + +// MARK: - InitializableInjectable + +extension ChannelsListInteractor { + + struct Dependencies { + let presenter: ChannelsListInteractorOutputProtocol + let conversationsProvider: ConversationsProviding + let storageService: StorageService + + let mode: ChannelsListMode + } +} diff --git a/Nynja/Modules/ChannelsList/WireFrame/ChannelsListWireFrame.swift b/Nynja/Modules/ChannelsList/WireFrame/ChannelsListWireFrame.swift index 19077e274..53436fff2 100644 --- a/Nynja/Modules/ChannelsList/WireFrame/ChannelsListWireFrame.swift +++ b/Nynja/Modules/ChannelsList/WireFrame/ChannelsListWireFrame.swift @@ -25,6 +25,7 @@ final class ChannelsListWireFrame: ChannelsListWireFrameProtocol { let serviceFactory = ServiceFactory() let messagePayloadParser = serviceFactory.makeMessagePayloadParser() let conversationProvider = serviceFactory.makeConversationsProvider() + let storageService = serviceFactory.makeStorageService() // Module components let view = ChannelsListViewController() @@ -32,6 +33,7 @@ final class ChannelsListWireFrame: ChannelsListWireFrameProtocol { let interactor = ChannelsListInteractor( dependencies: .init(presenter: presenter, conversationsProvider: conversationProvider, + storageService: storageService, mode: mode)) // Connecting diff --git a/Nynja/Modules/ChatsList/Interactor/ChatsListInteractor.swift b/Nynja/Modules/ChatsList/Interactor/ChatsListInteractor.swift index 3f92bbe83..169875381 100644 --- a/Nynja/Modules/ChatsList/Interactor/ChatsListInteractor.swift +++ b/Nynja/Modules/ChatsList/Interactor/ChatsListInteractor.swift @@ -64,7 +64,7 @@ class ChatsListInteractor: BaseInteractor, ChatsListInteractorInputProtocol, Ini let contact = Contact(contact: dbContact) chats = updateChatsList(with: contact) } else { - guard let args = rosterId.map({ ConversationsProvidingFetchingArgs(rosterId: $0) }) else { + guard let args = rosterId.map({ ConversationsProviding.FetchingArgs(rosterId: $0) }) else { return } @@ -78,7 +78,7 @@ class ChatsListInteractor: BaseInteractor, ChatsListInteractorInputProtocol, Ini //MARK: - Private private func fetchChats() { - guard let args = rosterId.map({ ConversationsProvidingFetchingArgs(rosterId: $0) }) else { + guard let args = rosterId.map({ ConversationsProviding.FetchingArgs(rosterId: $0) }) else { return } diff --git a/Nynja/Modules/Favorites/Presenter/FavoritesPresenter.swift b/Nynja/Modules/Favorites/Presenter/FavoritesPresenter.swift index 35f397cf4..f1661ea99 100644 --- a/Nynja/Modules/Favorites/Presenter/FavoritesPresenter.swift +++ b/Nynja/Modules/Favorites/Presenter/FavoritesPresenter.swift @@ -22,7 +22,7 @@ class FavoritesPresenter: BasePresenter, FavoritesPresenterProtocol, FavoritesIn } func getStarsSuccess(stars: [Star]) { - self.view.setup(stars: stars) + view.setup(stars: stars) } func showed() { diff --git a/Nynja/Modules/ForwardSelector/Interactor/ForwardSelectorInteractor.swift b/Nynja/Modules/ForwardSelector/Interactor/ForwardSelectorInteractor.swift index 898ef5ea7..532b3c548 100644 --- a/Nynja/Modules/ForwardSelector/Interactor/ForwardSelectorInteractor.swift +++ b/Nynja/Modules/ForwardSelector/Interactor/ForwardSelectorInteractor.swift @@ -33,6 +33,7 @@ final class ForwardSelectorInteractor: BaseInteractor, ForwardSelectorInteractor weak var presenter: ForwardSelectorInteractorOutputProtocol! private var mqttService: MQTTService! private var storageService: StorageService! + private var conversationsProviding: ConversationsProviding! private var contacts: [ForwardTarget]? private var groups: [ForwardTarget]? @@ -132,12 +133,19 @@ final class ForwardSelectorInteractor: BaseInteractor, ForwardSelectorInteractor } else { let selectedRooms = targets?.groups ?? [] - let groups = RoomDAO.fetchRooms(kind: .group) - .map { room in - let isSelected = selectedRooms.contains(where: { $0.id == room.id }) - return ForwardTarget(content: .room(room), isSelected: isSelected) - } - .sorted(by: sortComparator) + let groups: [ForwardTarget] + if let rosterId = storageService.rosterId { + let args = ConversationsProviding.FetchingArgs(rosterId: rosterId) + groups = conversationsProviding + .fetchGroups(with: args) + .map { room in + let isSelected = selectedRooms.contains(where: { $0.id == room.id }) + return ForwardTarget(content: .room(room), isSelected: isSelected) + } + .sorted(by: sortComparator) + } else { + groups = [] + } self.groups = groups completion(groups) @@ -182,11 +190,13 @@ extension ForwardSelectorInteractor { let presenter: ForwardSelectorInteractorOutputProtocol let mqttService: MQTTService let storageService: StorageService + let conversationsProviding: ConversationsProviding } func inject(dependencies: Dependencies) { presenter = dependencies.presenter mqttService = dependencies.mqttService storageService = dependencies.storageService + conversationsProviding = dependencies.conversationsProviding } } diff --git a/Nynja/Modules/ForwardSelector/WireFrame/ForwardSelectorWireFrame.swift b/Nynja/Modules/ForwardSelector/WireFrame/ForwardSelectorWireFrame.swift index 6df4be8d4..33e951de6 100644 --- a/Nynja/Modules/ForwardSelector/WireFrame/ForwardSelectorWireFrame.swift +++ b/Nynja/Modules/ForwardSelector/WireFrame/ForwardSelectorWireFrame.swift @@ -24,7 +24,13 @@ final class ForwardSelectorWireFrame: ForwardSelectorWireFrameProtocol { let view = ForwardSelectorViewController() let presenter = ForwardSelectorPresenter() - let interactorDependencies = ForwardSelectorInteractor.Dependencies(presenter: presenter, mqttService: MQTTService.sharedInstance, storageService: StorageService.sharedInstance) + let serviceFactory = ServiceFactory() + + let interactorDependencies = ForwardSelectorInteractor.Dependencies( + presenter: presenter, + mqttService: serviceFactory.makeMQTTService(), + storageService: serviceFactory.makeStorageService(), + conversationsProviding: serviceFactory.makeConversationsProvider()) let interactor = ForwardSelectorInteractor(mode: mode) // Connecting diff --git a/Nynja/Modules/GroupsList/Interactor/GroupsListInteractor.swift b/Nynja/Modules/GroupsList/Interactor/GroupsListInteractor.swift index e2a7ad016..c44ceff9f 100644 --- a/Nynja/Modules/GroupsList/Interactor/GroupsListInteractor.swift +++ b/Nynja/Modules/GroupsList/Interactor/GroupsListInteractor.swift @@ -70,7 +70,7 @@ class GroupsListInteractor: BaseInteractor, GroupsListInteractorInputProtocol, I applyFilter(with: searchText) } } else { - guard let args = rosterId.map({ ConversationsProvidingFetchingArgs(rosterId: $0) }) else { + guard let args = rosterId.map({ ConversationsProviding.FetchingArgs(rosterId: $0) }) else { return } @@ -83,7 +83,7 @@ class GroupsListInteractor: BaseInteractor, GroupsListInteractorInputProtocol, I //MARK: - Private private func fetchGroups() { - guard let args = rosterId.map({ ConversationsProvidingFetchingArgs(rosterId: $0) }) else { + guard let args = rosterId.map({ ConversationsProviding.FetchingArgs(rosterId: $0) }) else { return } diff --git a/Nynja/Modules/Main/View/MainViewController+Recents.swift b/Nynja/Modules/Main/View/MainViewController+Recents.swift index 752cb8147..3e7a8cb5f 100644 --- a/Nynja/Modules/Main/View/MainViewController+Recents.swift +++ b/Nynja/Modules/Main/View/MainViewController+Recents.swift @@ -54,18 +54,20 @@ extension MainViewController { showRecentConversation(indexPath: indexPath, conversations: conversations) } - private func fetchConversation(using fetcher: (ConversationsProvidingFetchingArgs) -> [ChatModel]) -> [ChatModel] { + func showRecentChannels(indexPath: IndexPath?) { + let conversations = fetchConversation( + using: ServiceFactory().makeConversationsProvider().fetchChannels(with:)) + showRecentConversation(indexPath: indexPath, conversations: conversations) + } + + private func fetchConversation(using fetcher: (ConversationsProviding.FetchingArgs) -> [ChatModel]) -> [ChatModel] { guard let rosterId = StorageService.sharedInstance.rosterId else { return [] } - let args = ConversationsProvidingFetchingArgs(rosterId: rosterId) + let args = ConversationsProviding.FetchingArgs(rosterId: rosterId) return fetcher(args) } - - func showRecentChannels(indexPath: IndexPath?) { - showRecentConversation(indexPath: indexPath, conversations: ServiceFactory().makeConversationsProvider().fetchChannels()) - } private func showRecentConversation(indexPath: IndexPath?, conversations: [ChatModel]) { let info = makeChatsTopLevelInfo(with: indexPath, conversations: conversations) @@ -99,5 +101,4 @@ extension MainViewController { } } } - } diff --git a/Nynja/Modules/Profile/Interactor/HomeDataProvider/HomeDataProviderImpl.swift b/Nynja/Modules/Profile/Interactor/HomeDataProvider/HomeDataProviderImpl.swift index 8322d2897..876ff8795 100644 --- a/Nynja/Modules/Profile/Interactor/HomeDataProvider/HomeDataProviderImpl.swift +++ b/Nynja/Modules/Profile/Interactor/HomeDataProvider/HomeDataProviderImpl.swift @@ -8,7 +8,7 @@ import GRDBCipher -class HomeDataProviderImpl: HomeDataProvider, InitializeInjectable { +final class HomeDataProviderImpl: HomeDataProvider, InitializeInjectable { let conversationsProviding: ConversationsProviding let contactsProviding: ContactsProviding let limit: Int @@ -34,14 +34,14 @@ class HomeDataProviderImpl: HomeDataProvider, InitializeInjectable { private func fetchConversations( rosterId: Int64, - fetcher: (ConversationsProvidingFetchingArgs) -> [T]) -> [T] { + fetcher: (ConversationsProviding.FetchingArgs) -> [T]) -> [T] { let args = makeConversationsProvidingFetchingArgs(rosterId: rosterId) return fetcher(args) } - private func makeConversationsProvidingFetchingArgs(rosterId: Int64) -> ConversationsProvidingFetchingArgs { - return ConversationsProvidingFetchingArgs( + private func makeConversationsProvidingFetchingArgs(rosterId: Int64) -> ConversationsProviding.FetchingArgs { + return ConversationsProviding.FetchingArgs( rosterId: rosterId, onlyUnread: true, limit: limit) @@ -67,14 +67,16 @@ class HomeDataProviderImpl: HomeDataProvider, InitializeInjectable { // MARK: - Stars func fetchStars(rosterId: Int64) -> [Star] { - let args = makeStarDAOFetchingArgs(rosterId: rosterId) + let args = makeStarQueryArgs(rosterId: rosterId) return StarDAO.fetchStars(with: args) } - private func makeStarDAOFetchingArgs(rosterId: Int64) -> StarDAOFetchingArgs { - return StarDAOFetchingArgs(rosterId: rosterId, - limit: limit, - orderColumns: [Column.created.desc]) + private func makeStarQueryArgs(rosterId: Int64) -> RosterRelatedQueryArgs { + return RosterRelatedQueryArgs( + rosterId: rosterId, + args: .init( + limit: limit, + orderingTerms: [Column.created.desc])) } @@ -89,8 +91,8 @@ class HomeDataProviderImpl: HomeDataProvider, InitializeInjectable { .map { ScheduledMessage(job: $0) } } - private func makeDBJobQueryArgs() -> DBJobQueryArgs { - return DBJobQueryArgs( + private func makeDBJobQueryArgs() -> DBJob.QueryArgs { + return DBJob.QueryArgs( type: .schedule, args: .init( limit: limit, diff --git a/Nynja/RoomDAO.swift b/Nynja/RoomDAO.swift index 118794be0..e4ef5c136 100644 --- a/Nynja/RoomDAO.swift +++ b/Nynja/RoomDAO.swift @@ -37,37 +37,19 @@ class RoomDAO: RoomDAOProtocol { // MARK: -- Rooms - static func fetchRooms(kind: Room.Kind? = nil) -> [Room] { - guard let rosterId = StorageService.sharedInstance.rosterId else { - return [] - } - - return dbManager.fetch { db -> [DBRoom] in - return try DBRoom.rooms(db, rosterId: rosterId, type: kind?.rawValue) - }.serverModels - } - static func fetchRooms(with ids: [String], kind: Room.Kind? = nil) -> [Room] { return dbManager.fetch { db -> [DBRoom] in return try DBRoom.rooms(db, ids: ids, type: kind?.rawValue) }.serverModels } - static func fetchUserRooms(with contactId: String, isAdmin: Bool, kind: Room.Kind?) -> [Room] { - return dbManager.fetch { db in - return try DBRoom.rooms(db, contactId: contactId, isAdmin: isAdmin, type: kind?.rawValue) - }.serverModels - } - - static func fetchRooms(with args: DBRoom.RequestArgs) -> [Room] { return dbManager.fetch { db in - return try DBRoom - .request(with: args) - .fetchAll(from: db) + return try DBRoom.rooms(db, args: args) }.serverModels } + // MARK: -- Reader static func fetchReader(for roomId: String, kind: ReaderKind) -> Int64? { diff --git a/Nynja/RoomDAOProtocol.swift b/Nynja/RoomDAOProtocol.swift index b08c3b38d..eaa3f1db0 100644 --- a/Nynja/RoomDAOProtocol.swift +++ b/Nynja/RoomDAOProtocol.swift @@ -19,10 +19,7 @@ protocol RoomDAOProtocol: DAOProtocol { // MARK: -- Rooms - static func fetchRooms(kind: Room.Kind?) -> [Room] static func fetchRooms(with ids: [String], kind: Room.Kind?) -> [Room] - static func fetchUserRooms(with contactId: String, isAdmin: Bool, kind: Room.Kind?) -> [Room] - static func fetchRooms(with args: DBRoom.RequestArgs) -> [Room] diff --git a/Nynja/Services/Debug/LogService/LogService/LogService.swift b/Nynja/Services/Debug/LogService/LogService/LogService.swift index ea209b8f4..375961628 100644 --- a/Nynja/Services/Debug/LogService/LogService/LogService.swift +++ b/Nynja/Services/Debug/LogService/LogService/LogService.swift @@ -9,7 +9,7 @@ import Foundation import os.log -class LogService { +final class LogService { private static let logWriter: LogWriterProtocol? = ServiceFactory().makeLogWriter() diff --git a/Nynja/Services/Debug/LogService/LogWriter.swift b/Nynja/Services/Debug/LogService/LogWriter.swift index a254422d3..01f2ce91e 100644 --- a/Nynja/Services/Debug/LogService/LogWriter.swift +++ b/Nynja/Services/Debug/LogService/LogWriter.swift @@ -8,7 +8,7 @@ import Foundation -class LogWriter: LogWriterProtocol { +final class LogWriter: LogWriterProtocol { static let shared: LogWriter? = LogWriter() diff --git a/Nynja/Services/HandleServices/ProfileHandler.swift b/Nynja/Services/HandleServices/ProfileHandler.swift index d3a900ce3..a1fa7952f 100644 --- a/Nynja/Services/HandleServices/ProfileHandler.swift +++ b/Nynja/Services/HandleServices/ProfileHandler.swift @@ -154,7 +154,7 @@ class ProfileHandler: BaseHandler { } private static func configureNynjaCommunicatorService(with roster: Roster) { - guard let _ = roster.phoneId else { + guard roster.phoneId != nil else { return } diff --git a/Nynja/Services/HandleServices/RoomHandler.swift b/Nynja/Services/HandleServices/RoomHandler.swift index 6193bb931..8fb79ae80 100644 --- a/Nynja/Services/HandleServices/RoomHandler.swift +++ b/Nynja/Services/HandleServices/RoomHandler.swift @@ -205,7 +205,7 @@ class RoomHandler: BaseHandler { oldRoom.readers = room.readers oldRoom.originalStatus = .get - oldRoom.unread = 0 + oldRoom.unread = 0 // TODO: need to check. try? storageService.perform(action: .save, with: oldRoom) } } diff --git a/Nynja/Services/StorageService.swift b/Nynja/Services/StorageService.swift index d003ee29f..ec1b3ea6b 100644 --- a/Nynja/Services/StorageService.swift +++ b/Nynja/Services/StorageService.swift @@ -134,7 +134,7 @@ extension StorageService: DBManagerProtocol { try databaseManager.perform(action: action, with: model) } - func perform(action: DatabaseAction, with model: DBModelProtocol) throws { + func perform(action: DatabaseAction, with model: DBModel) throws { try databaseManager.perform(action: action, with: model) } @@ -142,7 +142,7 @@ extension StorageService: DBManagerProtocol { try databaseManager.perform(action: action, with: models) } - func perform(action: DatabaseAction, with models: [DBModelProtocol]) throws { + func perform(action: DatabaseAction, with models: [DBModel]) throws { try databaseManager.perform(action: action, with: models) } diff --git a/Nynja/StarDAO.swift b/Nynja/StarDAO.swift index cc40a15ca..44bac7df4 100644 --- a/Nynja/StarDAO.swift +++ b/Nynja/StarDAO.swift @@ -16,7 +16,7 @@ class StarDAO: StarDAOProtocol { return dbManager.fetch { db in return try DBStar .filter(Column.rowID == rowId) - .fetchOne(from: db) + .fetchOneConstructed(db) } } @@ -24,16 +24,17 @@ class StarDAO: StarDAOProtocol { return dbManager.fetch { db in return try DBStar .filter(Column.clientId == clientId) - .fetchOne(from: db) + .fetchOneConstructed(db) } } static func fetchStars(rosterId: Int64) -> [String: Star] { let stars = dbManager.fetch { db -> [DBStar] in - let args = StarDAOFetchingArgs(rosterId: rosterId) - return try makeQuery(with: args) + let args = RosterRelatedQueryArgs(rosterId: rosterId) + return try args + .makeTypedRequest() .filter(Column.status !== Star.Status.remove.rawValue) - .fetchAll(from: db) + .fetchAllConstructed(db) } var result: [String: Star] = [:] @@ -47,26 +48,21 @@ class StarDAO: StarDAOProtocol { return result } - static func fetchStars(with args: StarDAOFetchingArgs) -> [Star] { + static func fetchStars(with args: RosterRelatedQueryArgs) -> [Star] { return dbManager.fetch { db in - return try makeQuery(with: args) + return try args + .makeTypedRequest() .filter(Column.status !== Star.Status.remove.rawValue) - .fetchAll(from: db) + .fetchAllConstructed(db) }.serverModels } static func fetchUndeliveredStars(rosterId: Int64) -> [Star] { return dbManager.fetch { db -> [DBStar] in - let args = StarDAOFetchingArgs(rosterId: rosterId) - return try makeQuery(with: args) + return try RosterRelatedQueryArgs(rosterId: rosterId) + .makeTypedRequest() .filter(Column.id == nil) - .fetchAll(from: db) + .fetchAllConstructed(db) }.serverModels } - - private static func makeQuery(with args: StarDAOFetchingArgs) -> QueryInterfaceRequest { - return DBStar.filter(Column.rosterId == args.rosterId) - .performIfValueExists(args.limit) { $0.limit($1, offset: args.offset) } - .performIfValueExists(args.orderColumns) { $0.order($1) } - } } diff --git a/Nynja/StarDAOFetchingArgs.swift b/Nynja/StarDAOFetchingArgs.swift deleted file mode 100644 index efa8bb74e..000000000 --- a/Nynja/StarDAOFetchingArgs.swift +++ /dev/null @@ -1,28 +0,0 @@ -// -// StarDAOFetchingArgs.swift -// Nynja -// -// Created by Volodymyr Hryhoriev on 11/7/18. -// Copyright © 2018 TecSynt Solutions. All rights reserved. -// - -import GRDBCipher - -struct StarDAOFetchingArgs { - let rosterId: Int64 - let limit: Int? - let offset: Int? - let orderColumns: [SQLOrderingTerm]? - - init( - rosterId: Int64, - limit: Int? = nil, - offset: Int? = nil, - orderColumns: [SQLOrderingTerm]? = nil) { - - self.rosterId = rosterId - self.limit = limit - self.offset = offset - self.orderColumns = orderColumns - } -} diff --git a/Shared/Library/Extensions/Models/Contact/ContactExtension.swift b/Shared/Library/Extensions/Models/Contact/ContactExtension.swift index 594fdbc4d..5be2afe2a 100644 --- a/Shared/Library/Extensions/Models/Contact/ContactExtension.swift +++ b/Shared/Library/Extensions/Models/Contact/ContactExtension.swift @@ -8,7 +8,7 @@ import UIKit -extension Contact: FullNameable { +extension Contact: FullNameRepresentable { enum Status: String { case get case request @@ -25,10 +25,6 @@ extension Contact: FullNameable { var alias: String? { return fullName } - - var hasName: Bool { - return names != nil && names != "" - } /// Returns the first letter of name. If name doesn't exist, it will return the first letter of surname. /// If surname doesn't exist too, it will return empty string. -- GitLab From 5211192fff2a7f3187527045bdf9c9d43460d79a Mon Sep 17 00:00:00 2001 From: Volodymyr Hryhoriev Date: Wed, 14 Nov 2018 10:24:18 +0200 Subject: [PATCH 48/68] Remove extra `DAO` file reference. --- Nynja.xcodeproj/project.pbxproj | 8 ++++---- Nynja.xcworkspace/contents.xcworkspacedata | 3 --- Nynja/DB/{Tables => }/DAO.swift | 0 Nynja/Generated/LocalizableConstants.swift | 2 +- 4 files changed, 5 insertions(+), 8 deletions(-) rename Nynja/DB/{Tables => }/DAO.swift (100%) diff --git a/Nynja.xcodeproj/project.pbxproj b/Nynja.xcodeproj/project.pbxproj index 5d6a13bb8..743b21911 100644 --- a/Nynja.xcodeproj/project.pbxproj +++ b/Nynja.xcodeproj/project.pbxproj @@ -647,6 +647,7 @@ 4B7E93382170D1BC001558CF /* RootNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B7E93372170D1BC001558CF /* RootNavigationController.swift */; }; 4B7E933B2170D410001558CF /* ForwardSelectorWireFrame.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B7E933A2170D410001558CF /* ForwardSelectorWireFrame.swift */; }; 4B7E933E2170D4FF001558CF /* ServiceFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B7E933D2170D4FF001558CF /* ServiceFactory.swift */; }; + 4B86C562219C12840006A192 /* DAO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B86C561219C12840006A192 /* DAO.swift */; }; 4B87712F2192F6940014AD09 /* ColumnDefinitionExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B87712E2192F6940014AD09 /* ColumnDefinitionExtension.swift */; }; 4B877131219312570014AD09 /* ColumnExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B877130219312570014AD09 /* ColumnExtension.swift */; }; 4B877133219314D50014AD09 /* TypedRequestExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B877132219314D50014AD09 /* TypedRequestExtension.swift */; }; @@ -689,7 +690,6 @@ 4BB0EFBB2151347900704136 /* AlertImageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB0EFB82151347900704136 /* AlertImageViewController.swift */; }; 4BB0EFBC2151347900704136 /* AlertImageViewControllerConstraints.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB0EFB92151347900704136 /* AlertImageViewControllerConstraints.swift */; }; 4BB0EFBD2151347900704136 /* AlertManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB0EFBA2151347900704136 /* AlertManager.swift */; }; - 4BB35E21219AF42B0007C18E /* DAO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB35E20219AF42A0007C18E /* DAO.swift */; }; 4BB35E23219AF46E0007C18E /* RosterRelatedQueryArgs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB35E22219AF46E0007C18E /* RosterRelatedQueryArgs.swift */; }; 4BC8B38D2191AC360086DC6C /* ContactsProvidingFetchingArgs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BC8B38C2191AC360086DC6C /* ContactsProvidingFetchingArgs.swift */; }; 4BD53BF4202C8BCA00569C1A /* AVURLAsset+Duration.swift in Sources */ = {isa = PBXBuildFile; fileRef = E77FBDDC1FFE828400BDB255 /* AVURLAsset+Duration.swift */; }; @@ -2928,6 +2928,7 @@ 4B7E93372170D1BC001558CF /* RootNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootNavigationController.swift; sourceTree = ""; }; 4B7E933A2170D410001558CF /* ForwardSelectorWireFrame.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ForwardSelectorWireFrame.swift; sourceTree = ""; }; 4B7E933D2170D4FF001558CF /* ServiceFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServiceFactory.swift; sourceTree = ""; }; + 4B86C561219C12840006A192 /* DAO.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DAO.swift; sourceTree = ""; }; 4B87712E2192F6940014AD09 /* ColumnDefinitionExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColumnDefinitionExtension.swift; sourceTree = ""; }; 4B877130219312570014AD09 /* ColumnExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColumnExtension.swift; sourceTree = ""; }; 4B877132219314D50014AD09 /* TypedRequestExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TypedRequestExtension.swift; sourceTree = ""; }; @@ -2964,7 +2965,6 @@ 4BB0EFB82151347900704136 /* AlertImageViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlertImageViewController.swift; sourceTree = ""; }; 4BB0EFB92151347900704136 /* AlertImageViewControllerConstraints.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlertImageViewControllerConstraints.swift; sourceTree = ""; }; 4BB0EFBA2151347900704136 /* AlertManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlertManager.swift; sourceTree = ""; }; - 4BB35E20219AF42A0007C18E /* DAO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = DAO.swift; path = /Users/volodymyrhryhoriev/Desktop/Work/Nynja/Nynja/DB/Tables/DAO.swift; sourceTree = ""; }; 4BB35E22219AF46E0007C18E /* RosterRelatedQueryArgs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RosterRelatedQueryArgs.swift; sourceTree = ""; }; 4BC8B38C2191AC360086DC6C /* ContactsProvidingFetchingArgs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactsProvidingFetchingArgs.swift; sourceTree = ""; }; 4BDC7E60203492CA00BCD381 /* TopSwipable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TopSwipable.swift; sourceTree = ""; }; @@ -11969,11 +11969,11 @@ E7417E961FBED8FD00E5C124 /* DB */ = { isa = PBXGroup; children = ( + 4B86C561219C12840006A192 /* DAO.swift */, E764628D1FCD68020091FC2E /* Protocols */, E74FD69B1FC5D04A00656611 /* Extensions */, E73CEF0F1FC2F52200246066 /* Models */, E7417E971FBED90600E5C124 /* Tables */, - 4BB35E20219AF42A0007C18E /* DAO.swift */, 4B87713A219328780014AD09 /* QueryArgs.swift */, 4BB35E22219AF46E0007C18E /* RosterRelatedQueryArgs.swift */, ); @@ -15267,6 +15267,7 @@ 267BE2AF1FE13AB600C47E18 /* ParticipantsPresenter.swift in Sources */, 2648C4112069B52100863614 /* ChangeNumberCodeView.swift in Sources */, 35F2DA611F73CAD400777920 /* NotificationManager.swift in Sources */, + 4B86C562219C12840006A192 /* DAO.swift in Sources */, 26C1A3ED2031D3030009F7F0 /* OtherUserContainerViewController.swift in Sources */, FB16E79920EFAFA8009FA203 /* Money.swift in Sources */, BA982E458F95A7A5AB4A8A73 /* TutorialViewController.swift in Sources */, @@ -16574,7 +16575,6 @@ 8520040720D4F436007C0036 /* StickerPreviewConfig.swift in Sources */, F1607B1D20B20F7800BDF60A /* GridView.swift in Sources */, F11786F320AC7A6E007A9A1B /* VideoPreviewView.swift in Sources */, - 4BB35E21219AF42B0007C18E /* DAO.swift in Sources */, FEA655ED2167777E00B44029 /* TransferHistoryHeaderView.swift in Sources */, 26534B25210B4BE70003B9BC /* DBMessage+Extension.swift in Sources */, 2F7C7F7837BDE6F5767A3A8C /* GroupStorageViewController.swift in Sources */, diff --git a/Nynja.xcworkspace/contents.xcworkspacedata b/Nynja.xcworkspace/contents.xcworkspacedata index 070d68e56..13dce7e5a 100644 --- a/Nynja.xcworkspace/contents.xcworkspacedata +++ b/Nynja.xcworkspace/contents.xcworkspacedata @@ -1,9 +1,6 @@ - - diff --git a/Nynja/DB/Tables/DAO.swift b/Nynja/DB/DAO.swift similarity index 100% rename from Nynja/DB/Tables/DAO.swift rename to Nynja/DB/DAO.swift diff --git a/Nynja/Generated/LocalizableConstants.swift b/Nynja/Generated/LocalizableConstants.swift index 361ff5bfb..6d8e67dfc 100644 --- a/Nynja/Generated/LocalizableConstants.swift +++ b/Nynja/Generated/LocalizableConstants.swift @@ -1122,7 +1122,7 @@ internal extension String { static var tpTranslation: String { return localizable.tr("Localizable", "tp_translation") } /// Translation failed. Please try again. static var tpTranslationFailed: String { return localizable.tr("Localizable", "tp_translation_failed") } - /// Original and translated messages language is the same. + /// Original and translated message are the same language. static var tpTranslationLanguageIsEqual: String { return localizable.tr("Localizable", "tp_translation_language_is_equal") } /// Transcribe with Google static var transcribe: String { return localizable.tr("Localizable", "transcribe") } -- GitLab From 81102a14374d645bea88687c721bd1e7aaf5c116 Mon Sep 17 00:00:00 2001 From: Volodymyr Hryhoriev Date: Wed, 14 Nov 2018 10:42:40 +0200 Subject: [PATCH 49/68] Notify `MQTTServiceDelegate` in the main queue. --- Nynja/Services/MQTT/MQTTService.swift | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Nynja/Services/MQTT/MQTTService.swift b/Nynja/Services/MQTT/MQTTService.swift index 0a0e3c3dd..a6cd65499 100644 --- a/Nynja/Services/MQTT/MQTTService.swift +++ b/Nynja/Services/MQTT/MQTTService.swift @@ -210,7 +210,7 @@ final class MQTTService: NSObject, CocoaMQTTDelegate, ConnectionServiceDelegate func mqtt(_ mqtt: CocoaMQTT, didReceiveMessage message: CocoaMQTTMessage, id: UInt16) { printMessage(msg: message, isSent: false) - queuePool.receivingQueue.async { [weak self] in + queuePool.receivingQueue.async { HandlerService.handle(response: message) } } @@ -260,11 +260,13 @@ final class MQTTService: NSObject, CocoaMQTTDelegate, ConnectionServiceDelegate } } - private func notifySubscribers(with eventClosure: (MQTTServiceDelegate) -> Void) { + private func notifySubscribers(with eventClosure: @escaping (MQTTServiceDelegate) -> Void) { queuePool.subscribersQueue.sync { [unowned self] in self.subscribers.forEach { (weak) in if let delegate = weak.value as? MQTTServiceDelegate { - eventClosure(delegate) + dispatchAsyncMain { + eventClosure(delegate) + } } } } -- GitLab From 4f2bb51601bf9063d185ab22f3e6a924e34ff5aa Mon Sep 17 00:00:00 2001 From: Volodymyr Hryhoriev Date: Wed, 14 Nov 2018 10:57:29 +0200 Subject: [PATCH 50/68] Make small refactoring in `StorageService` and `UserInfoImpl`. --- Nynja/Services/StorageService.swift | 8 ++++---- Nynja/UserInfoImpl.swift | 10 ---------- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/Nynja/Services/StorageService.swift b/Nynja/Services/StorageService.swift index 9fa0f73bc..cd0bcfe97 100644 --- a/Nynja/Services/StorageService.swift +++ b/Nynja/Services/StorageService.swift @@ -10,7 +10,9 @@ import Foundation import GRDBCipher import CryptoSwift -//MARK: - The Game is began +// MARK: - The Game is began + +/// Thread-safe class StorageService { private let passphraseKey = KeychainService.Keys.dataBasePassphrase @@ -22,8 +24,6 @@ class StorageService { dependencies: .init(userDefaults: userDefaults)) }() - private let countriesProvider = CountriesProvider() - #if !SHARE_EXTENSION private let databaseManager = DatabaseManager() @@ -37,7 +37,7 @@ class StorageService { // MARK: - Properties lazy var countries: [CountryModel] = { - return countriesProvider.fetchCountries() + return CountriesProvider().fetchCountries() }() /// It is used only for debug purposes. diff --git a/Nynja/UserInfoImpl.swift b/Nynja/UserInfoImpl.swift index 87dd926df..6f81d898d 100644 --- a/Nynja/UserInfoImpl.swift +++ b/Nynja/UserInfoImpl.swift @@ -83,16 +83,6 @@ class UserInfoImpl: UserInfo, InitializeInjectable { set { set(newValue, forId: .wasRun) } } - func setupAuth(clientId: String, token: String) { - self.clientId = clientId - self.token = token - } - - func setupUserInfo(from roster: Roster) { - phone = roster.phone - rosterId = roster.id - } - private func value(forId id: UserIdentifiers) -> T? { return userDefaults?.value(forKey: id.rawValue) as? T } -- GitLab From 23602d80d77d0f33309a799c03c5eac1b8423420 Mon Sep 17 00:00:00 2001 From: Volodymyr Hryhoriev Date: Wed, 14 Nov 2018 11:02:27 +0200 Subject: [PATCH 51/68] Remove unused `SearchHandler`, make small refactoring of others. --- Nynja.xcodeproj/project.pbxproj | 4 --- Nynja/ExtendedStarHandler.swift | 8 +++-- Nynja/HandlerFactory.swift | 2 -- Nynja/Handlers.swift | 1 - Nynja/JobHandler.swift | 8 +++-- .../HandleServices/ContactHandler.swift | 4 +-- .../HandleServices/RosterHandler.swift | 6 +++- .../HandleServices/SearchHandler.swift | 29 ------------------- .../Services/HandleServices/StarHandler.swift | 8 +++-- Nynja/Services/MQTT/MQTTService.swift | 1 + 10 files changed, 26 insertions(+), 45 deletions(-) delete mode 100644 Nynja/Services/HandleServices/SearchHandler.swift diff --git a/Nynja.xcodeproj/project.pbxproj b/Nynja.xcodeproj/project.pbxproj index c800ae3ff..6c974490e 100644 --- a/Nynja.xcodeproj/project.pbxproj +++ b/Nynja.xcodeproj/project.pbxproj @@ -141,7 +141,6 @@ 26053123212741C2002E1CF1 /* LogOutputWireFrame.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26053122212741C2002E1CF1 /* LogOutputWireFrame.swift */; }; 2605312921298BEF002E1CF1 /* Logoutputcell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2605312821298BEF002E1CF1 /* Logoutputcell.swift */; }; 2605312B21299198002E1CF1 /* LogOutputDS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2605312A21299198002E1CF1 /* LogOutputDS.swift */; }; - 260552A61F9E1CD100D68DE6 /* SearchHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 260552A51F9E1CD100D68DE6 /* SearchHandler.swift */; }; 260629712056EF2800CB8F65 /* LinksCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 260629702056EF2800CB8F65 /* LinksCell.swift */; }; 2606F3BC20BFE20500CF7F15 /* MessageInteractor+Translation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2606F3BB20BFE20400CF7F15 /* MessageInteractor+Translation.swift */; }; 2610D4642076516900E6E2B2 /* Array+Feature.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26DCB25320692237001EF0AB /* Array+Feature.swift */; }; @@ -2538,7 +2537,6 @@ 26053122212741C2002E1CF1 /* LogOutputWireFrame.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogOutputWireFrame.swift; sourceTree = ""; }; 2605312821298BEF002E1CF1 /* Logoutputcell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logoutputcell.swift; sourceTree = ""; }; 2605312A21299198002E1CF1 /* LogOutputDS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogOutputDS.swift; sourceTree = ""; }; - 260552A51F9E1CD100D68DE6 /* SearchHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SearchHandler.swift; path = Services/HandleServices/SearchHandler.swift; sourceTree = ""; }; 260629702056EF2800CB8F65 /* LinksCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinksCell.swift; sourceTree = ""; }; 2606F3BB20BFE20400CF7F15 /* MessageInteractor+Translation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "MessageInteractor+Translation.swift"; sourceTree = ""; }; 2611CEF62182090900FFD4DD /* LogWriterProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogWriterProtocol.swift; sourceTree = ""; }; @@ -5969,7 +5967,6 @@ 3A771CA91F191B38008D968A /* ProfileHandler.swift */, 3A2374D81F262A1600701045 /* ContactHandler.swift */, 3A237BCC1F30E5D400C42B6E /* RosterHandler.swift */, - 260552A51F9E1CD100D68DE6 /* SearchHandler.swift */, 26FA420F201821B400E6F6EC /* StarHandler.swift */, 0008E91A20333A38003E316E /* JobHandler.swift */, 855EF424202CCADB00541BE3 /* ExtendedStarHandler.swift */, @@ -15729,7 +15726,6 @@ 8584C90F20920F3C001A0BBB /* StickerGridCellModel.swift in Sources */, A42D51A4206A361400EEB952 /* Feature.swift in Sources */, 26D6D227212EDA6600EA2419 /* ConvertMessageDAOProtocol.swift in Sources */, - 260552A61F9E1CD100D68DE6 /* SearchHandler.swift in Sources */, F1607B1F20B21A9D00BDF60A /* CameraViewController.swift in Sources */, 853E595B20D71E6C007799B9 /* StickerPack+DB.swift in Sources */, A4CE80C720C95EE000400713 /* CollectionState.swift in Sources */, diff --git a/Nynja/ExtendedStarHandler.swift b/Nynja/ExtendedStarHandler.swift index 4ea3c670b..184b5d6a0 100644 --- a/Nynja/ExtendedStarHandler.swift +++ b/Nynja/ExtendedStarHandler.swift @@ -10,11 +10,15 @@ import Foundation final class ExtendedStarHandler: BaseHandler { + private static var storageService: StorageService { + return .sharedInstance + } + static func executeHandle(data: BertList) { let extendedStars = data.elements.compactMap { get_ExtendedStar().parse(bert: $0) as? ExtendedStar } let stars = extendedStars.compactMap { extendedStar -> DBStar? in - guard let rosterId = StorageService.sharedInstance.rosterId else { return nil } + guard let rosterId = storageService.rosterId else { return nil } if let star = extendedStar.star, StarActionDAO.containsDeleteAction(for: star) { star.starStatus = .remove @@ -22,6 +26,6 @@ final class ExtendedStarHandler: BaseHandler { return DBStar(extendedStar: extendedStar, rosterId: rosterId) } - try? StorageService.sharedInstance.perform(action: .save, with: stars) + try? storageService.perform(action: .save, with: stars) } } diff --git a/Nynja/HandlerFactory.swift b/Nynja/HandlerFactory.swift index c94fbcbee..6313b5934 100644 --- a/Nynja/HandlerFactory.swift +++ b/Nynja/HandlerFactory.swift @@ -22,8 +22,6 @@ final class HandlerFactory { return HistoryHandler.self case .message: return MessageHandler.self - case .search: - return SearchHandler.self case .room: return RoomHandler.self case .member: diff --git a/Nynja/Handlers.swift b/Nynja/Handlers.swift index 92f9b5494..fc256fa32 100644 --- a/Nynja/Handlers.swift +++ b/Nynja/Handlers.swift @@ -13,7 +13,6 @@ enum Handlers: String { case contact case history case message - case search case room case member case typing diff --git a/Nynja/JobHandler.swift b/Nynja/JobHandler.swift index 0ea1a1720..32343977d 100644 --- a/Nynja/JobHandler.swift +++ b/Nynja/JobHandler.swift @@ -8,6 +8,10 @@ final class JobHandler: BaseHandler { + private static var storageService: StorageService { + return .sharedInstance + } + static func executeHandle(data: BertTuple) { guard let job = get_Job().parse(bert: data) as? Job, let status = StringAtom.string(job.status) else { @@ -16,9 +20,9 @@ final class JobHandler: BaseHandler { switch status { case "pending", "update": - try? StorageService.sharedInstance.perform(action: .save, with: job) + try? storageService.perform(action: .save, with: job) case "complete", "delete": - try? StorageService.sharedInstance.perform(action: .delete, with: job) + try? storageService.perform(action: .delete, with: job) default: break } diff --git a/Nynja/Services/HandleServices/ContactHandler.swift b/Nynja/Services/HandleServices/ContactHandler.swift index 42104e794..42e1e46bc 100644 --- a/Nynja/Services/HandleServices/ContactHandler.swift +++ b/Nynja/Services/HandleServices/ContactHandler.swift @@ -10,11 +10,11 @@ final class ContactHandler: BaseHandler { // MARK: - Dependencies - static var storageService: StorageService { + private static var storageService: StorageService { return .sharedInstance } - static var notificationManager: NotificationManager { + private static var notificationManager: NotificationManager { return .shared } diff --git a/Nynja/Services/HandleServices/RosterHandler.swift b/Nynja/Services/HandleServices/RosterHandler.swift index ea054dddd..2596cd69d 100644 --- a/Nynja/Services/HandleServices/RosterHandler.swift +++ b/Nynja/Services/HandleServices/RosterHandler.swift @@ -8,6 +8,10 @@ final class RosterHandler: BaseHandler { + private static var storageService: StorageService { + return .sharedInstance + } + static func executeHandle(data: BertTuple) { guard let roster = get_Roster().parse(bert: data) as? Roster, let status = (roster.status as? StringAtom)?.string else { @@ -15,7 +19,7 @@ final class RosterHandler: BaseHandler { } if status == "patch" || status == "nick" { - try? StorageService.sharedInstance.perform(action: .save, with: roster) + try? storageService.perform(action: .save, with: roster) } } } diff --git a/Nynja/Services/HandleServices/SearchHandler.swift b/Nynja/Services/HandleServices/SearchHandler.swift deleted file mode 100644 index 5889095bd..000000000 --- a/Nynja/Services/HandleServices/SearchHandler.swift +++ /dev/null @@ -1,29 +0,0 @@ -// -// SearchHandler.swift -// Nynja -// -// Created by Anton Makarov on 23.10.17. -// Copyright © 2017 TecSynt Solutions. All rights reserved. -// - -final class SearchHandler: BaseHandler { - - static func executeHandle(data: BertTuple) { - guard let search = get_Search().parse(bert: data) as? Search, - let ref = search.ref, - let refType = SearchModelReference(rawValue: ref) else { - return - } - - switch refType { - case .PHONE: - break - case .PHONEBOOK: - break - case .QRCODE: - break - case .USERNAME: - break - } - } -} diff --git a/Nynja/Services/HandleServices/StarHandler.swift b/Nynja/Services/HandleServices/StarHandler.swift index 22d335905..1b830f2ad 100644 --- a/Nynja/Services/HandleServices/StarHandler.swift +++ b/Nynja/Services/HandleServices/StarHandler.swift @@ -8,6 +8,10 @@ final class StarHandler: BaseHandler { + private static var storageService: StorageService { + return .sharedInstance + } + static func executeHandle(data: BertTuple) { guard let star = get_Star().parse(bert: data) as? Star, let status = star.starStatus else { @@ -18,10 +22,10 @@ final class StarHandler: BaseHandler { switch status { case .add: if !StarActionDAO.containsDeleteAction(for: star) { - try StorageService.sharedInstance.perform(action: .save, with: star) + try storageService.perform(action: .save, with: star) } case .remove: - try StorageService.sharedInstance.perform(action: .delete, with: star) + try storageService.perform(action: .delete, with: star) } } catch { } } diff --git a/Nynja/Services/MQTT/MQTTService.swift b/Nynja/Services/MQTT/MQTTService.swift index a6cd65499..23fd5dae7 100644 --- a/Nynja/Services/MQTT/MQTTService.swift +++ b/Nynja/Services/MQTT/MQTTService.swift @@ -9,6 +9,7 @@ import Foundation import CocoaMQTT +/// Performs works in the several serial background queues final class MQTTService: NSObject, CocoaMQTTDelegate, ConnectionServiceDelegate { private var mqtt: CocoaMQTT? -- GitLab From c45e52e65432eff3fcbc1fc18c35bd1c535748a3 Mon Sep 17 00:00:00 2001 From: Volodymyr Hryhoriev Date: Wed, 14 Nov 2018 11:59:41 +0200 Subject: [PATCH 52/68] Remove thread-safity from `StorageService` because `UserDefaults` is thread-safe. --- Nynja/Services/StorageService.swift | 86 +++++++---------------------- 1 file changed, 19 insertions(+), 67 deletions(-) diff --git a/Nynja/Services/StorageService.swift b/Nynja/Services/StorageService.swift index cd0bcfe97..fc79d14d2 100644 --- a/Nynja/Services/StorageService.swift +++ b/Nynja/Services/StorageService.swift @@ -116,15 +116,11 @@ class StorageService { func clearStorage() { isolationQueue.sync { [weak self] in - self?._clearStorage() + #if !SHARE_EXTENSION + self?.databaseManager.clear() + #endif + self?.keychain.clear() } - } - - private func _clearStorage() { - #if !SHARE_EXTENSION - databaseManager.clear() - #endif - keychain.clear() dropUserInfo() } } @@ -172,86 +168,42 @@ extension StorageService: DBManagerProtocol { extension StorageService: UserInfo { var token: String? { - get { - return value(forKeyPath: \UserInfo.token) } - set { - isolationQueue.async { [weak self] in - self?.userInfo.token = newValue - } - } + get { return userInfo.token } + set { userInfo.token = newValue } } var tokenData: Data? { - get { - var data: Data? - isolationQueue.sync { [weak self] in - data = self?.userInfo.tokenData - } - return data - } + get { return userInfo.tokenData } } var pushToken: String? { - get { return value(forKeyPath: \UserInfo.pushToken) } - set { - isolationQueue.async { [weak self] in - self?.userInfo.pushToken = newValue - } - } + get { return userInfo.pushToken } + set { userInfo.pushToken = newValue } } var phone: String? { - get { return value(forKeyPath: \UserInfo.phone) } - set { - isolationQueue.async { [weak self] in - self?.userInfo.phone = newValue - } - } + get { return userInfo.phone } + set { userInfo.phone = newValue } } var rosterId: Int64? { - get { return value(forKeyPath: \UserInfo.rosterId) } - set { - isolationQueue.async { [weak self] in - self?.userInfo.rosterId = newValue - } - } + get { return userInfo.rosterId } + set { userInfo.rosterId = newValue } } var clientId: String? { - get { return value(forKeyPath: \UserInfo.clientId) } - set { - isolationQueue.async { [weak self] in - self?.userInfo.clientId = newValue - } - } + get { return userInfo.clientId } + set { userInfo.clientId = newValue } } var wasLogined: Bool { - get { return value(forKeyPath: \UserInfo.wasLogined) } - set { - isolationQueue.async { [weak self] in - self?.userInfo.wasLogined = newValue - } - } + get { return userInfo.wasLogined } + set { userInfo.wasLogined = newValue } } var wasRun: Bool { - get { return value(forKeyPath: \UserInfo.wasRun) } - set { - isolationQueue.async { [weak self] in - self?.userInfo.wasRun = newValue - } - } - } - - private func value(forKeyPath keyPath: WritableKeyPath) -> T { - var value: T? - isolationQueue.sync { [weak self] in - guard let `self` = self else { return } - value = self.userInfo[keyPath: keyPath] - } - return value! + get { return userInfo.wasRun } + set { userInfo.wasRun = newValue } } func setupAuth(clientId: String, token: String) { -- GitLab From f2fce0fb91f2efa2911153c311c0f302a62447ad Mon Sep 17 00:00:00 2001 From: Volodymyr Hryhoriev Date: Wed, 14 Nov 2018 13:09:56 +0200 Subject: [PATCH 53/68] Make working with subscribers thread-safe inside `UserSettingsService`. --- .../Service/UserSettingsService.swift | 44 ++++++++++++++----- Nynja/UserSettings/Settings/Theme.swift | 4 +- .../UserSettings/Settings/WheelPosition.swift | 4 +- 3 files changed, 37 insertions(+), 15 deletions(-) diff --git a/Nynja/UserSettings/Service/UserSettingsService.swift b/Nynja/UserSettings/Service/UserSettingsService.swift index 5ae2229d1..3a82323dd 100644 --- a/Nynja/UserSettings/Service/UserSettingsService.swift +++ b/Nynja/UserSettings/Service/UserSettingsService.swift @@ -46,13 +46,26 @@ final class UserSettingsService { static let theme = "theme" static let notifications = "notifications" } + + + // MARK: - Singleton + static let shared = UserSettingsService() - private let storage = UserDefaults.standard + private init() {} + + + // MARK: - Queues + + private let subscribersQueue = DispatchQueue(label: String.label(withSuffix: "user-settings-service.subscribers-queue")) // MARK: - Properties + private var storage: UserDefaults { + return .standard + } + private lazy var settings: UserSettings = { return UserSettings(wheelPosition: wheelPosition, theme: theme, notifications: notifications) }() @@ -108,23 +121,32 @@ final class UserSettingsService { private var subscribers: [UserSettingsSubscriber] = [] func addSubscriber(_ object: AnyObject, for options: UserSettingsUpdateOptions = .all, handler: @escaping UserSettingsChangeHandler) { - guard !subscribers.contains(where: { $0.object.value === object }) else { - return + subscribersQueue.sync { [unowned self] in + guard !self.subscribers.contains(where: { $0.object.value === object }) else { + return + } + let subscriber = UserSettingsSubscriber(object: object, options: options, handler: handler) + self.subscribers.append(subscriber) + + // notify subscriber with current ui settings. + subscriber.handler(settings) } - let subscriber = UserSettingsSubscriber(object: object, options: options, handler: handler) - subscribers.append(subscriber) - - // notify subscriber with current ui settings. - subscriber.handler(settings) } func removeSubscriber(_ object: AnyObject) { - subscribers = subscribers.filter { $0.object.value != nil && $0.object.value !== object } + subscribersQueue.sync { [unowned self] in + subscribers = self.subscribers.filter { $0.object.value != nil && $0.object.value !== object } + } } private func notifySubscribers(with settings: UserSettings, options: UserSettingsUpdateOptions) { - for subscriber in subscribers where subscriber.options.contains(options) { - subscriber.handler(settings) + subscribersQueue.async() { [unowned self] in + for subscriber in self.subscribers where subscriber.options.contains(options) { + + dispatchAsyncMain { + subscriber.handler(settings) + } + } } } } diff --git a/Nynja/UserSettings/Settings/Theme.swift b/Nynja/UserSettings/Settings/Theme.swift index 6bba3db89..03acaa78c 100644 --- a/Nynja/UserSettings/Settings/Theme.swift +++ b/Nynja/UserSettings/Settings/Theme.swift @@ -13,8 +13,8 @@ enum Theme: String { static let `default` = Theme.dark static let all: [Theme] = [dark, light] - case light = "light" - case dark = "dark" + case light + case dark var name: String { switch self { diff --git a/Nynja/UserSettings/Settings/WheelPosition.swift b/Nynja/UserSettings/Settings/WheelPosition.swift index 2a0c2600c..acc6de350 100644 --- a/Nynja/UserSettings/Settings/WheelPosition.swift +++ b/Nynja/UserSettings/Settings/WheelPosition.swift @@ -9,8 +9,8 @@ import Foundation enum WheelPosition: String { - case left = "left" - case right = "right" + case left + case right static let `default` = WheelPosition.right static let all: [WheelPosition] = [left, right] -- GitLab From 58f82bbebb77c29dbb9404a6f18f7e667ee51418 Mon Sep 17 00:00:00 2001 From: Volodymyr Hryhoriev Date: Wed, 14 Nov 2018 13:10:35 +0200 Subject: [PATCH 54/68] Add dependency injections to `MainInteractor`. --- Nynja.xcodeproj/project.pbxproj | 16 +++ .../Main/Interactor/MainInteractor.swift | 105 ++++++++++++------ Nynja/Modules/Main/MainProtocols.swift | 4 +- .../Main/Presenter/MainPresenter.swift | 8 +- .../Main/View/MainViewController.swift | 8 +- .../Main/WireFrame/MainWireframe.swift | 20 +++- Nynja/Services/Amazon+FileSync.swift | 2 +- Nynja/Services/PushService.swift | 17 ++- .../FileDownloaderFactory.swift | 23 ++++ .../FileDownloaderKind.swift | 11 ++ .../ServiceFactory/ServiceFactory.swift | 32 ++++++ Nynja/SyncFileManager/SyncFileManager.swift | 6 +- 12 files changed, 196 insertions(+), 56 deletions(-) create mode 100644 Nynja/Services/ServiceFactory/FileDownloaderFactory/FileDownloaderFactory.swift create mode 100644 Nynja/Services/ServiceFactory/FileDownloaderFactory/FileDownloaderKind.swift diff --git a/Nynja.xcodeproj/project.pbxproj b/Nynja.xcodeproj/project.pbxproj index 6c974490e..a1ddbeca3 100644 --- a/Nynja.xcodeproj/project.pbxproj +++ b/Nynja.xcodeproj/project.pbxproj @@ -553,6 +553,8 @@ 4B030F3F2195D88100F293B7 /* UserInfoImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B030F3D2195D88100F293B7 /* UserInfoImpl.swift */; }; 4B052CB0203614D400BC2A9B /* StringAtomExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B052CAF203614D400BC2A9B /* StringAtomExtension.swift */; }; 4B052CB12036193900BC2A9B /* StringAtomExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B052CAF203614D400BC2A9B /* StringAtomExtension.swift */; }; + 4B055C37219C313A001FE077 /* FileDownloaderFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B055C36219C313A001FE077 /* FileDownloaderFactory.swift */; }; + 4B055C39219C3146001FE077 /* FileDownloaderKind.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B055C38219C3146001FE077 /* FileDownloaderKind.swift */; }; 4B058F03204EA928004C7D9F /* DAOProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B058F02204EA928004C7D9F /* DAOProtocol.swift */; }; 4B058F0B204EAEBA004C7D9F /* RoomDAOProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B058F0A204EAEBA004C7D9F /* RoomDAOProtocol.swift */; }; 4B058F0D204EAEC3004C7D9F /* RoomDAO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B058F0C204EAEC3004C7D9F /* RoomDAO.swift */; }; @@ -2853,6 +2855,8 @@ 4B030F3A2195CF8100F293B7 /* Host.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Host.swift; sourceTree = ""; }; 4B030F3D2195D88100F293B7 /* UserInfoImpl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserInfoImpl.swift; sourceTree = ""; }; 4B052CAF203614D400BC2A9B /* StringAtomExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringAtomExtension.swift; sourceTree = ""; }; + 4B055C36219C313A001FE077 /* FileDownloaderFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileDownloaderFactory.swift; sourceTree = ""; }; + 4B055C38219C3146001FE077 /* FileDownloaderKind.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileDownloaderKind.swift; sourceTree = ""; }; 4B058EFD204EA751004C7D9F /* ProfileDAO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileDAO.swift; sourceTree = ""; }; 4B058F00204EA7F5004C7D9F /* DBManagerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DBManagerProtocol.swift; sourceTree = ""; }; 4B058F02204EA928004C7D9F /* DAOProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DAOProtocol.swift; sourceTree = ""; }; @@ -6516,6 +6520,15 @@ path = Entities; sourceTree = ""; }; + 4B055C35219C30F5001FE077 /* FileDownloaderFactory */ = { + isa = PBXGroup; + children = ( + 4B055C36219C313A001FE077 /* FileDownloaderFactory.swift */, + 4B055C38219C3146001FE077 /* FileDownloaderKind.swift */, + ); + path = FileDownloaderFactory; + sourceTree = ""; + }; 4B058EF9204EA6EE004C7D9F /* DB */ = { isa = PBXGroup; children = ( @@ -13004,6 +13017,7 @@ F11786EF20AC5474007A9A1B /* ServiceFactory */ = { isa = PBXGroup; children = ( + 4B055C35219C30F5001FE077 /* FileDownloaderFactory */, F11786F020AC5482007A9A1B /* ServiceFactory.swift */, ); name = ServiceFactory; @@ -16592,6 +16606,7 @@ 855EF423202CC85300541BE3 /* MQTTServiceStars.swift in Sources */, 3362A56D731AC1411C02D037 /* MyGroupAliasWireframe.swift in Sources */, 260313A020A0A4BA009AC66D /* SwitchableActionCell.swift in Sources */, + 4B055C37219C313A001FE077 /* FileDownloaderFactory.swift in Sources */, 8ED0F3D11FBC5CF2004916AB /* GroupsListViewController.swift in Sources */, BBF46945EB64E07C58817ACA /* EditGroupNameProtocols.swift in Sources */, 8520040020D466CE007C0036 /* StickerPack_Spec.swift in Sources */, @@ -16694,6 +16709,7 @@ 26534B25210B4BE70003B9BC /* DBMessage+Extension.swift in Sources */, 2F7C7F7837BDE6F5767A3A8C /* GroupStorageViewController.swift in Sources */, 4B1D7E012029C4BE00703228 /* OptionsItemsFactory.swift in Sources */, + 4B055C39219C3146001FE077 /* FileDownloaderKind.swift in Sources */, 4B348FFE2163850100CCB0E3 /* DeletedIndexesCalculator.swift in Sources */, C921738220BADAFC00519A2D /* TextInputValidationService.swift in Sources */, 9763CCDFE5AF7B58C21CDED9 /* GroupStoragePresenter.swift in Sources */, diff --git a/Nynja/Modules/Main/Interactor/MainInteractor.swift b/Nynja/Modules/Main/Interactor/MainInteractor.swift index b9eebeb06..9388dc693 100644 --- a/Nynja/Modules/Main/Interactor/MainInteractor.swift +++ b/Nynja/Modules/Main/Interactor/MainInteractor.swift @@ -8,7 +8,8 @@ import SDWebImage import Intercom -class MainInteractor: MainInteractorInputProtocol, EditPhotoDelegate, MQTTServiceDelegate { + +final class MainInteractor: BaseInteractor, MainInteractorInputProtocol, EditPhotoDelegate, MQTTServiceDelegate, SetInjectable { weak var presenter: MainInteractorOutputProtocol! @@ -16,53 +17,57 @@ class MainInteractor: MainInteractorInputProtocol, EditPhotoDelegate, MQTTServic return ContactDAO.currentContact } - private var storageService: StorageService { - return StorageService.sharedInstance - } - - init() { - let notificationsSettings = UIUserNotificationSettings(types: [.sound, .alert, .badge], categories: nil) - UIApplication.shared.registerUserNotificationSettings(notificationsSettings) - UIApplication.shared.registerForRemoteNotifications() - let _ = PushService.sharedInstance - - MQTTService.sharedInstance.addSubscriber(self) + private var storageService: StorageService! + private var mqttService: MQTTService! + private var pushService: PushService! + private var communicatorService: NynjaCommunicatorService! + private var badgeNumberService: BadgeNumberServiceProtocol! + private var notificationManager: NotificationManager! + private var userSettingsService: UserSettingsService! + private var syncFileManager: SyncFileManager! + + override func loadData() { + super.loadData() + + + pushService.registerForPushNotifications() + mqttService.addSubscriber(self) setupIntercom() } func checkSession() { - MQTTService.sharedInstance.reconnect() + mqttService.reconnect() } func call(phoneId: String) { if let ctc = ContactDAO.findContactBy(phoneId: phoneId), let name = ctc.fullName { - NynjaCommunicatorService.sharedInstance.call(userId: phoneId, name: name, withVideo: false) + communicatorService.call(userId: phoneId, name: name, withVideo: false) } } func videoCall(phoneId: String) { if let ctc = ContactDAO.findContactBy(phoneId: phoneId), let name = ctc.names { - NynjaCommunicatorService.sharedInstance.call(userId: phoneId, name: name, withVideo: true) + communicatorService.call(userId: phoneId, name: name, withVideo: true) } } func dialInGroup(name: String) { - NynjaCommunicatorService.sharedInstance.dialInGroup(groupname: name) + communicatorService.dialInGroup(groupname: name) } func createGroupCall(callId: String?, contacts: [Contact], room: Room?) { if let cid = callId { - NynjaCommunicatorService.sharedInstance.addAndStartConference(callId: cid, contacts: contacts, room: room) + communicatorService.addAndStartConference(callId: cid, contacts: contacts, room: room) } else { - NynjaCommunicatorService.sharedInstance.createConference(contacts: contacts, room: room) + communicatorService.createConference(contacts: contacts, room: room) } } func createConferenceCall(callId: String?, contacts: [Contact], room: Room?) { if let cid = callId { - NynjaCommunicatorService.sharedInstance.addAndStartConference(callId: cid, contacts: contacts, room: room) + communicatorService.addAndStartConference(callId: cid, contacts: contacts, room: room) } else { - NynjaCommunicatorService.sharedInstance.createConference(contacts: contacts, room: room) + communicatorService.createConference(contacts: contacts, room: room) } } @@ -74,27 +79,32 @@ class MainInteractor: MainInteractorInputProtocol, EditPhotoDelegate, MQTTServic } func logout() { - MQTTService.sharedInstance.logout() + mqttService.logout() LogService.log(topic: .db) { return "Clear storage: logout" } cleanServices() } func deleteAccount() { if let phone = storageService.phone, phone != "" { - MQTTService.sharedInstance.deleteUser(number: phone) + mqttService.deleteUser(number: phone) } LogService.log(topic: .db) { return "Clear storage: delete account" } cleanServices() } private func cleanServices() { - StorageService.sharedInstance.clearStorage() + storageService.clearStorage() + clearSDWebImageCache() + + badgeNumberService.clean() + notificationManager.clean() + userSettingsService.reset() + } + + private func clearSDWebImageCache() { let cache = SDImageCache.shared() cache.clearMemory() cache.clearDisk() - BadgeNumberService.shared.clean() - NotificationManager.shared.clean() - UserSettingsService.shared.reset() } func photoEditFinished(url: URL) { @@ -122,11 +132,9 @@ class MainInteractor: MainInteractorInputProtocol, EditPhotoDelegate, MQTTServic } private func uploadAvatar(with url: URL, rosterId: Int64) { - let sync = SyncFileManager.sharedInstance - sync.downloader = AmazonManager.shared - SyncFileManager.sharedInstance.saveExternalFileLink(localUrl: url.path) { (ext, progress, request) in + syncFileManager.saveExternalFileLink(localUrl: url.path) { [mqttService](ext, progress, request) in if ext != nil { - MQTTService.sharedInstance.updateAvatar(id: rosterId, link: String(describing: ext!)) + mqttService?.updateAvatar(id: rosterId, link: String(describing: ext!)) } } } @@ -135,15 +143,15 @@ class MainInteractor: MainInteractorInputProtocol, EditPhotoDelegate, MQTTServic func mqttServiceDidConnect(_ mqttService: MQTTService) { - DispatchQueue.main.async { - self.presenter.hideUILocker() + dispatchAsyncMain { [weak self] in + self?.presenter.hideUILocker() } } func mqttServiceDidReceiveAuthenticationFailure(_ mqttService: MQTTService) { - DispatchQueue.main.async { - self.logout() - self.presenter.changeScreenToAuth() + dispatchAsyncMain { [weak self] in + self?.logout() + self?.presenter.changeScreenToAuth() } } @@ -163,3 +171,30 @@ class MainInteractor: MainInteractorInputProtocol, EditPhotoDelegate, MQTTServic Intercom.updateUser(userAttributes) } } + + +// MARK: - SetInjectable + +extension MainInteractor { + struct Dependencies { + let presenter: MainInteractorOutputProtocol + let storageService: StorageService + let mqttService: MQTTService + let communicatorService: NynjaCommunicatorService + let badgeNumberService: BadgeNumberServiceProtocol + let notificationManager: NotificationManager + let userSettingsService: UserSettingsService + let syncFileManager: SyncFileManager + } + + func inject(dependencies: MainInteractor.Dependencies) { + presenter = dependencies.presenter + storageService = dependencies.storageService + mqttService = dependencies.mqttService + communicatorService = dependencies.communicatorService + badgeNumberService = dependencies.badgeNumberService + notificationManager = dependencies.notificationManager + userSettingsService = dependencies.userSettingsService + syncFileManager = dependencies.syncFileManager + } +} diff --git a/Nynja/Modules/Main/MainProtocols.swift b/Nynja/Modules/Main/MainProtocols.swift index 45812c17c..7fa230b9b 100644 --- a/Nynja/Modules/Main/MainProtocols.swift +++ b/Nynja/Modules/Main/MainProtocols.swift @@ -135,7 +135,7 @@ protocol MainViewProtocol: WheelOutProtocol { func notifyAvailability(snackBar: SnackBar, isAvailable: Bool) } -protocol MainPresenterProtocol: class { +protocol MainPresenterProtocol: BasePresenterProtocol { var view: MainViewProtocol! { get set } var interactor: MainInteractorInputProtocol! { get set } @@ -225,7 +225,7 @@ protocol MainInteractorOutputProtocol: class { func changeScreenToAuth() } -protocol MainInteractorInputProtocol: class { +protocol MainInteractorInputProtocol: BaseInteractorProtocol { var contact: Contact? { get } diff --git a/Nynja/Modules/Main/Presenter/MainPresenter.swift b/Nynja/Modules/Main/Presenter/MainPresenter.swift index 1cc2cee73..029ea6c21 100644 --- a/Nynja/Modules/Main/Presenter/MainPresenter.swift +++ b/Nynja/Modules/Main/Presenter/MainPresenter.swift @@ -6,15 +6,19 @@ // Copyright © 2017 TecSynt Solutions. All rights reserved. // -class MainPresenter: MainPresenterProtocol, MainInteractorOutputProtocol, ScheduleMessageDelegate, EditParticipantsDelegate { +final class MainPresenter: BasePresenter, MainPresenterProtocol, MainInteractorOutputProtocol, ScheduleMessageDelegate, EditParticipantsDelegate { func returnToCall(call: NYNCall?) { self.wireFrame.returnToCall(call: call) } weak var view: MainViewProtocol! - var interactor: MainInteractorInputProtocol! var wireFrame: MainWireFrameProtocol! + var interactor: MainInteractorInputProtocol! { + didSet { + _interactor = interactor + } + } func showQRReader() { wireFrame.showQRReader() diff --git a/Nynja/Modules/Main/View/MainViewController.swift b/Nynja/Modules/Main/View/MainViewController.swift index 36d8fc95f..2531476ff 100644 --- a/Nynja/Modules/Main/View/MainViewController.swift +++ b/Nynja/Modules/Main/View/MainViewController.swift @@ -13,11 +13,15 @@ import MobileCoreServices import AssetsLibrary import AVFoundation -class MainViewController: BaseVC, MainViewProtocol, HitTestDelegate, UINavigationControllerDelegate, WheelPreviewProtocol { +final class MainViewController: BaseVC, MainViewProtocol, HitTestDelegate, UINavigationControllerDelegate, WheelPreviewProtocol { private var cameraCoordinator: CameraFlowCoordinatorProtocol? private var galleryCoordinator: GalleryFlowCoordinatorProtocol? - var presenter: MainPresenterProtocol! + var presenter: MainPresenterProtocol! { + didSet { + _presenter = presenter + } + } var galleryFetchController: GalleryFetchController? diff --git a/Nynja/Modules/Main/WireFrame/MainWireframe.swift b/Nynja/Modules/Main/WireFrame/MainWireframe.swift index cbb1e674e..a9fc5758f 100644 --- a/Nynja/Modules/Main/WireFrame/MainWireframe.swift +++ b/Nynja/Modules/Main/WireFrame/MainWireframe.swift @@ -10,7 +10,7 @@ import UIKit import CoreLocation import SnapKit -class MainWireFrame: MainWireFrameProtocol, NynjaCommunicatorServiceDelegate { +final class MainWireFrame: MainWireFrameProtocol, NynjaCommunicatorServiceDelegate { weak var navigation : UINavigationController? weak var contentNavigation: UINavigationController! @@ -20,6 +20,8 @@ class MainWireFrame: MainWireFrameProtocol, NynjaCommunicatorServiceDelegate { weak var external: EditParticipantsDelegate? = nil func presentMain(navigation: UINavigationController, isRegistered: Bool, checkSession: Bool = false) { + let serviceFactory = ServiceFactory() + let view = MainViewController() let presenter = MainPresenter() let interactor = MainInteractor() @@ -34,7 +36,17 @@ class MainWireFrame: MainWireFrameProtocol, NynjaCommunicatorServiceDelegate { presenter.view = view presenter.wireFrame = self presenter.interactor = interactor - interactor.presenter = presenter + + interactor.inject( + dependencies: .init( + presenter: presenter, + storageService: serviceFactory.makeStorageService(), + mqttService: serviceFactory.makeMQTTService(), + communicatorService: serviceFactory.makeNynjaCommunicatorService(), + badgeNumberService: serviceFactory.makeBadgeNumberService(), + notificationManager: serviceFactory.makeNotificationManager(), + userSettingsService: serviceFactory.makeUserSettingsService(), + syncFileManager: serviceFactory.makeSyncFileManager(with: .amazon))) // set content view let contentNavigation = ContentNavigationVC() @@ -275,10 +287,6 @@ class MainWireFrame: MainWireFrameProtocol, NynjaCommunicatorServiceDelegate { isVideo = false self.showReturnToCallView() self.view?.hidePartnerVideoView() - -// if let call = self.call { -// self.view?.showReturnToCall(call: call) -// } } func returnToCall(call: NYNCall?) { diff --git a/Nynja/Services/Amazon+FileSync.swift b/Nynja/Services/Amazon+FileSync.swift index 5ccd350c3..c76edb8c5 100644 --- a/Nynja/Services/Amazon+FileSync.swift +++ b/Nynja/Services/Amazon+FileSync.swift @@ -10,7 +10,7 @@ import Foundation import AWSCore import AWSS3 -extension AmazonManager: FileNetworkProtocol { +extension AmazonManager: FileDownloader { func download(url: String, from destination: RemoteStorageDestination, result: ((_ result: String?, _ transferInfo: TransferInfo?) -> ())?) -> AWSRequest? { diff --git a/Nynja/Services/PushService.swift b/Nynja/Services/PushService.swift index 0cc420b9b..a3cac82de 100644 --- a/Nynja/Services/PushService.swift +++ b/Nynja/Services/PushService.swift @@ -16,11 +16,7 @@ final class PushService: NSObject, PKPushRegistryDelegate, UserSettingsRespondab return .sharedInstance } - static let sharedInstance: PushService = { - let instance = PushService() - instance.registerForPushNotifications() - return instance - }() + static let sharedInstance = PushService() // MARK: - Init @@ -38,6 +34,17 @@ final class PushService: NSObject, PKPushRegistryDelegate, UserSettingsRespondab // MARK: - Setup func registerForPushNotifications() { + registerForNativeNotifications() + registerForPushNotifications() + } + + private func registerForNativeNotifications() { + let notificationsSettings = UIUserNotificationSettings(types: [.sound, .alert, .badge], categories: nil) + UIApplication.shared.registerUserNotificationSettings(notificationsSettings) + UIApplication.shared.registerForRemoteNotifications() + } + + private func registerForPushKitNotifications() { let voipRegistry = PKPushRegistry(queue: DispatchQueue.main) voipRegistry.delegate = self voipRegistry.desiredPushTypes = [.voIP] diff --git a/Nynja/Services/ServiceFactory/FileDownloaderFactory/FileDownloaderFactory.swift b/Nynja/Services/ServiceFactory/FileDownloaderFactory/FileDownloaderFactory.swift new file mode 100644 index 000000000..b7a1b8427 --- /dev/null +++ b/Nynja/Services/ServiceFactory/FileDownloaderFactory/FileDownloaderFactory.swift @@ -0,0 +1,23 @@ +// +// FileDownloaderFactory.swift +// Nynja +// +// Created by Volodymyr Hryhoriev on 11/14/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +final class FileDownloaderFactory { + + let serviceFactory: ServiceFactoryProtocol + + init(serviceFactory: ServiceFactoryProtocol) { + self.serviceFactory = serviceFactory + } + + func makeFileDownloader(for kind: FileDownloaderKind) -> FileDownloader { + switch kind { + case .amazon: + return serviceFactory.makeAmazonManager() + } + } +} diff --git a/Nynja/Services/ServiceFactory/FileDownloaderFactory/FileDownloaderKind.swift b/Nynja/Services/ServiceFactory/FileDownloaderFactory/FileDownloaderKind.swift new file mode 100644 index 000000000..a841644a5 --- /dev/null +++ b/Nynja/Services/ServiceFactory/FileDownloaderFactory/FileDownloaderKind.swift @@ -0,0 +1,11 @@ +// +// FileDownloaderKind.swift +// Nynja +// +// Created by Volodymyr Hryhoriev on 11/14/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +enum FileDownloaderKind { + case amazon +} diff --git a/Nynja/Services/ServiceFactory/ServiceFactory.swift b/Nynja/Services/ServiceFactory/ServiceFactory.swift index 99adf6859..686feb212 100644 --- a/Nynja/Services/ServiceFactory/ServiceFactory.swift +++ b/Nynja/Services/ServiceFactory/ServiceFactory.swift @@ -34,6 +34,7 @@ protocol ServiceFactoryProtocol: SharedServiceFactoryProtocol { func makeWalletService() -> WalletService func makeSyncFileManager() -> SyncFileManager + func makeSyncFileManager(with kind: FileDownloaderKind) -> SyncFileManager func makeMuteChatService() -> MuteChatServiceProtocol @@ -49,6 +50,14 @@ protocol ServiceFactoryProtocol: SharedServiceFactoryProtocol { func makeAudioSessionManager() -> AudioSessionManager func makeHomeDataProvider(limit: Int) -> HomeDataProvider + + func makeNynjaCommunicatorService() -> NynjaCommunicatorService + + func makeBadgeNumberService() -> BadgeNumberServiceProtocol + + func makeNotificationManager() -> NotificationManager + + func makeUserSettingsService() -> UserSettingsService } final class ServiceFactory: SharedServiceFactory, ServiceFactoryProtocol { @@ -142,6 +151,13 @@ final class ServiceFactory: SharedServiceFactory, ServiceFactoryProtocol { func makeSyncFileManager() -> SyncFileManager { return SyncFileManager.sharedInstance } + + func makeSyncFileManager(with kind: FileDownloaderKind) -> SyncFileManager { + let downloader = FileDownloaderFactory(serviceFactory: self).makeFileDownloader(for: kind) + let syncFileManager = makeSyncFileManager() + syncFileManager.downloader = downloader + return syncFileManager + } func makeMuteChatService() -> MuteChatServiceProtocol { let dependencies = MuteChatService.Dependencies(mqttService: makeMQTTService()) @@ -182,4 +198,20 @@ final class ServiceFactory: SharedServiceFactory, ServiceFactoryProtocol { fields: .init( limit: limit))) } + + func makeNynjaCommunicatorService() -> NynjaCommunicatorService { + return .sharedInstance + } + + func makeBadgeNumberService() -> BadgeNumberServiceProtocol { + return BadgeNumberService.shared + } + + func makeNotificationManager() -> NotificationManager { + return .shared + } + + func makeUserSettingsService() -> UserSettingsService { + return .shared + } } diff --git a/Nynja/SyncFileManager/SyncFileManager.swift b/Nynja/SyncFileManager/SyncFileManager.swift index 3e1263358..9dcf4275f 100644 --- a/Nynja/SyncFileManager/SyncFileManager.swift +++ b/Nynja/SyncFileManager/SyncFileManager.swift @@ -10,7 +10,7 @@ import Foundation import AWSCore import AWSS3 -protocol FileNetworkProtocol: class { +protocol FileDownloader: class { func download(url: String, from destination: RemoteStorageDestination, result: ((_ result: String?, _ transferInfo: TransferInfo?) -> ())?) -> AWSRequest? @@ -18,7 +18,7 @@ protocol FileNetworkProtocol: class { func upload(localUrl: String, result: ((_ result:String?, _ transferInfo:TransferInfo?)->())?) -> AWSRequest? } -extension FileNetworkProtocol { +extension FileDownloader { func download(url: String, result: ((_ result: String?, _ transferInfo: TransferInfo?) -> ())?) -> AWSRequest? { return download(url: url, from: .default, result: result) @@ -29,7 +29,7 @@ class SyncFileManager { static let sharedInstance = SyncFileManager() - var downloader: FileNetworkProtocol = AmazonManager.shared + var downloader: FileDownloader = AmazonManager.shared func saveExternalFileLink(localUrl: String, completion: ((_ externalUrl: URL?, _ transferInfo: TransferInfo?, _ request: AWSRequest?) ->())?) { if let shortPath = localUrl.getShortPath() { -- GitLab From 55c4631ab446089fe043e90ef9e95e6adc21340f Mon Sep 17 00:00:00 2001 From: Volodymyr Hryhoriev Date: Wed, 14 Nov 2018 14:05:01 +0200 Subject: [PATCH 55/68] Create `MQTTServiceProtocol`. --- .../View/ForwardSelectorViewController.swift | 2 +- Nynja.xcodeproj/project.pbxproj | 6 +++++ .../Splash/Interactor/SplashInteractor.swift | 2 +- Nynja/Services/MQTT/MQTTService.swift | 10 +++---- Nynja/Services/MQTT/MQTTServiceProtocol.swift | 26 +++++++++++++++++++ 5 files changed, 39 insertions(+), 7 deletions(-) create mode 100644 Nynja/Services/MQTT/MQTTServiceProtocol.swift diff --git a/Nynja-Share/UI/ForwardSelector/View/ForwardSelectorViewController.swift b/Nynja-Share/UI/ForwardSelector/View/ForwardSelectorViewController.swift index 19a61f0c3..1575fcb76 100644 --- a/Nynja-Share/UI/ForwardSelector/View/ForwardSelectorViewController.swift +++ b/Nynja-Share/UI/ForwardSelector/View/ForwardSelectorViewController.swift @@ -393,7 +393,7 @@ final class ForwardSelectorViewController: UIViewController, ForwardSelectorView } func closeShareExtension() { - MQTTService.sharedInstance.disconnect(shouldRemoveConnectionSubscriber: false) + MQTTService.sharedInstance.disconnect() if let context = self.extensionContext { context.completeRequest(returningItems: nil, completionHandler: nil) } diff --git a/Nynja.xcodeproj/project.pbxproj b/Nynja.xcodeproj/project.pbxproj index a1ddbeca3..44f2a9501 100644 --- a/Nynja.xcodeproj/project.pbxproj +++ b/Nynja.xcodeproj/project.pbxproj @@ -555,6 +555,8 @@ 4B052CB12036193900BC2A9B /* StringAtomExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B052CAF203614D400BC2A9B /* StringAtomExtension.swift */; }; 4B055C37219C313A001FE077 /* FileDownloaderFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B055C36219C313A001FE077 /* FileDownloaderFactory.swift */; }; 4B055C39219C3146001FE077 /* FileDownloaderKind.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B055C38219C3146001FE077 /* FileDownloaderKind.swift */; }; + 4B055C3B219C4101001FE077 /* MQTTServiceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B055C3A219C4101001FE077 /* MQTTServiceProtocol.swift */; }; + 4B055C3C219C4119001FE077 /* MQTTServiceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B055C3A219C4101001FE077 /* MQTTServiceProtocol.swift */; }; 4B058F03204EA928004C7D9F /* DAOProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B058F02204EA928004C7D9F /* DAOProtocol.swift */; }; 4B058F0B204EAEBA004C7D9F /* RoomDAOProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B058F0A204EAEBA004C7D9F /* RoomDAOProtocol.swift */; }; 4B058F0D204EAEC3004C7D9F /* RoomDAO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B058F0C204EAEC3004C7D9F /* RoomDAO.swift */; }; @@ -2857,6 +2859,7 @@ 4B052CAF203614D400BC2A9B /* StringAtomExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringAtomExtension.swift; sourceTree = ""; }; 4B055C36219C313A001FE077 /* FileDownloaderFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileDownloaderFactory.swift; sourceTree = ""; }; 4B055C38219C3146001FE077 /* FileDownloaderKind.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileDownloaderKind.swift; sourceTree = ""; }; + 4B055C3A219C4101001FE077 /* MQTTServiceProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MQTTServiceProtocol.swift; sourceTree = ""; }; 4B058EFD204EA751004C7D9F /* ProfileDAO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileDAO.swift; sourceTree = ""; }; 4B058F00204EA7F5004C7D9F /* DBManagerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DBManagerProtocol.swift; sourceTree = ""; }; 4B058F02204EA928004C7D9F /* DAOProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DAOProtocol.swift; sourceTree = ""; }; @@ -6094,6 +6097,7 @@ 4B030F382195CDDA00F293B7 /* Extensions */, 3A8045CD1F60C8E200AED866 /* MQTTService.swift */, 4B030F312195CD4500F293B7 /* MQTTServiceDelegate.swift */, + 4B055C3A219C4101001FE077 /* MQTTServiceProtocol.swift */, ); name = MQTT; path = Services/MQTT; @@ -14680,6 +14684,7 @@ A4F3DAA42084935400FF71C7 /* Constants.swift in Sources */, E7ED35131FB33806008B5704 /* CellModel.swift in Sources */, A42CE55220692EDB000889CC /* act.swift in Sources */, + 4B055C3C219C4119001FE077 /* MQTTServiceProtocol.swift in Sources */, A42CE5DA20692EDB000889CC /* p2p_Spec.swift in Sources */, A4A2424F2060390000B0A804 /* Handlers.swift in Sources */, 2610D4642076516900E6E2B2 /* Array+Feature.swift in Sources */, @@ -16699,6 +16704,7 @@ 0D520AAAA7FD1F0464C8174F /* GroupRulesInteractor.swift in Sources */, A45F115E20B422AF00F45004 /* Contact+DB.swift in Sources */, 8505445720627C7C00E0F2B3 /* HistoryCellModel.swift in Sources */, + 4B055C3B219C4101001FE077 /* MQTTServiceProtocol.swift in Sources */, 2F2A5C12A7202E7834F923DC /* GroupRulesWireframe.swift in Sources */, 2625DBF820EFC5DE00E01C05 /* FourCharCode+StringLiteralConvertible.swift in Sources */, D3A30AF05BD7C46A9A8C1FC1 /* GroupStorageProtocols.swift in Sources */, diff --git a/Nynja/Modules/Splash/Interactor/SplashInteractor.swift b/Nynja/Modules/Splash/Interactor/SplashInteractor.swift index 38422a4da..f683f5feb 100644 --- a/Nynja/Modules/Splash/Interactor/SplashInteractor.swift +++ b/Nynja/Modules/Splash/Interactor/SplashInteractor.swift @@ -45,7 +45,7 @@ class SplashInteractor: SplashInteractorInputProtocol { application.applicationIconBadgeNumber = Int(badgeNumber) } - mqttService.initialize() + mqttService.connect() callService.initialize() MediaDownloadManager.setupAppDataUsageSettingsIfNeeded() diff --git a/Nynja/Services/MQTT/MQTTService.swift b/Nynja/Services/MQTT/MQTTService.swift index 23fd5dae7..eea0985b4 100644 --- a/Nynja/Services/MQTT/MQTTService.swift +++ b/Nynja/Services/MQTT/MQTTService.swift @@ -10,7 +10,7 @@ import Foundation import CocoaMQTT /// Performs works in the several serial background queues -final class MQTTService: NSObject, CocoaMQTTDelegate, ConnectionServiceDelegate { +final class MQTTService: NSObject, MQTTServiceProtocol, CocoaMQTTDelegate, ConnectionServiceDelegate { private var mqtt: CocoaMQTT? @@ -74,7 +74,7 @@ final class MQTTService: NSObject, CocoaMQTTDelegate, ConnectionServiceDelegate // MARK: - Setup - func initialize() { + func connect() { queuePool.processingQueue.async { [unowned self] in self.setup() self.mqtt?.connect() @@ -125,10 +125,10 @@ final class MQTTService: NSObject, CocoaMQTTDelegate, ConnectionServiceDelegate func reconnect() { LogService.log(topic: .MQTT) { return "Reconnect" } - initialize() + connect() } - func disconnect(shouldRemoveConnectionSubscriber: Bool = true) { + func disconnect() { queuePool.processingQueue.async { [unowned self] in LogService.log(topic: .MQTT) { return "Disconnect" } self.mqtt?.disconnect() @@ -285,7 +285,7 @@ final class MQTTService: NSObject, CocoaMQTTDelegate, ConnectionServiceDelegate guard let networkStatus = sender.state[.networking] else { return } switch networkStatus { case .connected: self.mqtt?.connect() - case .disconnected: self.disconnect(shouldRemoveConnectionSubscriber: false) + case .disconnected: self.disconnect() case .switched: self.reconnect() default: break } diff --git a/Nynja/Services/MQTT/MQTTServiceProtocol.swift b/Nynja/Services/MQTT/MQTTServiceProtocol.swift new file mode 100644 index 000000000..a58000f49 --- /dev/null +++ b/Nynja/Services/MQTT/MQTTServiceProtocol.swift @@ -0,0 +1,26 @@ +// +// MQTTServiceProtocol.swift +// Nynja +// +// Created by Volodymyr Hryhoriev on 11/14/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +//import Founda + +protocol MQTTServiceProtocol { + + var isBadProtocolVersion: Bool { get } + var isConnectedSuccess: Bool { get } + + func connect() + func tryReconnect() + func reconnect() + func disconnect() + + func publish(model: BaseMQTTModel) + + func addSubscriber(_ subscriber: MQTTServiceDelegate) + func removeSubscriber(_ subscriber: MQTTServiceDelegate) + func removeAllSubscribers() +} -- GitLab From 19b57db807468e861304ae3aad2baedcebebdfc7 Mon Sep 17 00:00:00 2001 From: Volodymyr Hryhoriev Date: Wed, 14 Nov 2018 17:06:34 +0200 Subject: [PATCH 56/68] 1. Remove `ScheduleMessageDelegate`. 2. Inject dependencies inside `MessagePresenter`. 3. Remove `logout` notifying from `IOHandler`. 4. Correct handling of `Profile/Remove` API. 5. Make `MQTTService` perform publishing inside `processingQueue`. --- Nynja.xcodeproj/project.pbxproj | 14 ++++- Nynja/DBObserver.swift | 2 + .../Interactor/VerifyNumberInteractor.swift | 1 - .../Main/Interactor/MainInteractor.swift | 54 +++++++++++++++---- Nynja/Modules/Main/MainProtocols.swift | 8 +-- .../Main/Presenter/MainPresenter.swift | 42 ++++++++++----- .../Main/WireFrame/MainWireframe.swift | 20 ++++--- .../Message/WireFrame/MessageWireframe.swift | 2 +- .../Presenter/ScheduleMessagePresenter.swift | 3 -- .../ScheduleMessageProtocols.swift | 8 +-- .../WireFrame/ScheduleMessageWireframe.swift | 4 +- Nynja/ProfileHandlerDelegate.swift | 11 ++++ .../HandleServices/ProfileHandler.swift | 17 +++--- .../Extensions/MQTTService+QueuePool.swift | 8 ++- Nynja/Services/MQTT/MQTTService.swift | 5 +- Nynja/Services/PushService.swift | 4 +- .../ServiceFactory/ServiceFactory.swift | 12 +++++ .../Handlers/IoHandler/IoHandler.swift | 7 --- .../IoHandler/IoHandlerDelegate.swift | 4 +- 19 files changed, 149 insertions(+), 77 deletions(-) create mode 100644 Nynja/ProfileHandlerDelegate.swift diff --git a/Nynja.xcodeproj/project.pbxproj b/Nynja.xcodeproj/project.pbxproj index 44f2a9501..26f61926e 100644 --- a/Nynja.xcodeproj/project.pbxproj +++ b/Nynja.xcodeproj/project.pbxproj @@ -557,6 +557,7 @@ 4B055C39219C3146001FE077 /* FileDownloaderKind.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B055C38219C3146001FE077 /* FileDownloaderKind.swift */; }; 4B055C3B219C4101001FE077 /* MQTTServiceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B055C3A219C4101001FE077 /* MQTTServiceProtocol.swift */; }; 4B055C3C219C4119001FE077 /* MQTTServiceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B055C3A219C4101001FE077 /* MQTTServiceProtocol.swift */; }; + 4B055C3F219C61A2001FE077 /* ProfileHandlerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B055C3E219C61A2001FE077 /* ProfileHandlerDelegate.swift */; }; 4B058F03204EA928004C7D9F /* DAOProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B058F02204EA928004C7D9F /* DAOProtocol.swift */; }; 4B058F0B204EAEBA004C7D9F /* RoomDAOProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B058F0A204EAEBA004C7D9F /* RoomDAOProtocol.swift */; }; 4B058F0D204EAEC3004C7D9F /* RoomDAO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B058F0C204EAEC3004C7D9F /* RoomDAO.swift */; }; @@ -2860,6 +2861,7 @@ 4B055C36219C313A001FE077 /* FileDownloaderFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileDownloaderFactory.swift; sourceTree = ""; }; 4B055C38219C3146001FE077 /* FileDownloaderKind.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileDownloaderKind.swift; sourceTree = ""; }; 4B055C3A219C4101001FE077 /* MQTTServiceProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MQTTServiceProtocol.swift; sourceTree = ""; }; + 4B055C3E219C61A2001FE077 /* ProfileHandlerDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileHandlerDelegate.swift; sourceTree = ""; }; 4B058EFD204EA751004C7D9F /* ProfileDAO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileDAO.swift; sourceTree = ""; }; 4B058F00204EA7F5004C7D9F /* DBManagerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DBManagerProtocol.swift; sourceTree = ""; }; 4B058F02204EA928004C7D9F /* DAOProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DAOProtocol.swift; sourceTree = ""; }; @@ -5969,9 +5971,9 @@ 4B8771762195AC3C0014AD09 /* HistoryHandler */, 4B8771792195AF7E0014AD09 /* TypingHandler */, 4B0CC2002195B67D00E0BA61 /* LinkHandler */, + 4B055C3D219C618B001FE077 /* ProfileHandler */, 265AEA141FE9AFA600AC4806 /* MemberHandler.swift */, 269666171FB57963009E41C1 /* RoomHandler.swift */, - 3A771CA91F191B38008D968A /* ProfileHandler.swift */, 3A2374D81F262A1600701045 /* ContactHandler.swift */, 3A237BCC1F30E5D400C42B6E /* RosterHandler.swift */, 26FA420F201821B400E6F6EC /* StarHandler.swift */, @@ -6533,6 +6535,15 @@ path = FileDownloaderFactory; sourceTree = ""; }; + 4B055C3D219C618B001FE077 /* ProfileHandler */ = { + isa = PBXGroup; + children = ( + 3A771CA91F191B38008D968A /* ProfileHandler.swift */, + 4B055C3E219C61A2001FE077 /* ProfileHandlerDelegate.swift */, + ); + name = ProfileHandler; + sourceTree = ""; + }; 4B058EF9204EA6EE004C7D9F /* DB */ = { isa = PBXGroup; children = ( @@ -15359,6 +15370,7 @@ FEA6560E2167797E00B44029 /* WalletFundingNetworkRouter.swift in Sources */, 3AE0A84B1F20321A008A04F3 /* Wheel.swift in Sources */, 00E4A65F201A287100CEC61F /* MapSearchDS.swift in Sources */, + 4B055C3F219C61A2001FE077 /* ProfileHandlerDelegate.swift in Sources */, 2603139D20A0A4BA009AC66D /* ChatLanguageSettingsProtocols.swift in Sources */, 26142B1120472ECD004E5FE4 /* MessageLinkTable.swift in Sources */, A4679BAC20B2DD100021FE9C /* SubscribersSelectorViewController.swift in Sources */, diff --git a/Nynja/DBObserver.swift b/Nynja/DBObserver.swift index 37e28e9b0..33b411fbf 100644 --- a/Nynja/DBObserver.swift +++ b/Nynja/DBObserver.swift @@ -10,6 +10,8 @@ import GRDBCipher final class DBObserver: StorageObserver, TransactionObserver { + // TODO: Notify only if subscribers exists! + static let `default` = DBObserver() private init() {} diff --git a/Nynja/Modules/Auth/VerifyNumber/Interactor/VerifyNumberInteractor.swift b/Nynja/Modules/Auth/VerifyNumber/Interactor/VerifyNumberInteractor.swift index c525eddce..92a4a1597 100644 --- a/Nynja/Modules/Auth/VerifyNumber/Interactor/VerifyNumberInteractor.swift +++ b/Nynja/Modules/Auth/VerifyNumber/Interactor/VerifyNumberInteractor.swift @@ -174,7 +174,6 @@ final class VerifyNumberInteractor: BaseInteractor, VerifyNumberInteractorInputP } } } - } diff --git a/Nynja/Modules/Main/Interactor/MainInteractor.swift b/Nynja/Modules/Main/Interactor/MainInteractor.swift index 9388dc693..d5c63f015 100644 --- a/Nynja/Modules/Main/Interactor/MainInteractor.swift +++ b/Nynja/Modules/Main/Interactor/MainInteractor.swift @@ -9,7 +9,7 @@ import SDWebImage import Intercom -final class MainInteractor: BaseInteractor, MainInteractorInputProtocol, EditPhotoDelegate, MQTTServiceDelegate, SetInjectable { +final class MainInteractor: BaseInteractor, MainInteractorInputProtocol, EditPhotoDelegate, MQTTServiceDelegate, ProfileHandlerDelegate, SetInjectable { weak var presenter: MainInteractorOutputProtocol! @@ -17,6 +17,11 @@ final class MainInteractor: BaseInteractor, MainInteractorInputProtocol, EditPho return ContactDAO.currentContact } + private var initialPhone: String? + + + // MARK: - Dependencies + private var storageService: StorageService! private var mqttService: MQTTService! private var pushService: PushService! @@ -25,19 +30,21 @@ final class MainInteractor: BaseInteractor, MainInteractorInputProtocol, EditPho private var notificationManager: NotificationManager! private var userSettingsService: UserSettingsService! private var syncFileManager: SyncFileManager! + private var alertManager: AlertManager! + + + // MARK: - BaseInteractor override func loadData() { super.loadData() - - pushService.registerForPushNotifications() + pushService.registerForNotifications() mqttService.addSubscriber(self) setupIntercom() } - - func checkSession() { - mqttService.reconnect() - } + + + // MARK: - Calls func call(phoneId: String) { @@ -70,10 +77,14 @@ final class MainInteractor: BaseInteractor, MainInteractorInputProtocol, EditPho communicatorService.createConference(contacts: contacts, room: room) } } + + + // MARK: - MainInteractorInputProtocol - func updateGroupCall(contacts: [Contact]) { + func checkSession() { + mqttService.reconnect() } - + func findContactBy(phoneId: String) -> Contact? { return ContactDAO.findContactBy(phoneId: phoneId) } @@ -82,6 +93,7 @@ final class MainInteractor: BaseInteractor, MainInteractorInputProtocol, EditPho mqttService.logout() LogService.log(topic: .db) { return "Clear storage: logout" } cleanServices() + mqttService.reconnect() } func deleteAccount() { @@ -89,7 +101,13 @@ final class MainInteractor: BaseInteractor, MainInteractorInputProtocol, EditPho mqttService.deleteUser(number: phone) } LogService.log(topic: .db) { return "Clear storage: delete account" } + handleProfileDeleting() + } + + private func handleProfileDeleting() { cleanServices() + alertManager.showAlertOk(message: String.localizable.authAttemptsRemoved) + mqttService.reconnect() } private func cleanServices() { @@ -170,6 +188,18 @@ final class MainInteractor: BaseInteractor, MainInteractorInputProtocol, EditPho userAttributes.phone = contact.phoneNumber Intercom.updateUser(userAttributes) } + + + // MARK: - ProfileHandlerDelegate + + func profileDeleted(with phone: String) { + guard initialPhone == phone else { + return + } + + handleProfileDeleting() + presenter.changeScreenToAuth() + } } @@ -180,21 +210,27 @@ extension MainInteractor { let presenter: MainInteractorOutputProtocol let storageService: StorageService let mqttService: MQTTService + let pushService: PushService let communicatorService: NynjaCommunicatorService let badgeNumberService: BadgeNumberServiceProtocol let notificationManager: NotificationManager let userSettingsService: UserSettingsService let syncFileManager: SyncFileManager + let alertManager: AlertManager } func inject(dependencies: MainInteractor.Dependencies) { presenter = dependencies.presenter storageService = dependencies.storageService mqttService = dependencies.mqttService + pushService = dependencies.pushService communicatorService = dependencies.communicatorService badgeNumberService = dependencies.badgeNumberService notificationManager = dependencies.notificationManager userSettingsService = dependencies.userSettingsService syncFileManager = dependencies.syncFileManager + alertManager = dependencies.alertManager + + initialPhone = storageService.phone } } diff --git a/Nynja/Modules/Main/MainProtocols.swift b/Nynja/Modules/Main/MainProtocols.swift index 7fa230b9b..48a9e0382 100644 --- a/Nynja/Modules/Main/MainProtocols.swift +++ b/Nynja/Modules/Main/MainProtocols.swift @@ -48,8 +48,8 @@ protocol MainWireFrameProtocol: class { func showAddParticipantsToCreateConferenceCall() func showPayment(profile: Profile, to contact: Contact) - func showScheduleMessage(with mode: ScheduledMessageMode, delegate: ScheduleMessageDelegate?) - func showScheduleMessage(with inputMessage: InputScheduleMessage, delegate: ScheduleMessageDelegate?) + func showScheduleMessage(with mode: ScheduledMessageMode) + func showScheduleMessage(with inputMessage: InputScheduleMessage) func showForwardSelector(with mode: ForwardSelectorMode, delegate: ForwardSelectorDelegate?) @@ -219,7 +219,6 @@ protocol MainInteractorOutputProtocol: class { * Add here your methods for communication INTERACTOR -> PRESENTER */ - func logout() func showUILocker() func hideUILocker() func changeScreenToAuth() @@ -241,15 +240,12 @@ protocol MainInteractorInputProtocol: BaseInteractorProtocol { func dialInGroup(name: String) func createGroupCall(callId: String?, contacts: [Contact], room: Room?) func createConferenceCall(callId: String?, contacts: [Contact], room: Room?) - func updateGroupCall(contacts: [Contact]) func findContactBy(phoneId: String)->Contact? } -// Provide for compiler info, about extension MainWireFrameProtocol { func showChat(_ chat: ChatModel) { showChat(chat, initialMessage: nil) } - } diff --git a/Nynja/Modules/Main/Presenter/MainPresenter.swift b/Nynja/Modules/Main/Presenter/MainPresenter.swift index 029ea6c21..8aa2b1bfe 100644 --- a/Nynja/Modules/Main/Presenter/MainPresenter.swift +++ b/Nynja/Modules/Main/Presenter/MainPresenter.swift @@ -6,7 +6,7 @@ // Copyright © 2017 TecSynt Solutions. All rights reserved. // -final class MainPresenter: BasePresenter, MainPresenterProtocol, MainInteractorOutputProtocol, ScheduleMessageDelegate, EditParticipantsDelegate { +final class MainPresenter: BasePresenter, MainPresenterProtocol, MainInteractorOutputProtocol, EditParticipantsDelegate, SetInjectable { func returnToCall(call: NYNCall?) { self.wireFrame.returnToCall(call: call) @@ -19,6 +19,8 @@ final class MainPresenter: BasePresenter, MainPresenterProtocol, MainInteractorO _interactor = interactor } } + + private var reachabilityService: ReachabilityService! func showQRReader() { wireFrame.showQRReader() @@ -139,12 +141,12 @@ final class MainPresenter: BasePresenter, MainPresenterProtocol, MainInteractorO } func logout() { - self.interactor.logout() - self.changeScreenToAuth() + interactor.logout() + changeScreenToAuth() } func changeScreenToAuth() { - self.wireFrame.logout() + wireFrame.logout() } func showMessages (room: Room, call: NYNCall, callVC: CallInProgressViewProtocol, isVideo: Bool = false) { @@ -236,8 +238,10 @@ final class MainPresenter: BasePresenter, MainPresenterProtocol, MainInteractorO } func deleteAccount() { - self.interactor.deleteAccount() - self.wireFrame.logout() + reachabilityService.performIfConnected { + interactor.deleteAccount() + changeScreenToAuth() + } } func showDataAndStorage() { @@ -295,13 +299,9 @@ final class MainPresenter: BasePresenter, MainPresenterProtocol, MainInteractorO view?.hideUILocker() } - // MARK: - ScheduleMessageDelegate - func scheduleMessageHasBeenSent() { - //TODO: -// view.cleanTextInput() - } - + // MARK: EditParticpantsDelegate + func participantsUpdated(contacts: [Contact]) { } @@ -319,5 +319,23 @@ final class MainPresenter: BasePresenter, MainPresenterProtocol, MainInteractorO break } } +} + + +// MARK: - SetInjectable +extension MainPresenter { + struct Dependencies { + let view: MainViewProtocol + let wireFrame: MainWireFrameProtocol + let interactor: MainInteractorInputProtocol + let reachabilityService: ReachabilityService + } + + func inject(dependencies: MainPresenter.Dependencies) { + view = dependencies.view + wireFrame = dependencies.wireFrame + interactor = dependencies.interactor + reachabilityService = dependencies.reachabilityService + } } diff --git a/Nynja/Modules/Main/WireFrame/MainWireframe.swift b/Nynja/Modules/Main/WireFrame/MainWireframe.swift index a9fc5758f..9d439b980 100644 --- a/Nynja/Modules/Main/WireFrame/MainWireframe.swift +++ b/Nynja/Modules/Main/WireFrame/MainWireframe.swift @@ -33,20 +33,26 @@ final class MainWireFrame: MainWireFrameProtocol, NynjaCommunicatorServiceDelega // Connecting view.presenter = presenter - presenter.view = view - presenter.wireFrame = self - presenter.interactor = interactor + + presenter.inject( + dependencies: .init( + view: view, + wireFrame: self, + interactor: interactor, + reachabilityService: serviceFactory.makeReachabilityService())) interactor.inject( dependencies: .init( presenter: presenter, storageService: serviceFactory.makeStorageService(), mqttService: serviceFactory.makeMQTTService(), + pushService: serviceFactory.makePushService(), communicatorService: serviceFactory.makeNynjaCommunicatorService(), badgeNumberService: serviceFactory.makeBadgeNumberService(), notificationManager: serviceFactory.makeNotificationManager(), userSettingsService: serviceFactory.makeUserSettingsService(), - syncFileManager: serviceFactory.makeSyncFileManager(with: .amazon))) + syncFileManager: serviceFactory.makeSyncFileManager(with: .amazon), + alertManager: serviceFactory.makeAlertManager())) // set content view let contentNavigation = ContentNavigationVC() @@ -157,15 +163,15 @@ final class MainWireFrame: MainWireFrameProtocol, NynjaCommunicatorServiceDelega HistoryWireFrame().presentHistory(navigation: contentNavigation, mainWireFrame: self) } - func showScheduleMessage(with mode: ScheduledMessageMode, delegate: ScheduleMessageDelegate? = nil) { + func showScheduleMessage(with mode: ScheduledMessageMode) { if let navigation = self.navigation { ScheduleMessageWireFrame().presentScheduleMessage(navigation: navigation, main: self, mode: mode) } } - func showScheduleMessage(with inputMessage: InputScheduleMessage, delegate: ScheduleMessageDelegate? = nil) { + func showScheduleMessage(with inputMessage: InputScheduleMessage) { if let info = messageinteractor?.scheduleInfo(for: inputMessage) { - showScheduleMessage(with: .create(info: info), delegate: delegate) + showScheduleMessage(with: .create(info: info)) } } diff --git a/Nynja/Modules/Message/WireFrame/MessageWireframe.swift b/Nynja/Modules/Message/WireFrame/MessageWireframe.swift index bdb7e114f..71987cd8c 100644 --- a/Nynja/Modules/Message/WireFrame/MessageWireframe.swift +++ b/Nynja/Modules/Message/WireFrame/MessageWireframe.swift @@ -127,7 +127,7 @@ class MessageWireFrame: MessageWireframeProtocol, DocumentInteractionWireFrame { } func openSchedule(with inputMessage: InputScheduleMessage) { - main?.showScheduleMessage(with: inputMessage, delegate: nil) + main?.showScheduleMessage(with: inputMessage) } func deleteAndLeave() { diff --git a/Nynja/Modules/ScheduleMessage/Presenter/ScheduleMessagePresenter.swift b/Nynja/Modules/ScheduleMessage/Presenter/ScheduleMessagePresenter.swift index 781e36ed9..c99265c6c 100644 --- a/Nynja/Modules/ScheduleMessage/Presenter/ScheduleMessagePresenter.swift +++ b/Nynja/Modules/ScheduleMessage/Presenter/ScheduleMessagePresenter.swift @@ -8,8 +8,6 @@ final class ScheduleMessagePresenter: BasePresenter, ScheduleMessagePresenterProtocol, ScheduleMessageInteractorOutputProtocol, DateTimePickerDelegate, TimeZoneSelectorDelegate, ForwardSelectorDelegate, ConnectionServiceDelegate { - weak var delegate: ScheduleMessageDelegate? - weak var view: ScheduleMessageViewProtocol! var wireFrame: ScheduleMessageWireFrameProtocol! var interactor: ScheduleMessageInteractorInputProtocol! { @@ -96,7 +94,6 @@ final class ScheduleMessagePresenter: BasePresenter, ScheduleMessagePresenterPro } else { self.goBack() } - self.delegate?.scheduleMessageHasBeenSent() } } diff --git a/Nynja/Modules/ScheduleMessage/ScheduleMessageProtocols.swift b/Nynja/Modules/ScheduleMessage/ScheduleMessageProtocols.swift index fb6c9a4ab..848f0670e 100644 --- a/Nynja/Modules/ScheduleMessage/ScheduleMessageProtocols.swift +++ b/Nynja/Modules/ScheduleMessage/ScheduleMessageProtocols.swift @@ -13,15 +13,9 @@ enum ScheduledMessageMode { case edit(jobId: Int64) } -protocol ScheduleMessageDelegate: class { - - func scheduleMessageHasBeenSent() - -} - protocol ScheduleMessageWireFrameProtocol: class { - func presentScheduleMessage(navigation: UINavigationController, main: MainWireFrameProtocol?, mode: ScheduledMessageMode, delegate: ScheduleMessageDelegate?) + func presentScheduleMessage(navigation: UINavigationController, main: MainWireFrameProtocol?, mode: ScheduledMessageMode) /** * Add here your methods for communication PRESENTER -> WIREFRAME diff --git a/Nynja/Modules/ScheduleMessage/WireFrame/ScheduleMessageWireframe.swift b/Nynja/Modules/ScheduleMessage/WireFrame/ScheduleMessageWireframe.swift index 179084cb6..6de7d02c2 100644 --- a/Nynja/Modules/ScheduleMessage/WireFrame/ScheduleMessageWireframe.swift +++ b/Nynja/Modules/ScheduleMessage/WireFrame/ScheduleMessageWireframe.swift @@ -13,14 +13,12 @@ final class ScheduleMessageWireFrame: ScheduleMessageWireFrameProtocol { weak var main: MainWireFrameProtocol? weak var navigation: UINavigationController? - func presentScheduleMessage(navigation: UINavigationController, main: MainWireFrameProtocol?, mode: ScheduledMessageMode, delegate: ScheduleMessageDelegate? = nil) { + func presentScheduleMessage(navigation: UINavigationController, main: MainWireFrameProtocol?, mode: ScheduledMessageMode) { let view = ScheduleMessageViewController() let presenter = ScheduleMessagePresenter() let interactor = ScheduleMessageInteractor(mode: mode) - presenter.delegate = delegate - self.main = main self.navigation = navigation diff --git a/Nynja/ProfileHandlerDelegate.swift b/Nynja/ProfileHandlerDelegate.swift new file mode 100644 index 000000000..9fdd8ef67 --- /dev/null +++ b/Nynja/ProfileHandlerDelegate.swift @@ -0,0 +1,11 @@ +// +// ProfileHandlerDelegate.swift +// Nynja +// +// Created by Volodymyr Hryhoriev on 11/14/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +protocol ProfileHandlerDelegate: class { + func profileDeleted(with phone: String) +} diff --git a/Nynja/Services/HandleServices/ProfileHandler.swift b/Nynja/Services/HandleServices/ProfileHandler.swift index 72d0ba98a..9af1da022 100644 --- a/Nynja/Services/HandleServices/ProfileHandler.swift +++ b/Nynja/Services/HandleServices/ProfileHandler.swift @@ -6,7 +6,9 @@ // Copyright © 2017 TecSynt Solutions. All rights reserved. // -final class ProfileHandler: BaseHandler { +final class ProfileHandler: BaseHandler, StaticDelegating { + + static weak var delegate: ProfileHandlerDelegate? // MARK: - Dependencies @@ -25,10 +27,6 @@ final class ProfileHandler: BaseHandler { static var messageBackgroundTaskHandler: BackgroundTaskHandler { return MessageBackgroundTaskHandler() } - - static var alertManager: AlertManager { - return AlertManager.sharedInstance - } // MARK: - Handler @@ -183,9 +181,10 @@ final class ProfileHandler: BaseHandler { // MARK: Remove private static func handleRemove(_ profile: Profile) { - try? storageService.perform(action: .delete, with: profile) - storageService.phone = nil - alertManager.showAlertOk(message: String.localizable.authAttemptsRemoved) - mqttService.reconnect() + guard let phone = profile.phone else { + return + } + + delegate { $0.profileDeleted(with: phone) } } } diff --git a/Nynja/Services/MQTT/Extensions/MQTTService+QueuePool.swift b/Nynja/Services/MQTT/Extensions/MQTTService+QueuePool.swift index 0f29b39d9..dcbe6b5bd 100644 --- a/Nynja/Services/MQTT/Extensions/MQTTService+QueuePool.swift +++ b/Nynja/Services/MQTT/Extensions/MQTTService+QueuePool.swift @@ -16,13 +16,11 @@ extension MQTTService { attributes: .concurrent) let loggingQueue = DispatchQueue(label: String.label(withSuffix: "mqtt-service.logging-queue")) - let processingQueue = DispatchQueue(label: String.label(withSuffix: "mqtt-service.processing-queue")) + let processingQueue = DispatchQueue( + label: String.label(withSuffix: "mqtt-service.processing-queue"), + qos: .utility) let receivingQueue = DispatchQueue( label: String.label(withSuffix: "mqtt-service.receiving-queue"), qos: .utility) - - let publishingQueue = DispatchQueue( - label: String.label(withSuffix: "mqtt-service.publishing-queue"), - qos: .utility) } } diff --git a/Nynja/Services/MQTT/MQTTService.swift b/Nynja/Services/MQTT/MQTTService.swift index eea0985b4..df19f0adf 100644 --- a/Nynja/Services/MQTT/MQTTService.swift +++ b/Nynja/Services/MQTT/MQTTService.swift @@ -125,6 +125,7 @@ final class MQTTService: NSObject, MQTTServiceProtocol, CocoaMQTTDelegate, Conne func reconnect() { LogService.log(topic: .MQTT) { return "Reconnect" } + disconnect() connect() } @@ -139,8 +140,8 @@ final class MQTTService: NSObject, MQTTServiceProtocol, CocoaMQTTDelegate, Conne // MARK: - Publish func publish(model: BaseMQTTModel) { - queuePool.publishingQueue.async { [weak self] in - guard let mqtt = self?.mqtt, mqtt.connState == .connected else { + queuePool.processingQueue.async { [mqtt] in + guard let mqtt = mqtt, mqtt.connState == .connected else { return } diff --git a/Nynja/Services/PushService.swift b/Nynja/Services/PushService.swift index a3cac82de..1525a106b 100644 --- a/Nynja/Services/PushService.swift +++ b/Nynja/Services/PushService.swift @@ -33,9 +33,9 @@ final class PushService: NSObject, PKPushRegistryDelegate, UserSettingsRespondab // MARK: - Setup - func registerForPushNotifications() { + func registerForNotifications() { registerForNativeNotifications() - registerForPushNotifications() + registerForPushKitNotifications() } private func registerForNativeNotifications() { diff --git a/Nynja/Services/ServiceFactory/ServiceFactory.swift b/Nynja/Services/ServiceFactory/ServiceFactory.swift index 686feb212..6ca43a469 100644 --- a/Nynja/Services/ServiceFactory/ServiceFactory.swift +++ b/Nynja/Services/ServiceFactory/ServiceFactory.swift @@ -51,6 +51,8 @@ protocol ServiceFactoryProtocol: SharedServiceFactoryProtocol { func makeHomeDataProvider(limit: Int) -> HomeDataProvider + func makePushService() -> PushService + func makeNynjaCommunicatorService() -> NynjaCommunicatorService func makeBadgeNumberService() -> BadgeNumberServiceProtocol @@ -58,6 +60,8 @@ protocol ServiceFactoryProtocol: SharedServiceFactoryProtocol { func makeNotificationManager() -> NotificationManager func makeUserSettingsService() -> UserSettingsService + + func makeReachabilityService() -> ReachabilityService } final class ServiceFactory: SharedServiceFactory, ServiceFactoryProtocol { @@ -199,6 +203,10 @@ final class ServiceFactory: SharedServiceFactory, ServiceFactoryProtocol { limit: limit))) } + func makePushService() -> PushService { + return .sharedInstance + } + func makeNynjaCommunicatorService() -> NynjaCommunicatorService { return .sharedInstance } @@ -214,4 +222,8 @@ final class ServiceFactory: SharedServiceFactory, ServiceFactoryProtocol { func makeUserSettingsService() -> UserSettingsService { return .shared } + + func makeReachabilityService() -> ReachabilityService { + return .sharedInstance + } } diff --git a/Shared/Services/Handlers/IoHandler/IoHandler.swift b/Shared/Services/Handlers/IoHandler/IoHandler.swift index 4ee13a7eb..58a1b1f52 100644 --- a/Shared/Services/Handlers/IoHandler/IoHandler.swift +++ b/Shared/Services/Handlers/IoHandler/IoHandler.swift @@ -69,13 +69,6 @@ final class IoHandler: BaseHandler, StaticDelegating { delegate { $0.attemptsExpired() } case "number_not_allowed": delegate { $0.numberNotAllowed() } - case "logout": - LogService.log(topic: .db) { return "Clear storage: IoHandler" } - storageService.clearStorage() - - mqttService.disconnect() - mqttService.reconnect() - delegate { $0.logout() } case "phone": if let roster = IO.data as? Roster { if let contact = roster.userlist?.first { diff --git a/Shared/Services/Handlers/IoHandler/IoHandlerDelegate.swift b/Shared/Services/Handlers/IoHandler/IoHandlerDelegate.swift index 47393593b..7620fa52b 100644 --- a/Shared/Services/Handlers/IoHandler/IoHandlerDelegate.swift +++ b/Shared/Services/Handlers/IoHandler/IoHandlerDelegate.swift @@ -19,7 +19,7 @@ protocol IoHandlerDelegate: class { func added() func invalidData() func callInProgress() - func logout() + func didLogout() func numberNotAllowed() func sessionsCleared() func sessionDeleted() @@ -46,7 +46,7 @@ extension IoHandlerDelegate { func added() {} func invalidData() {} func callInProgress() {} - func logout() {} + func didLogout() {} func sessionsCleared() {} func sessionDeleted() {} -- GitLab From 1b6f31dd21de9a1129e874ea3bf54dda896fc90d Mon Sep 17 00:00:00 2001 From: Volodymyr Hryhoriev Date: Wed, 14 Nov 2018 17:53:06 +0200 Subject: [PATCH 57/68] Correct prev commit. --- .../Main/Interactor/MainInteractor.swift | 6 ++++-- .../Main/Presenter/MainPresenter.swift | 1 - .../Main/WireFrame/MainWireframe.swift | 20 +++++++++---------- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/Nynja/Modules/Main/Interactor/MainInteractor.swift b/Nynja/Modules/Main/Interactor/MainInteractor.swift index d5c63f015..16e1aa743 100644 --- a/Nynja/Modules/Main/Interactor/MainInteractor.swift +++ b/Nynja/Modules/Main/Interactor/MainInteractor.swift @@ -38,6 +38,7 @@ final class MainInteractor: BaseInteractor, MainInteractorInputProtocol, EditPho override func loadData() { super.loadData() + ProfileHandler.delegate = self pushService.registerForNotifications() mqttService.addSubscriber(self) setupIntercom() @@ -101,13 +102,14 @@ final class MainInteractor: BaseInteractor, MainInteractorInputProtocol, EditPho mqttService.deleteUser(number: phone) } LogService.log(topic: .db) { return "Clear storage: delete account" } - handleProfileDeleting() + presenter.showUILocker() } private func handleProfileDeleting() { cleanServices() alertManager.showAlertOk(message: String.localizable.authAttemptsRemoved) mqttService.reconnect() + presenter.hideUILocker() } private func cleanServices() { @@ -157,8 +159,8 @@ final class MainInteractor: BaseInteractor, MainInteractorInputProtocol, EditPho } } - // MARK: MQTT subscribing + // MARK: - MQTTServiceDelegate func mqttServiceDidConnect(_ mqttService: MQTTService) { dispatchAsyncMain { [weak self] in diff --git a/Nynja/Modules/Main/Presenter/MainPresenter.swift b/Nynja/Modules/Main/Presenter/MainPresenter.swift index 8aa2b1bfe..6a2574f2f 100644 --- a/Nynja/Modules/Main/Presenter/MainPresenter.swift +++ b/Nynja/Modules/Main/Presenter/MainPresenter.swift @@ -240,7 +240,6 @@ final class MainPresenter: BasePresenter, MainPresenterProtocol, MainInteractorO func deleteAccount() { reachabilityService.performIfConnected { interactor.deleteAccount() - changeScreenToAuth() } } diff --git a/Nynja/Modules/Main/WireFrame/MainWireframe.swift b/Nynja/Modules/Main/WireFrame/MainWireframe.swift index 9d439b980..2b962c3fd 100644 --- a/Nynja/Modules/Main/WireFrame/MainWireframe.swift +++ b/Nynja/Modules/Main/WireFrame/MainWireframe.swift @@ -43,16 +43,16 @@ final class MainWireFrame: MainWireFrameProtocol, NynjaCommunicatorServiceDelega interactor.inject( dependencies: .init( - presenter: presenter, - storageService: serviceFactory.makeStorageService(), - mqttService: serviceFactory.makeMQTTService(), - pushService: serviceFactory.makePushService(), - communicatorService: serviceFactory.makeNynjaCommunicatorService(), - badgeNumberService: serviceFactory.makeBadgeNumberService(), - notificationManager: serviceFactory.makeNotificationManager(), - userSettingsService: serviceFactory.makeUserSettingsService(), - syncFileManager: serviceFactory.makeSyncFileManager(with: .amazon), - alertManager: serviceFactory.makeAlertManager())) + presenter: presenter, + storageService: serviceFactory.makeStorageService(), + mqttService: serviceFactory.makeMQTTService(), + pushService: serviceFactory.makePushService(), + communicatorService: serviceFactory.makeNynjaCommunicatorService(), + badgeNumberService: serviceFactory.makeBadgeNumberService(), + notificationManager: serviceFactory.makeNotificationManager(), + userSettingsService: serviceFactory.makeUserSettingsService(), + syncFileManager: serviceFactory.makeSyncFileManager(with: .amazon), + alertManager: serviceFactory.makeAlertManager())) // set content view let contentNavigation = ContentNavigationVC() -- GitLab From e26ad6a9788fdd6fafe570348c74997f07c03d1e Mon Sep 17 00:00:00 2001 From: Volodymyr Hryhoriev Date: Wed, 14 Nov 2018 17:53:28 +0200 Subject: [PATCH 58/68] Fix crash in `MQTTService` subscription. --- Nynja/Services/MQTT/MQTTService.swift | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Nynja/Services/MQTT/MQTTService.swift b/Nynja/Services/MQTT/MQTTService.swift index df19f0adf..bf6a52ce3 100644 --- a/Nynja/Services/MQTT/MQTTService.swift +++ b/Nynja/Services/MQTT/MQTTService.swift @@ -245,14 +245,18 @@ final class MQTTService: NSObject, MQTTServiceProtocol, CocoaMQTTDelegate, Conne // MARK: Subscribers func addSubscriber(_ subscriber: MQTTServiceDelegate) { - queuePool.subscribersQueue.async(flags: .barrier) { [unowned self] in - self.subscribers.append(WeakRef(value: subscriber)) + queuePool.subscribersQueue.async(flags: .barrier) { [unowned self, weak subscriber] in + if let subscriber = subscriber { + self.subscribers.append(WeakRef(value: subscriber)) + } } } func removeSubscriber(_ subscriber: MQTTServiceDelegate) { - queuePool.subscribersQueue.async(flags: .barrier) { [unowned self] in - self.subscribers = self.subscribers.filter { $0.value !== subscriber } + queuePool.subscribersQueue.async(flags: .barrier) { [unowned self, weak subscriber] in + if let subscriber = subscriber { + self.subscribers = self.subscribers.filter { $0.value !== subscriber } + } } } -- GitLab From e7ed3ab2bec2a235e9e787a246a988527c52e897 Mon Sep 17 00:00:00 2001 From: Volodymyr Hryhoriev Date: Wed, 14 Nov 2018 18:08:14 +0200 Subject: [PATCH 59/68] Make small refactoring inside some handlers. --- Nynja/MemberHandler.swift | 6 +++++- .../HandleServices/ProfileHandler.swift | 19 +++++++++++-------- .../Services/HandleServices/RoomHandler.swift | 4 ++-- 3 files changed, 18 insertions(+), 11 deletions(-) diff --git a/Nynja/MemberHandler.swift b/Nynja/MemberHandler.swift index 2332ee5a9..d6a524011 100644 --- a/Nynja/MemberHandler.swift +++ b/Nynja/MemberHandler.swift @@ -8,6 +8,10 @@ class MemberHandler: BaseHandler { + private static var storageService: StorageService { + return .sharedInstance + } + static func executeHandle(data: BertTuple) { guard let member = get_Member().parse(bert: data) as? Member, let status = (member.status as? StringAtom)?.string else { @@ -20,7 +24,7 @@ class MemberHandler: BaseHandler { member.status = oldMember.status } - try? StorageService.sharedInstance.perform(action: .save, with: member) + try? storageService.perform(action: .save, with: member) default: return } diff --git a/Nynja/Services/HandleServices/ProfileHandler.swift b/Nynja/Services/HandleServices/ProfileHandler.swift index 9af1da022..88d150871 100644 --- a/Nynja/Services/HandleServices/ProfileHandler.swift +++ b/Nynja/Services/HandleServices/ProfileHandler.swift @@ -12,22 +12,25 @@ final class ProfileHandler: BaseHandler, StaticDelegating { // MARK: - Dependencies - static var mqttService: MQTTService { - return MQTTService.sharedInstance + private static var mqttService: MQTTService { + return .sharedInstance } - static var historyFactory: HistoryRequestModelFactoryProtocol { + private static var historyFactory: HistoryRequestModelFactoryProtocol { return HistoryRequestModelFactory() } - static var storageService: StorageService { - return StorageService.sharedInstance + private static var storageService: StorageService { + return .sharedInstance } - static var messageBackgroundTaskHandler: BackgroundTaskHandler { + private static var messageBackgroundTaskHandler: BackgroundTaskHandler { return MessageBackgroundTaskHandler() } + private static var communicatorService: NynjaCommunicatorService { + return .sharedInstance + } // MARK: - Handler @@ -156,7 +159,7 @@ final class ProfileHandler: BaseHandler, StaticDelegating { return } - NynjaCommunicatorService.sharedInstance.initialize() + communicatorService.initialize() } private static func requestJobs(with phoneId: String) { @@ -177,7 +180,7 @@ final class ProfileHandler: BaseHandler, StaticDelegating { } } - + // MARK: Remove private static func handleRemove(_ profile: Profile) { diff --git a/Nynja/Services/HandleServices/RoomHandler.swift b/Nynja/Services/HandleServices/RoomHandler.swift index 64928d097..a29be4c96 100644 --- a/Nynja/Services/HandleServices/RoomHandler.swift +++ b/Nynja/Services/HandleServices/RoomHandler.swift @@ -10,11 +10,11 @@ final class RoomHandler: BaseHandler { // MARK: - Dependencies - static var storageService: StorageService { + private static var storageService: StorageService { return .sharedInstance } - static var notificationManager: NotificationManager { + private static var notificationManager: NotificationManager { return .shared } -- GitLab From 11ac03a801db63c1860e138cc708fed70c1f9e19 Mon Sep 17 00:00:00 2001 From: Volodymyr Hryhoriev Date: Wed, 14 Nov 2018 18:26:45 +0200 Subject: [PATCH 60/68] Make small changes inside `ChatService` and `SystemSoundManager`. --- Nynja/ChatService/ChatService.swift | 4 +++- .../Audio/SystemSoundManager/SystemSoundManager.swift | 10 +++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/Nynja/ChatService/ChatService.swift b/Nynja/ChatService/ChatService.swift index 622c682f0..2cfad147c 100644 --- a/Nynja/ChatService/ChatService.swift +++ b/Nynja/ChatService/ChatService.swift @@ -10,7 +10,9 @@ final class ChatService { // MARK: - Dependencies - private static let storageService = StorageService.sharedInstance + private static var storageService: StorageService { + return .sharedInstance + } private static let messageParser = MessageParser(dependencies: .init(storageService: storageService)) diff --git a/Nynja/Services/Audio/SystemSoundManager/SystemSoundManager.swift b/Nynja/Services/Audio/SystemSoundManager/SystemSoundManager.swift index 8291eea75..bb06789be 100644 --- a/Nynja/Services/Audio/SystemSoundManager/SystemSoundManager.swift +++ b/Nynja/Services/Audio/SystemSoundManager/SystemSoundManager.swift @@ -47,11 +47,11 @@ final class SystemSoundManager { // MARK: - Play Sound func playSound(with url: URL) { - guard canPlaySound(with: url) else { - return - } - - dispatchAsyncMain { + dispatchAsyncMain { [unowned self] in + guard self.canPlaySound(with: url) else { + return + } + let soundId = self.makeSoundId(with: url) self.cachedSounds[url] = SoundInfo(soundId: soundId, lastTimeSoundPlayed: CFAbsoluteTimeGetCurrent()) AudioServicesPlaySystemSound(soundId) -- GitLab From d04b4a3a94c85d428ad70837fa1cbb8bc44c143d Mon Sep 17 00:00:00 2001 From: Volodymyr Hryhoriev Date: Wed, 14 Nov 2018 18:33:09 +0200 Subject: [PATCH 61/68] 1. Update `HistoryHandler`. 2. Remove unused code from `StickersDownloadingService`. --- Nynja/Services/HandleServices/HistoryHandler.swift | 6 +++--- .../StickersDownloadingService.swift | 13 ------------- 2 files changed, 3 insertions(+), 16 deletions(-) diff --git a/Nynja/Services/HandleServices/HistoryHandler.swift b/Nynja/Services/HandleServices/HistoryHandler.swift index 4b8bb4a73..4a7180294 100644 --- a/Nynja/Services/HandleServices/HistoryHandler.swift +++ b/Nynja/Services/HandleServices/HistoryHandler.swift @@ -42,15 +42,15 @@ final class HistoryHandler: BaseHandler { // MARK: - Dependencies - static var storageService: StorageService { + private static var storageService: StorageService { return StorageService.sharedInstance } - static var messageEditService: MessageEditServiceProtocol { + private static var messageEditService: MessageEditServiceProtocol { return MessageEditService(dependencies: .init(storageService: storageService)) } - static let stickersDownloadingService: StickersDownloadingService = { + private static let stickersDownloadingService: StickersDownloadingService = { return StickersDownloadingService() }() diff --git a/Nynja/Services/StickersDownloadingService/StickersDownloadingService.swift b/Nynja/Services/StickersDownloadingService/StickersDownloadingService.swift index 438562db6..5eb64229b 100644 --- a/Nynja/Services/StickersDownloadingService/StickersDownloadingService.swift +++ b/Nynja/Services/StickersDownloadingService/StickersDownloadingService.swift @@ -11,19 +11,6 @@ import SDWebImage final class StickersDownloadingService { - private let operationQueue: OperationQueue - - - // MARK: - Init - - init() { - operationQueue = OperationQueue() - operationQueue.maxConcurrentOperationCount = 1 - } - - - // MARK: - Prefetching - func download(_ packages: [StickerPack]) { var urls = [URL]() for package in packages { -- GitLab From 86074338edbd6813e60f0422e0ec29338116c2c7 Mon Sep 17 00:00:00 2001 From: Volodymyr Hryhoriev Date: Thu, 15 Nov 2018 11:49:17 +0200 Subject: [PATCH 62/68] Use `Bundle.main` instead of `Bundle(for: ...)`. --- Nynja/Library/UI/Extensions/String/StringExtensions.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Nynja/Library/UI/Extensions/String/StringExtensions.swift b/Nynja/Library/UI/Extensions/String/StringExtensions.swift index 01ba0b492..75ad7b8a7 100644 --- a/Nynja/Library/UI/Extensions/String/StringExtensions.swift +++ b/Nynja/Library/UI/Extensions/String/StringExtensions.swift @@ -383,6 +383,6 @@ extension String { extension String { static func label(withSuffix suffix: String) -> String { - return Bundle(for: ServiceFactory.self).bundleIdentifier.appending(suffix) + return Bundle.main.bundleIdentifier.appending(suffix) } } -- GitLab From b779b37266858327a2508549413ca62f57349370 Mon Sep 17 00:00:00 2001 From: Volodymyr Hryhoriev Date: Thu, 15 Nov 2018 15:00:55 +0200 Subject: [PATCH 63/68] Correct naming of methods which returns plain models in `ContactDAO` and `RoomDAO` one. --- Nynja/ContactDAO.swift | 24 +++++++++++-------- Nynja/ContactDAOProtocol.swift | 13 +++++----- Nynja/ContactsProvider.swift | 6 ++--- Nynja/ConversationsProvider.swift | 6 ++--- .../Models/Member/MemberExtension.swift | 2 +- Nynja/JobService.swift | 4 ++-- .../AddContactByUsernameInteractor.swift | 2 +- .../AddContactViaPhoneInteractor.swift | 2 +- .../AddParticipantsInteractor.swift | 2 +- .../SubscribersSelectorInteractor.swift | 2 +- .../Interactor/ContactsInteractor.swift | 4 ++-- .../ForwardSelectorInteractor.swift | 2 +- .../Interactor/ParticipantsInteractor.swift | 2 +- .../Interactor/QRCodeReaderInteractor.swift | 2 +- .../Interactor/PrivacyListInteractor.swift | 2 +- Nynja/RoomDAO.swift | 4 ++-- Nynja/RoomDAOProtocol.swift | 4 ++-- 17 files changed, 44 insertions(+), 39 deletions(-) diff --git a/Nynja/ContactDAO.swift b/Nynja/ContactDAO.swift index a14579f80..27b611e77 100644 --- a/Nynja/ContactDAO.swift +++ b/Nynja/ContactDAO.swift @@ -15,6 +15,7 @@ import GRDBCipher class ContactDAO: ContactDAOProtocol { + // MARK: - Fetch // MARK: -- Contact static var currentContact: Contact? { @@ -34,7 +35,7 @@ class ContactDAO: ContactDAOProtocol { } } - static func findContactBy(phone: String) -> Contact? { + static func findPlainContactBy(phone: String) -> Contact? { return dbManager.fetch { db in return try DBContact .filter(Column.phoneId.like("\(phone)%")) @@ -46,15 +47,18 @@ class ContactDAO: ContactDAOProtocol { return fetchContact(by: phoneId)?.serverModel } - static func findContactBy(username: String) -> Contact? { + static func findPlainContactBy(username: String) -> Contact? { return dbManager.fetch { db in - return try DBContact.filter(Column.nick == username).fetchOne(db) + return try DBContact + .filter(Column.nick == username) + .fetchOne(db) }?.serverModel } // MARK: -- Contacts - static func fetchContacts() -> [Contact] { + + static func fetchPlainContacts() -> [Contact] { guard let rosterId = StorageService.sharedInstance.rosterId else { return [] } @@ -66,19 +70,19 @@ class ContactDAO: ContactDAOProtocol { }.serverModels } - static func fetchContactWithoutSelf() -> [Contact] { + static func fetchPlainContactsWithoutSelf() -> [Contact] { var contacts: [Contact] = [] if let phoneId = StorageService.sharedInstance.phoneId { - contacts = fetchContacts(with: [phoneId], isExcluded: true) + contacts = fetchPlainContacts(with: [phoneId], isExcluded: true) } else { - contacts = fetchContacts() + contacts = fetchPlainContacts() } return contacts } - static func fetchContacts(with phoneIds: [String], isExcluded: Bool = false) -> [Contact] { + static func fetchPlainContacts(with phoneIds: [String], isExcluded: Bool = false) -> [Contact] { return dbManager.fetch { db -> [DBContact] in var predicate = phoneIds.contains(Column.phoneId) if isExcluded { @@ -89,7 +93,7 @@ class ContactDAO: ContactDAOProtocol { }.serverModels } - static func fetchContacts(with statuses: [Contact.Status]) -> [Contact] { + static func fetchPlainContacts(with statuses: [Contact.Status]) -> [Contact] { let statuses = statuses.map { $0.rawValue } return dbManager.fetch { db -> [DBContact] in @@ -105,7 +109,7 @@ class ContactDAO: ContactDAOProtocol { }.serverModels } - static func fetchContacts( + static func fetchPlainContacts( with args: RosterRelatedQueryArgs, excludingIds ids: [String], excludingStatuses statuses: [Contact.Status]?) -> [Contact] { diff --git a/Nynja/ContactDAOProtocol.swift b/Nynja/ContactDAOProtocol.swift index 0442ec4a4..4543120ad 100644 --- a/Nynja/ContactDAOProtocol.swift +++ b/Nynja/ContactDAOProtocol.swift @@ -16,19 +16,20 @@ protocol ContactDAOProtocol: DAOProtocol { static func fetchContact(by id: String) -> DBContact? static func fetchContact(by rowId: Int64) -> DBContact? + static func findPlainContactBy(phone: String) -> Contact? static func findContactBy(phoneId: String) -> Contact? - static func findContactBy(username: String) -> Contact? + static func findPlainContactBy(username: String) -> Contact? // MARK: -- Contacts - static func fetchContacts() -> [Contact] - static func fetchContactWithoutSelf() -> [Contact] - static func fetchContacts(with phoneIds: [String], isExcluded: Bool) -> [Contact] - static func fetchContacts(with statuses: [Contact.Status]) -> [Contact] + static func fetchPlainContacts() -> [Contact] + static func fetchPlainContactsWithoutSelf() -> [Contact] + static func fetchPlainContacts(with phoneIds: [String], isExcluded: Bool) -> [Contact] + static func fetchPlainContacts(with statuses: [Contact.Status]) -> [Contact] static func fetchContacts(with args: RosterRelatedQueryArgs) -> [Contact] - static func fetchContacts(with args: RosterRelatedQueryArgs, + static func fetchPlainContacts(with args: RosterRelatedQueryArgs, excludingIds ids: [String], excludingStatuses statuses: [Contact.Status]?) -> [Contact] diff --git a/Nynja/ContactsProvider.swift b/Nynja/ContactsProvider.swift index 48ecf9345..16c6c5f0e 100644 --- a/Nynja/ContactsProvider.swift +++ b/Nynja/ContactsProvider.swift @@ -15,7 +15,7 @@ final class ContactsProvider: ContactsProviding { func fetchHistory(with args: FetchingArgs) -> [Contact] { let daoArgs = makeRosterRelatedQueryArgs(from: args) - return ContactDAO.fetchContacts(with: daoArgs, + return ContactDAO.fetchPlainContacts(with: daoArgs, excludingIds: [args.phoneId], excludingStatuses: args.statuses) } @@ -29,13 +29,13 @@ final class ContactsProvider: ContactsProviding { func fetchFriends() -> [Contact] { return ContactDAO - .fetchContacts(with: [.friend, .banned, .ban]) + .fetchPlainContacts(with: [.friend, .banned, .ban]) .withoutSelf } func fetchFriendsWithoutBlocked() -> [Contact] { return ContactDAO - .fetchContacts(with: [.friend]) + .fetchPlainContacts(with: [.friend]) .withoutSelf } } diff --git a/Nynja/ConversationsProvider.swift b/Nynja/ConversationsProvider.swift index 13e1a5026..f5d011edf 100644 --- a/Nynja/ConversationsProvider.swift +++ b/Nynja/ConversationsProvider.swift @@ -28,7 +28,7 @@ class ConversationsProvider: ConversationsProviding, InitializeInjectable { func fetchGroups(with args: FetchingArgs) -> [Room] { let groupRequestArgs = makeGroupsRequestArgs(from: args, kind: .group) - return RoomDAO.fetchRooms(with: groupRequestArgs) + return RoomDAO.fetchPlainRooms(with: groupRequestArgs) } @@ -36,12 +36,12 @@ class ConversationsProvider: ConversationsProviding, InitializeInjectable { func fetchChannels(with args: ConversationsProviding.FetchingArgs) -> [Room] { let channelRequestArgs = makeGroupsRequestArgs(from: args, kind: .channel) - return RoomDAO.fetchRooms(with: channelRequestArgs) + return RoomDAO.fetchPlainRooms(with: channelRequestArgs) } func fetchMyChannels(with args: ConversationsProviding.FetchingArgs, ownerId: String) -> [Room] { let channelRequestArgs = makeGroupsRequestArgs(from: args, kind: .channel, ownerId: ownerId) - return RoomDAO.fetchRooms(with: channelRequestArgs) + return RoomDAO.fetchPlainRooms(with: channelRequestArgs) } private func makeGroupsRequestArgs(from args: FetchingArgs, kind: Room.Kind, ownerId: String? = nil) -> DBRoom.RequestArgs { diff --git a/Nynja/Extensions/Models/Member/MemberExtension.swift b/Nynja/Extensions/Models/Member/MemberExtension.swift index c6cfa1f5d..ef11c8e04 100644 --- a/Nynja/Extensions/Models/Member/MemberExtension.swift +++ b/Nynja/Extensions/Models/Member/MemberExtension.swift @@ -104,7 +104,7 @@ extension Array where Element == Member { var contacts: [Contact] { let phoneIds = compactMap { $0.phone_id } - let tempContacts = ContactDAO.fetchContacts(with: phoneIds) + let tempContacts = ContactDAO.fetchPlainContacts(with: phoneIds) var contacts: [Contact] = [] diff --git a/Nynja/JobService.swift b/Nynja/JobService.swift index b7777de5b..1ccd48c05 100644 --- a/Nynja/JobService.swift +++ b/Nynja/JobService.swift @@ -16,12 +16,12 @@ class JobService { private static func fetchContacts(for job: Job) -> [Contact] { let ids = job.contactsIds - return ContactDAO.fetchContacts(with: ids) + return ContactDAO.fetchPlainContacts(with: ids) } private static func fetchRooms(for job: Job) -> [Room] { let ids = job.roomsIds - return RoomDAO.fetchRooms(with: ids) + return RoomDAO.fetchPlainRooms(with: ids) } } diff --git a/Nynja/Modules/AddContactByUsername/Interactor/AddContactByUsernameInteractor.swift b/Nynja/Modules/AddContactByUsername/Interactor/AddContactByUsernameInteractor.swift index 21d47a38e..1882fbe24 100644 --- a/Nynja/Modules/AddContactByUsername/Interactor/AddContactByUsernameInteractor.swift +++ b/Nynja/Modules/AddContactByUsername/Interactor/AddContactByUsernameInteractor.swift @@ -33,7 +33,7 @@ final class AddContactByUsernameInteractor: AddContactByUsernameInteractorInputP func getContactByUserName(username: String) { searchAction = nil - if let contact = ContactDAO.findContactBy(username: username) { + if let contact = ContactDAO.findPlainContactBy(username: username) { if contact.phoneId == StorageService.sharedInstance.phoneId { presenter.getMyProfile() } else { diff --git a/Nynja/Modules/AddContactViaPhone/Interactor/AddContactViaPhoneInteractor.swift b/Nynja/Modules/AddContactViaPhone/Interactor/AddContactViaPhoneInteractor.swift index b3e89801c..3e9ba94c0 100644 --- a/Nynja/Modules/AddContactViaPhone/Interactor/AddContactViaPhoneInteractor.swift +++ b/Nynja/Modules/AddContactViaPhone/Interactor/AddContactViaPhoneInteractor.swift @@ -36,7 +36,7 @@ final class AddContactViaPhoneInteractor: AddContactViaPhoneInteractorInputProto if storageService.phone == number { presenter.getMyProfile() - } else if let contact = ContactDAO.findContactBy(phone: number) { + } else if let contact = ContactDAO.findPlainContactBy(phone: number) { presenter.getContactByPhoneSuccess(contact: contact) } else { searchAction = { [weak self] in diff --git a/Nynja/Modules/AddParticipants/Interactor/AddParticipantsInteractor.swift b/Nynja/Modules/AddParticipants/Interactor/AddParticipantsInteractor.swift index 7c4c2b1a7..7536ea662 100644 --- a/Nynja/Modules/AddParticipants/Interactor/AddParticipantsInteractor.swift +++ b/Nynja/Modules/AddParticipants/Interactor/AddParticipantsInteractor.swift @@ -133,7 +133,7 @@ class AddParticipantsInteractor: BaseInteractor, AddParticipantsInteractorInputP //MARK: - AddParticipantsInteractorInputProtocol func fetchParticipants(`for` mode: ParticipantsModeType) { - var contacts = ContactDAO.fetchContacts(with: [.friend]) + var contacts = ContactDAO.fetchPlainContacts(with: [.friend]) if mode == .delete || mode == .admins { if let members = members { diff --git a/Nynja/Modules/Channel/SubscribersSelector/Interactor/SubscribersSelectorInteractor.swift b/Nynja/Modules/Channel/SubscribersSelector/Interactor/SubscribersSelectorInteractor.swift index 4aaa35199..06301f43a 100644 --- a/Nynja/Modules/Channel/SubscribersSelector/Interactor/SubscribersSelectorInteractor.swift +++ b/Nynja/Modules/Channel/SubscribersSelector/Interactor/SubscribersSelectorInteractor.swift @@ -57,7 +57,7 @@ final class SubscribersSelectorInteractor: BaseInteractor, SubscribersSelectorIn private func members(from subscribers: [ChannelSubscriber]) -> [Member] { let ids = subscribers.map { $0.id } return ContactDAO - .fetchContacts(with: ids) + .fetchPlainContacts(with: ids) .map { Member(contact: $0) } } diff --git a/Nynja/Modules/Contacts/Interactor/ContactsInteractor.swift b/Nynja/Modules/Contacts/Interactor/ContactsInteractor.swift index 5b0a313da..bf794f695 100644 --- a/Nynja/Modules/Contacts/Interactor/ContactsInteractor.swift +++ b/Nynja/Modules/Contacts/Interactor/ContactsInteractor.swift @@ -75,12 +75,12 @@ class ContactsInteractor: BaseInteractor, ContactsInteractorInputProtocol, IoHan //MARK: - Private private func fetchFriends() { - contacts = ContactDAO.fetchContactWithoutSelf() + contacts = ContactDAO.fetchPlainContactsWithoutSelf() showHistory(contacts: contacts) } private func fetchAll() { - contacts = ContactDAO.fetchContacts() + contacts = ContactDAO.fetchPlainContacts() showHistory(contacts: contacts) } diff --git a/Nynja/Modules/ForwardSelector/Interactor/ForwardSelectorInteractor.swift b/Nynja/Modules/ForwardSelector/Interactor/ForwardSelectorInteractor.swift index 532b3c548..271f881b9 100644 --- a/Nynja/Modules/ForwardSelector/Interactor/ForwardSelectorInteractor.swift +++ b/Nynja/Modules/ForwardSelector/Interactor/ForwardSelectorInteractor.swift @@ -112,7 +112,7 @@ final class ForwardSelectorInteractor: BaseInteractor, ForwardSelectorInteractor if let contacts = self.contacts { completion(contacts) } else { - let contacts = ContactDAO.fetchContacts(with: [.friend]) + let contacts = ContactDAO.fetchPlainContacts(with: [.friend]) .map { makeForwardTarget(with: $0) } .sorted(by: sortComparator) diff --git a/Nynja/Modules/Participants/Interactor/ParticipantsInteractor.swift b/Nynja/Modules/Participants/Interactor/ParticipantsInteractor.swift index babd3b0c4..5e2db5099 100644 --- a/Nynja/Modules/Participants/Interactor/ParticipantsInteractor.swift +++ b/Nynja/Modules/Participants/Interactor/ParticipantsInteractor.swift @@ -84,7 +84,7 @@ class ParticipantsInteractor: BaseInteractor, ParticipantsInteractorInputProtoco //MARK: - Private private func loadContacts() { - let contacts = ContactDAO.fetchContacts(with: [.friend, .ban, .banned]) + let contacts = ContactDAO.fetchPlainContacts(with: [.friend, .ban, .banned]) mapToParticipants(contacts) } diff --git a/Nynja/Modules/QRCodeReader/Interactor/QRCodeReaderInteractor.swift b/Nynja/Modules/QRCodeReader/Interactor/QRCodeReaderInteractor.swift index 60cf171cd..0f7e8eaaf 100644 --- a/Nynja/Modules/QRCodeReader/Interactor/QRCodeReaderInteractor.swift +++ b/Nynja/Modules/QRCodeReader/Interactor/QRCodeReaderInteractor.swift @@ -21,7 +21,7 @@ class QRCodeReaderInteractor: QRCodeReaderInteractorInputProtocol, IoHandlerDele currentNumer = number if StorageService.sharedInstance.phone == number { self.presenter.getMyProfile() - } else if let contact = ContactDAO.findContactBy(phone: number) { + } else if let contact = ContactDAO.findPlainContactBy(phone: number) { self.presenter.getContactSuccess(contact: contact) } else { MQTTService.sharedInstance.tryFindContact(number: number, modelReference: .QRCODE) diff --git a/Nynja/Modules/Settings/Privacy/Interactor/PrivacyListInteractor.swift b/Nynja/Modules/Settings/Privacy/Interactor/PrivacyListInteractor.swift index f549baa56..05273084c 100644 --- a/Nynja/Modules/Settings/Privacy/Interactor/PrivacyListInteractor.swift +++ b/Nynja/Modules/Settings/Privacy/Interactor/PrivacyListInteractor.swift @@ -13,7 +13,7 @@ final class PrivacyListInteractor: BaseInteractor, PrivacyListInteractorInputPr weak var presenter: PrivacyListInteractorOutputProtocol! private var filterText: String? private var contacts: [Contact] { - return ContactDAO.fetchContacts(with: [.banned]) + return ContactDAO.fetchPlainContacts(with: [.banned]) } // MARK: - PrivacyListInteractorInputProtocol diff --git a/Nynja/RoomDAO.swift b/Nynja/RoomDAO.swift index e4ef5c136..6a4612ff9 100644 --- a/Nynja/RoomDAO.swift +++ b/Nynja/RoomDAO.swift @@ -37,13 +37,13 @@ class RoomDAO: RoomDAOProtocol { // MARK: -- Rooms - static func fetchRooms(with ids: [String], kind: Room.Kind? = nil) -> [Room] { + static func fetchPlainRooms(with ids: [String], kind: Room.Kind? = nil) -> [Room] { return dbManager.fetch { db -> [DBRoom] in return try DBRoom.rooms(db, ids: ids, type: kind?.rawValue) }.serverModels } - static func fetchRooms(with args: DBRoom.RequestArgs) -> [Room] { + static func fetchPlainRooms(with args: DBRoom.RequestArgs) -> [Room] { return dbManager.fetch { db in return try DBRoom.rooms(db, args: args) }.serverModels diff --git a/Nynja/RoomDAOProtocol.swift b/Nynja/RoomDAOProtocol.swift index eaa3f1db0..15da8a221 100644 --- a/Nynja/RoomDAOProtocol.swift +++ b/Nynja/RoomDAOProtocol.swift @@ -19,8 +19,8 @@ protocol RoomDAOProtocol: DAOProtocol { // MARK: -- Rooms - static func fetchRooms(with ids: [String], kind: Room.Kind?) -> [Room] - static func fetchRooms(with args: DBRoom.RequestArgs) -> [Room] + static func fetchPlainRooms(with ids: [String], kind: Room.Kind?) -> [Room] + static func fetchPlainRooms(with args: DBRoom.RequestArgs) -> [Room] // MARK: -- Reader -- GitLab From ce501e5f4506e761c19cbc8281d35883ea40db24 Mon Sep 17 00:00:00 2001 From: Volodymyr Hryhoriev Date: Thu, 15 Nov 2018 15:32:05 +0200 Subject: [PATCH 64/68] Remove ambiguous methods such as `request(...)` inside `DBContact` which returns request connected with `DBFeature`. --- Nynja/DB/Models/DBContact.swift | 12 ++++-------- Nynja/DB/Models/DBDesc.swift | 2 +- Nynja/DB/Models/DBFeature.swift | 5 +---- Nynja/DB/Models/DBJob.swift | 7 ++----- Nynja/DB/Models/DBMember.swift | 10 +++------- Nynja/DB/Models/DBProfile.swift | 14 ++++---------- Nynja/DB/Models/DBRoom.swift | 10 +++------- Nynja/DB/Models/DBService.swift | 3 +++ 8 files changed, 21 insertions(+), 42 deletions(-) diff --git a/Nynja/DB/Models/DBContact.swift b/Nynja/DB/Models/DBContact.swift index e9f681f79..9dc20c5a2 100644 --- a/Nynja/DB/Models/DBContact.swift +++ b/Nynja/DB/Models/DBContact.swift @@ -146,8 +146,8 @@ final class DBContact: Record, DBExtendedModel { @discardableResult func deleteAggregate(_ db: Database) throws -> Bool { - try DBContact.request(targetId: phoneId).deleteAll(db) - try DBService.request(targetId: phoneId, targetType: .contact).deleteAll(db) + try DBFeature.deleteAll(db, targetId: phoneId, targetType: .contact) + try DBService.deleteAll(db, targetId: phoneId, targetType: .contact) if let phoneId = StorageService.sharedInstance.phoneId { try DBP2p.delete(db, firstId: phoneId, secondId: phoneId) @@ -193,16 +193,12 @@ final class DBContact: Record, DBExtendedModel { message = try DBMessage.message(db, localId: messageId) } - features = (try? DBContact.request(targetId: self.phoneId).fetchAll(db)) ?? [] - services = try DBService.request(targetId: self.phoneId, targetType: .contact).fetchAll(db) + features = (try? DBFeature.request(targetId: phoneId, targetType: .contact).fetchAll(db)) ?? [] + services = try DBService.request(targetId: phoneId, targetType: .contact).fetchAll(db) } // MARK: - Request - static private func request(targetId: String) -> QueryInterfaceRequest { - return DBFeature.request(targetId: targetId, targetType: DBFeature.TargetType.contact) - } - static func request(phoneId: String) -> QueryInterfaceRequest { return DBContact.filter(Column(ContactTable.Column.phoneId.title) == phoneId) } diff --git a/Nynja/DB/Models/DBDesc.swift b/Nynja/DB/Models/DBDesc.swift index b8884b2eb..eb003f7b1 100644 --- a/Nynja/DB/Models/DBDesc.swift +++ b/Nynja/DB/Models/DBDesc.swift @@ -106,7 +106,7 @@ final class DBDesc: Record, DBModel { private func deleteFeatures(_ db: Database) throws { let featureTarget = DBDesc.featureTarget(for: targetType) - try DBFeature.request(targetId: serverId, targetType: featureTarget).deleteAll(db) + try DBFeature.deleteAll(db, targetId: serverId, targetType: featureTarget) } static func deleteAll(_ db: Database, targetId: String, targetType: TargetType) throws { diff --git a/Nynja/DB/Models/DBFeature.swift b/Nynja/DB/Models/DBFeature.swift index 90116c1c8..260150295 100644 --- a/Nynja/DB/Models/DBFeature.swift +++ b/Nynja/DB/Models/DBFeature.swift @@ -30,10 +30,7 @@ final class DBFeature: Codable { } static func deleteAll(_ db: Database, targetId: String, targetType: TargetType) throws { - let targetIdColumn = Column(FeatureTable.Column.targetId.title) - let targetTypeColumn = Column(FeatureTable.Column.targetType.title) - - try DBFeature.filter(targetIdColumn == targetId && targetTypeColumn == targetType.rawValue).deleteAll(db) + try request(targetId: targetId, targetType: targetType).deleteAll(db) } } diff --git a/Nynja/DB/Models/DBJob.swift b/Nynja/DB/Models/DBJob.swift index 74b6205f2..199ef98d4 100644 --- a/Nynja/DB/Models/DBJob.swift +++ b/Nynja/DB/Models/DBJob.swift @@ -115,7 +115,7 @@ final class DBJob: Record, DBExtendedModel { func construct(_ db: Database) throws { if let jobId = self.id { self.messages = try DBJobMessage.messages(db, jobId: jobId) - self.features = try DBJob.requestFeature(jobId: String(describing: jobId)).fetchAll(db) + self.features = try DBFeature.request(targetId: "\(jobId)", targetType: .job).fetchAll(db) } } @@ -152,7 +152,7 @@ final class DBJob: Record, DBExtendedModel { guard let jobId = tempId else { return false } self.id = jobId - try DBJob.requestFeature(jobId: String(describing: jobId)).deleteAll(db) + try DBFeature.deleteAll(db, targetId: "\(jobId)", targetType: .job) try deleteMessages(db, jobId: jobId) return try self.delete(db) @@ -174,9 +174,6 @@ final class DBJob: Record, DBExtendedModel { } // MARK: - Make Requests - static func requestFeature(jobId: String) -> QueryInterfaceRequest { - return DBFeature.request(targetId: jobId, targetType: .job) - } static func request(jobId: Int64) -> QueryInterfaceRequest { return DBJob.filter(Column.id == jobId) diff --git a/Nynja/DB/Models/DBMember.swift b/Nynja/DB/Models/DBMember.swift index 52aa7fb66..abbba7181 100644 --- a/Nynja/DB/Models/DBMember.swift +++ b/Nynja/DB/Models/DBMember.swift @@ -169,7 +169,7 @@ final class DBMember: Record, DBModel { func construct(_ db: Database) throws { let memberId = "\(self.id)" - self.features = (try? DBMember.requestFeature(targetId: memberId).fetchAll(db)) ?? [] + self.features = (try? DBFeature.request(targetId: memberId, targetType: .member).fetchAll(db)) ?? [] self.services = (try? DBService.request(targetId: memberId, targetType: .member).fetchAll(db)) ?? [] if let feedId = self.feedId, let feedType = self.feedType { @@ -185,17 +185,13 @@ final class DBMember: Record, DBModel { @discardableResult func deleteAggregate(_ db: Database) throws -> Bool { let memberId = "\(self.id)" - try DBMember.requestFeature(targetId: memberId).deleteAll(db) - - try? DBService.request(targetId: memberId, targetType: .member).deleteAll(db) + try DBFeature.deleteAll(db, targetId: memberId, targetType: .member) + try? DBService.deleteAll(db, targetId: memberId, targetType: .member) return try self.delete(db) } // MARK: - Requests - static private func requestFeature(targetId: String) -> QueryInterfaceRequest { - return DBFeature.request(targetId: targetId, targetType: DBFeature.TargetType.member) - } static private func requestMember(roomId: String, phoneIds: [String]) -> AnyTypedRequest { let memberTable = MemberTable.name diff --git a/Nynja/DB/Models/DBProfile.swift b/Nynja/DB/Models/DBProfile.swift index bc714e10b..5c89cf9ff 100644 --- a/Nynja/DB/Models/DBProfile.swift +++ b/Nynja/DB/Models/DBProfile.swift @@ -80,23 +80,17 @@ final class DBProfile: Record, DBModel { dbProfile.services = try DBService.request(targetId: dbProfile.phone, targetType: .profile).fetchAll(db) dbProfile.rosters = try DBRoster.rosters(from: db, profileId: dbProfile.phone) - dbProfile.features = try request(targetId: dbProfile.phone).fetchAll(db) + dbProfile.features = try DBFeature.request(targetId: dbProfile.phone, targetType: .profile).fetchAll(db) return dbProfile } @discardableResult func deleteAggregate(_ db: Database) throws -> Bool { - try DBProfile.request(targetId: self.phone).deleteAll(db) + try DBFeature.deleteAll(db, targetId: phone, targetType: .profile) + try DBService.deleteAll(db, targetId: phone, targetType: .profile) - try DBService.request(targetId: self.phone, targetType: .profile).deleteAll(db) - - return try self.delete(db) - } - - static private func request(targetId: String) -> QueryInterfaceRequest { - return DBFeature.request(targetId: targetId, targetType: DBFeature.TargetType.profile) + return try delete(db) } - } diff --git a/Nynja/DB/Models/DBRoom.swift b/Nynja/DB/Models/DBRoom.swift index 87d8890e8..1424c99d2 100644 --- a/Nynja/DB/Models/DBRoom.swift +++ b/Nynja/DB/Models/DBRoom.swift @@ -177,8 +177,8 @@ final class DBRoom: Record, DBExtendedModel { @discardableResult func deleteAggregate(_ db: Database) throws -> Bool { - try DBRoom.requestFeature(targetId: self.id).deleteAll(db) - try DBDesc.deleteAll(db, targetId: self.id, targetType: .room) + try DBFeature.deleteAll(db, targetId: id, targetType: .room) + try DBDesc.deleteAll(db, targetId: id, targetType: .room) try DBMuc.filter(MucTable.Column.name.title == self.name).deleteAll(db) return try self.delete(db) } @@ -248,7 +248,7 @@ final class DBRoom: Record, DBExtendedModel { try constructMembers(db, phoneIds: phoneIds) - self.features = (try? DBRoom.requestFeature(targetId: id).fetchAll(db)) ?? [] + self.features = (try? DBFeature.request(targetId: id, targetType: .room).fetchAll(db)) ?? [] self.files = DBRoom.fetchDescs(db, roomId: id) self.links = (try? DBLink.links(db, roomId: id)) ?? [] } @@ -284,10 +284,6 @@ final class DBRoom: Record, DBExtendedModel { // MARK: - Make Requests - static private func requestFeature(targetId: String) -> QueryInterfaceRequest { - return DBFeature.request(targetId: targetId, targetType: .room) - } - static private func descs(targetId: String, db: Database) throws -> [DBDesc] { return try DBDesc.descs(targetId: targetId, targetType: .room, db: db) } diff --git a/Nynja/DB/Models/DBService.swift b/Nynja/DB/Models/DBService.swift index d098688d1..849aca8ae 100644 --- a/Nynja/DB/Models/DBService.swift +++ b/Nynja/DB/Models/DBService.swift @@ -58,6 +58,9 @@ final class DBService: Codable { self.targetType = targetType.rawValue } + static func deleteAll(_ db: Database, targetId: String, targetType: TargetType) throws { + try request(targetId: targetId, targetType: targetType).deleteAll(db) + } } extension DBService: RowConvertible, Persistable { -- GitLab From 2dcafe9b1d7446b8e283e71a2377d4807d9ffe1a Mon Sep 17 00:00:00 2001 From: Volodymyr Hryhoriev Date: Fri, 16 Nov 2018 11:15:31 +0200 Subject: [PATCH 65/68] Add `Release-Debug` scheme. --- .../NynjaUIKit.xcodeproj/project.pbxproj | 178 +++++++++++++++++ Nynja.xcodeproj/project.pbxproj | 182 ++++++++++++++++++ Nynja/Resources/ReleaseConfig.xcconfig | 2 +- 3 files changed, 361 insertions(+), 1 deletion(-) diff --git a/Frameworks/NynjaUIKit/NynjaUIKit.xcodeproj/project.pbxproj b/Frameworks/NynjaUIKit/NynjaUIKit.xcodeproj/project.pbxproj index 4b37699ee..6c1fa3dd9 100644 --- a/Frameworks/NynjaUIKit/NynjaUIKit.xcodeproj/project.pbxproj +++ b/Frameworks/NynjaUIKit/NynjaUIKit.xcodeproj/project.pbxproj @@ -36,6 +36,7 @@ /* Begin PBXFileReference section */ 1820AD65D6897E3E306C16A2 /* Pods-NynjaUIKit.translate.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NynjaUIKit.translate.xcconfig"; path = "../../Pods/Target Support Files/Pods-NynjaUIKit/Pods-NynjaUIKit.translate.xcconfig"; sourceTree = ""; }; + 1D38BAE43B22E6C930DB3980 /* Pods-NynjaUIKit.release-debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NynjaUIKit.release-debug.xcconfig"; path = "../../Pods/Target Support Files/Pods-NynjaUIKit/Pods-NynjaUIKit.release-debug.xcconfig"; sourceTree = ""; }; 600B9308D041B8E4DE0DBEF1 /* Pods-NynjaUIKit.loaddb.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NynjaUIKit.loaddb.xcconfig"; path = "../../Pods/Target Support Files/Pods-NynjaUIKit/Pods-NynjaUIKit.loaddb.xcconfig"; sourceTree = ""; }; 7336042AC840197E622730FD /* Pods-NynjaUIKit.prerelease.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NynjaUIKit.prerelease.xcconfig"; path = "../../Pods/Target Support Files/Pods-NynjaUIKit/Pods-NynjaUIKit.prerelease.xcconfig"; sourceTree = ""; }; 8514D4C120EE27080002378A /* NynjaUIKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = NynjaUIKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -67,6 +68,7 @@ B90E6396110C47D18FB00838 /* Pods-NynjaUIKit.dev.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NynjaUIKit.dev.xcconfig"; path = "../../Pods/Target Support Files/Pods-NynjaUIKit/Pods-NynjaUIKit.dev.xcconfig"; sourceTree = ""; }; C6C80841C9BA48F16147BAAE /* Pods_NynjaUIKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_NynjaUIKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; C90742AD8E6E2E817F7DB1E9 /* Pods-NynjaUIKit.channels.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NynjaUIKit.channels.xcconfig"; path = "../../Pods/Target Support Files/Pods-NynjaUIKit/Pods-NynjaUIKit.channels.xcconfig"; sourceTree = ""; }; + E05C445073991174833FDBC7 /* Pods-NynjaUIKit.spotify.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NynjaUIKit.spotify.xcconfig"; path = "../../Pods/Target Support Files/Pods-NynjaUIKit/Pods-NynjaUIKit.spotify.xcconfig"; sourceTree = ""; }; E966C049045184395EBB9166 /* Pods-NynjaUIKit.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NynjaUIKit.release.xcconfig"; path = "../../Pods/Target Support Files/Pods-NynjaUIKit/Pods-NynjaUIKit.release.xcconfig"; sourceTree = ""; }; F6A55196057E1D4A2F24AC17 /* Pods-NynjaUIKit.devautotests.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NynjaUIKit.devautotests.xcconfig"; path = "../../Pods/Target Support Files/Pods-NynjaUIKit/Pods-NynjaUIKit.devautotests.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -93,6 +95,8 @@ E966C049045184395EBB9166 /* Pods-NynjaUIKit.release.xcconfig */, 7336042AC840197E622730FD /* Pods-NynjaUIKit.prerelease.xcconfig */, 600B9308D041B8E4DE0DBEF1 /* Pods-NynjaUIKit.loaddb.xcconfig */, + E05C445073991174833FDBC7 /* Pods-NynjaUIKit.spotify.xcconfig */, + 1D38BAE43B22E6C930DB3980 /* Pods-NynjaUIKit.release-debug.xcconfig */, ); name = Pods; sourceTree = ""; @@ -521,6 +525,176 @@ }; name = LoadDB; }; + 4B31B845219DE57F00837B59 /* Release-Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = E966C049045184395EBB9166 /* Pods-NynjaUIKit.release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = "Release-Debug"; + }; + 4B31B846219DE57F00837B59 /* Release-Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 1D38BAE43B22E6C930DB3980 /* Pods-NynjaUIKit.release-debug.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Manual; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = NynjaUIKit/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.nynja.mobile.communicator.NynjaUIKit; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SKIP_INSTALL = YES; + SWIFT_VERSION = 4.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = "Release-Debug"; + }; + 4B31B847219DE58D00837B59 /* Spotify */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = B90E6396110C47D18FB00838 /* Pods-NynjaUIKit.dev.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Spotify; + }; + 4B31B848219DE58D00837B59 /* Spotify */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = E05C445073991174833FDBC7 /* Pods-NynjaUIKit.spotify.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Manual; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = NynjaUIKit/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.nynja.mobile.communicator.NynjaUIKit; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SKIP_INSTALL = YES; + SWIFT_VERSION = 4.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Spotify; + }; 8514D4C820EE27080002378A /* Dev */ = { isa = XCBuildConfiguration; baseConfigurationReference = B90E6396110C47D18FB00838 /* Pods-NynjaUIKit.dev.xcconfig */; @@ -868,9 +1042,11 @@ isa = XCConfigurationList; buildConfigurations = ( 8514D4C820EE27080002378A /* Dev */, + 4B31B847219DE58D00837B59 /* Spotify */, 2611CEEC21807A6E00FFD4DD /* LoadDB */, 85631BF220EFC50F0002BE51 /* DevAutoTests */, 85631BF020EFC4FC0002BE51 /* Release */, + 4B31B845219DE57F00837B59 /* Release-Debug */, 85631BEE20EFC4F30002BE51 /* Prerelease */, ); defaultConfigurationIsVisible = 0; @@ -880,9 +1056,11 @@ isa = XCConfigurationList; buildConfigurations = ( 8514D4CB20EE27080002378A /* Dev */, + 4B31B848219DE58D00837B59 /* Spotify */, 2611CEED21807A6E00FFD4DD /* LoadDB */, 85631BF320EFC50F0002BE51 /* DevAutoTests */, 85631BF120EFC4FC0002BE51 /* Release */, + 4B31B846219DE57F00837B59 /* Release-Debug */, 85631BEF20EFC4F30002BE51 /* Prerelease */, ); defaultConfigurationIsVisible = 0; diff --git a/Nynja.xcodeproj/project.pbxproj b/Nynja.xcodeproj/project.pbxproj index b1b2af8e0..5e4df7aa5 100644 --- a/Nynja.xcodeproj/project.pbxproj +++ b/Nynja.xcodeproj/project.pbxproj @@ -3473,6 +3473,7 @@ 9BD8E41220F3A2E2001384EC /* CallInProgressInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallInProgressInteractor.swift; sourceTree = ""; }; 9BE521212189B2E10070C664 /* ThreeButtonHeaderView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThreeButtonHeaderView.swift; sourceTree = ""; }; 9BFFE61A2178DCFF004FE2CA /* BannerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BannerView.swift; sourceTree = ""; }; + 9C2E07BBF40570582F4258A3 /* Pods-Nynja.release-debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Nynja.release-debug.xcconfig"; path = "Pods/Target Support Files/Pods-Nynja/Pods-Nynja.release-debug.xcconfig"; sourceTree = ""; }; 9C4192D925259B75441492A9 /* FavoritesPresenter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = FavoritesPresenter.swift; sourceTree = ""; }; 9DE44A136617140435B23343 /* GroupStorageWireframe.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = GroupStorageWireframe.swift; sourceTree = ""; }; 9E82188EE0AC1D1C05470692 /* Pods-NynjaUnitTests.channels.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NynjaUnitTests.channels.xcconfig"; path = "Pods/Target Support Files/Pods-NynjaUnitTests/Pods-NynjaUnitTests.channels.xcconfig"; sourceTree = ""; }; @@ -3891,6 +3892,7 @@ B28D1FE755A0457DBEDAC068 /* MapSearchProtocols.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = MapSearchProtocols.swift; sourceTree = ""; }; B2B221F69CB3D5C6A1B12456 /* VideoPreviewInteractor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = VideoPreviewInteractor.swift; sourceTree = ""; }; B2EF4EFCD3C9DE0B69FC40F9 /* TimeZoneSelectorWireframe.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = TimeZoneSelectorWireframe.swift; sourceTree = ""; }; + B3D64F28A0B75CE6D74100B1 /* Pods-Nynja-Share.release-debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Nynja-Share.release-debug.xcconfig"; path = "Pods/Target Support Files/Pods-Nynja-Share/Pods-Nynja-Share.release-debug.xcconfig"; sourceTree = ""; }; B4DC9EF9D78D3F5C48B00EF3 /* ScheduleMessageWireframe.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ScheduleMessageWireframe.swift; sourceTree = ""; }; B62646CA6345B6C5AD0C87A0 /* ScheduleMessagePresenter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ScheduleMessagePresenter.swift; sourceTree = ""; }; B7121EB7205045F300AABBE6 /* MediaDownloadManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaDownloadManager.swift; sourceTree = ""; }; @@ -3997,6 +3999,7 @@ CBE3BAC9B7EA418FB463EF04 /* EditUsernameInteractor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = EditUsernameInteractor.swift; sourceTree = ""; }; CCA291E1CE928BC100DD6353 /* Pods-Nynja-Share.translate.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Nynja-Share.translate.xcconfig"; path = "Pods/Target Support Files/Pods-Nynja-Share/Pods-Nynja-Share.translate.xcconfig"; sourceTree = ""; }; CDF62E1A004579220E231142 /* LoginProtocols.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = LoginProtocols.swift; sourceTree = ""; }; + CEBE788F463F17C3CCD8302A /* Pods-NynjaUnitTests.release-debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NynjaUnitTests.release-debug.xcconfig"; path = "Pods/Target Support Files/Pods-NynjaUnitTests/Pods-NynjaUnitTests.release-debug.xcconfig"; sourceTree = ""; }; D1AE7296B9A53355289740D1 /* ProfilePresenter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ProfilePresenter.swift; sourceTree = ""; }; D1D5302025583482829BBF2E /* GroupStorageViewController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = GroupStorageViewController.swift; sourceTree = ""; }; D270F638DBB2D8FC1BDEB633 /* ProfileViewController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ProfileViewController.swift; sourceTree = ""; }; @@ -7727,6 +7730,9 @@ 7ADCB0C891B31AF691307B4F /* Pods-Nynja.spotify.xcconfig */, 7F7FC209C7703E3E7617D782 /* Pods-Nynja-Share.spotify.xcconfig */, A0A57BD401783039D49B7B75 /* Pods-NynjaUnitTests.spotify.xcconfig */, + 9C2E07BBF40570582F4258A3 /* Pods-Nynja.release-debug.xcconfig */, + B3D64F28A0B75CE6D74100B1 /* Pods-Nynja-Share.release-debug.xcconfig */, + CEBE788F463F17C3CCD8302A /* Pods-NynjaUnitTests.release-debug.xcconfig */, ); name = Pods; sourceTree = ""; @@ -17359,6 +17365,7 @@ PROVISIONING_PROFILE = "2a318f9e-d0ab-41dc-968a-e1cb13de4de5"; PROVISIONING_PROFILE_SPECIFIER = ProductionBundle_AppstoreExt; SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = RELEASE; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 4.0; @@ -17546,6 +17553,176 @@ }; name = Release; }; + 4B31B83F219DE52000837B59 /* Release-Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = F1313B0020888CC400E04092 /* ReleaseConfig.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 10.3; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_SWIFT3_OBJC_INFERENCE = Off; + SWIFT_WHOLE_MODULE_OPTIMIZATION = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = "Release-Debug"; + }; + 4B31B840219DE52000837B59 /* Release-Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9C2E07BBF40570582F4258A3 /* Pods-Nynja.release-debug.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(inherited)"; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Nynja/Resources/Nynja.entitlements; + CODE_SIGN_IDENTITY = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; + CODE_SIGN_STYLE = Manual; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 9GKQ5AMF2B; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = "$(SRCROOT)/Nynja/Resources/Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "$(BundleIdentifier)"; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE = "1971b115-2b2c-48b6-a20f-3686249e0a88"; + PROVISIONING_PROFILE_SPECIFIER = ProductionBundle_Appstore; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OBJC_BRIDGING_HEADER = "Nynja-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_SWIFT3_OBJC_INFERENCE = Off; + SWIFT_VERSION = 4.0; + SWIFT_WHOLE_MODULE_OPTIMIZATION = YES; + }; + name = "Release-Debug"; + }; + 4B31B841219DE52000837B59 /* Release-Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = B3D64F28A0B75CE6D74100B1 /* Pods-Nynja-Share.release-debug.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(inherited)"; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_ENTITLEMENTS = "Nynja-Share/Resources/Nynja-Share.entitlements"; + CODE_SIGN_IDENTITY = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; + CODE_SIGN_STYLE = Manual; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_TEAM = 9GKQ5AMF2B; + ENABLE_BITCODE = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = "Nynja-Share/Resources/Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; + OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" -DSHARE_EXTENSION"; + PRODUCT_BUNDLE_IDENTIFIER = "$(ExtensionBundleIdentifier)"; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE = "2a318f9e-d0ab-41dc-968a-e1cb13de4de5"; + PROVISIONING_PROFILE_SPECIFIER = ProductionBundle_AppstoreExt; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 4.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = "Release-Debug"; + }; + 4B31B842219DE52000837B59 /* Release-Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = CEBE788F463F17C3CCD8302A /* Pods-NynjaUnitTests.release-debug.xcconfig */; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 9GKQ5AMF2B; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = NynjaUnitTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 11.3; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.nynja.mobile.communicator.NynjaUnitTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_VERSION = 4.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = "Release-Debug"; + }; + 4B31B843219DE52000837B59 /* Release-Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 9GKQ5AMF2B; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = NynjaIntegrationTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 11.3; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.nynja.dev.mobile.communicator.NynjaIntegrationTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 4.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Nynja.app/Nynja"; + }; + name = "Release-Debug"; + }; 5B4DFF6D2191A08E00E89D17 /* Spotify */ = { isa = XCBuildConfiguration; baseConfigurationReference = 5B4DFF6C21919FA000E89D17 /* SpotifyConfig.xcconfig */; @@ -18400,6 +18577,7 @@ F10AFE9E20EF8BBE00C7CE83 /* DevAutoTests */, F1313AFD20888BD300E04092 /* Prerelease */, 357809AF1F9765CF00C9680C /* Release */, + 4B31B841219DE52000837B59 /* Release-Debug */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; @@ -18413,6 +18591,7 @@ F10AFE9C20EF8BBE00C7CE83 /* DevAutoTests */, F1313AFB20888BD300E04092 /* Prerelease */, 3ABCE8FE1EC9330D00A80B15 /* Release */, + 4B31B83F219DE52000837B59 /* Release-Debug */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; @@ -18426,6 +18605,7 @@ F10AFE9D20EF8BBE00C7CE83 /* DevAutoTests */, F1313AFC20888BD300E04092 /* Prerelease */, 3ABCE9011EC9330D00A80B15 /* Release */, + 4B31B840219DE52000837B59 /* Release-Debug */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; @@ -18439,6 +18619,7 @@ F10AFE9F20EF8BBE00C7CE83 /* DevAutoTests */, F1C37AB1209A1BF4005EA197 /* Prerelease */, F1C37AB2209A1BF4005EA197 /* Release */, + 4B31B842219DE52000837B59 /* Release-Debug */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; @@ -18452,6 +18633,7 @@ FE21ACB02113AA7F006010A0 /* DevAutoTests */, FE21ACB32113AA7F006010A0 /* Prerelease */, FE21ACB42113AA7F006010A0 /* Release */, + 4B31B843219DE52000837B59 /* Release-Debug */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; diff --git a/Nynja/Resources/ReleaseConfig.xcconfig b/Nynja/Resources/ReleaseConfig.xcconfig index f8b763d10..f65b25f2e 100644 --- a/Nynja/Resources/ReleaseConfig.xcconfig +++ b/Nynja/Resources/ReleaseConfig.xcconfig @@ -14,7 +14,7 @@ AppName = NYNJA ServerPort = 8443 Config = release AppGroup = group.com.nynja.mobile.communicator -ModelsVersion = 9 +ModelsVersion = 10 isServerConnectionSecure = true ConfServerAddress = call.nynja.net ConfServerPort = 443 -- GitLab From 0f37d305d5eed8f4cb5c182642f223d133332ca6 Mon Sep 17 00:00:00 2001 From: Volodymyr Hryhoriev Date: Fri, 16 Nov 2018 20:58:32 +0200 Subject: [PATCH 66/68] Make working with subscribers thread-safe inside `DBObserver`. --- Nynja/DBObserver.swift | 4 ++ Nynja/Improvements/StorageObserver.swift | 47 +++++++++++++++++------- 2 files changed, 37 insertions(+), 14 deletions(-) diff --git a/Nynja/DBObserver.swift b/Nynja/DBObserver.swift index 33b411fbf..d7efa23fb 100644 --- a/Nynja/DBObserver.swift +++ b/Nynja/DBObserver.swift @@ -20,9 +20,13 @@ final class DBObserver: StorageObserver, TransactionObserver { label: String.label(withSuffix: "db-observer.processing-queue"), qos: .utility) + // MARK: - Properties + var subscribers: [SubscribeType: [StorageSubscriberReference]] = [:] + let isolationQueue = DispatchQueue(label: String.label(withSuffix: "db-observer.isolation-queue")) + private var allChanges: Dictionary = [:] private let tableNames: [String] = { diff --git a/Nynja/Improvements/StorageObserver.swift b/Nynja/Improvements/StorageObserver.swift index 83d8b76a8..299312bc0 100644 --- a/Nynja/Improvements/StorageObserver.swift +++ b/Nynja/Improvements/StorageObserver.swift @@ -9,6 +9,8 @@ protocol StorageObserver : class { var subscribers: [SubscribeType: [StorageSubscriberReference]] { get set } + var isolationQueue: DispatchQueue { get } + func register(subscriber: StorageSubscriber, type: SubscribeType) func unregister(subscriber: StorageSubscriber, type: SubscribeType) func unregister(subscribers type: SubscribeType) @@ -19,12 +21,18 @@ protocol StorageObserver : class { extension StorageObserver { func register(subscriber: StorageSubscriber, type: SubscribeType) { - let reference = StorageSubscriberReference(subscriber) - if var subs = subscribers[type] { - subs.append(reference) - subscribers[type] = subs - } else { - subscribers[type] = [reference] + isolationQueue.async { [weak self] in + guard let `self` = self else { + return + } + + let reference = StorageSubscriberReference(subscriber) + if var subs = self.subscribers[type] { + subs.append(reference) + self.subscribers[type] = subs + } else { + self.subscribers[type] = [reference] + } } } @@ -35,11 +43,19 @@ extension StorageObserver { } func unregister(subscriber: StorageSubscriber, type: SubscribeType) { - subscribers[type] = subscribers[type]?.filter { $0.subscriber != nil && $0.subscriber?.id != subscriber.id } + isolationQueue.async { [weak self] in + guard let `self` = self else { + return + } + + self.subscribers[type] = self.subscribers[type]?.filter { $0.subscriber != nil && $0.subscriber?.id != subscriber.id } + } } func unregister(subscribers type: SubscribeType) { - subscribers.removeValue(forKey: type) + isolationQueue.async { [weak self] in + self?.subscribers.removeValue(forKey: type) + } } func unregister(subscriber: StorageSubscriber) { @@ -49,12 +65,15 @@ extension StorageObserver { } func notify(with changes: [StorageChange], type: SubscribeType) { - guard let subscribers = subscribers[type] else { - return - } - for subscriberRef in subscribers { - dispatchAsyncMain { - subscriberRef.subscriber?.update(with: changes, type: type) + isolationQueue.sync { [weak self] in + guard let subscribers = self?.subscribers[type] else { + return + } + + for subscriberRef in subscribers { + dispatchAsyncMain { + subscriberRef.subscriber?.update(with: changes, type: type) + } } } } -- GitLab From f9cc47d277512949947ddab6f37a7998f985a400 Mon Sep 17 00:00:00 2001 From: Volodymyr Hryhoriev Date: Fri, 16 Nov 2018 21:01:14 +0200 Subject: [PATCH 67/68] 1. Correct usage of `subscribersQueue` inside `MQTTService`. 2. Remove unnecessary usage of `dispatchMainAsync`. --- Nynja.xcodeproj/project.pbxproj | 1 + Nynja/Modules/Main/Interactor/MainInteractor.swift | 10 +++------- .../MQTT/Extensions/MQTTService+QueuePool.swift | 5 +---- Nynja/Services/MQTT/MQTTService.swift | 6 +++--- 4 files changed, 8 insertions(+), 14 deletions(-) diff --git a/Nynja.xcodeproj/project.pbxproj b/Nynja.xcodeproj/project.pbxproj index 5e4df7aa5..e2f9099bb 100644 --- a/Nynja.xcodeproj/project.pbxproj +++ b/Nynja.xcodeproj/project.pbxproj @@ -17626,6 +17626,7 @@ INFOPLIST_FILE = "$(SRCROOT)/Nynja/Resources/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 10.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + OTHER_SWIFT_FLAGS = "$(inherited) -D SQLITE_HAS_CODEC -D GRDBCIPHER $(inherited) \"-D\" \"COCOAPODS\" -Xfrontend -debug-time-function-bodies -Xfrontend -warn-long-expression-type-checking=20 -Xfrontend -warn-long-function-bodies=20"; PRODUCT_BUNDLE_IDENTIFIER = "$(BundleIdentifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = "1971b115-2b2c-48b6-a20f-3686249e0a88"; diff --git a/Nynja/Modules/Main/Interactor/MainInteractor.swift b/Nynja/Modules/Main/Interactor/MainInteractor.swift index 16e1aa743..244af25b0 100644 --- a/Nynja/Modules/Main/Interactor/MainInteractor.swift +++ b/Nynja/Modules/Main/Interactor/MainInteractor.swift @@ -163,16 +163,12 @@ final class MainInteractor: BaseInteractor, MainInteractorInputProtocol, EditPho // MARK: - MQTTServiceDelegate func mqttServiceDidConnect(_ mqttService: MQTTService) { - dispatchAsyncMain { [weak self] in - self?.presenter.hideUILocker() - } + presenter.hideUILocker() } func mqttServiceDidReceiveAuthenticationFailure(_ mqttService: MQTTService) { - dispatchAsyncMain { [weak self] in - self?.logout() - self?.presenter.changeScreenToAuth() - } + logout() + presenter.changeScreenToAuth() } private func setupIntercom() { diff --git a/Nynja/Services/MQTT/Extensions/MQTTService+QueuePool.swift b/Nynja/Services/MQTT/Extensions/MQTTService+QueuePool.swift index dcbe6b5bd..81257299c 100644 --- a/Nynja/Services/MQTT/Extensions/MQTTService+QueuePool.swift +++ b/Nynja/Services/MQTT/Extensions/MQTTService+QueuePool.swift @@ -11,10 +11,7 @@ import Foundation extension MQTTService { struct QueuePool { - let subscribersQueue = DispatchQueue( - label: String.label(withSuffix: "mqtt-service.subscribers-queue"), - attributes: .concurrent) - + let subscribersQueue = DispatchQueue(label: String.label(withSuffix: "mqtt-service.subscribers-queue")) let loggingQueue = DispatchQueue(label: String.label(withSuffix: "mqtt-service.logging-queue")) let processingQueue = DispatchQueue( label: String.label(withSuffix: "mqtt-service.processing-queue"), diff --git a/Nynja/Services/MQTT/MQTTService.swift b/Nynja/Services/MQTT/MQTTService.swift index bf6a52ce3..9272ea851 100644 --- a/Nynja/Services/MQTT/MQTTService.swift +++ b/Nynja/Services/MQTT/MQTTService.swift @@ -245,7 +245,7 @@ final class MQTTService: NSObject, MQTTServiceProtocol, CocoaMQTTDelegate, Conne // MARK: Subscribers func addSubscriber(_ subscriber: MQTTServiceDelegate) { - queuePool.subscribersQueue.async(flags: .barrier) { [unowned self, weak subscriber] in + queuePool.subscribersQueue.async { [unowned self, weak subscriber] in if let subscriber = subscriber { self.subscribers.append(WeakRef(value: subscriber)) } @@ -253,7 +253,7 @@ final class MQTTService: NSObject, MQTTServiceProtocol, CocoaMQTTDelegate, Conne } func removeSubscriber(_ subscriber: MQTTServiceDelegate) { - queuePool.subscribersQueue.async(flags: .barrier) { [unowned self, weak subscriber] in + queuePool.subscribersQueue.async { [unowned self, weak subscriber] in if let subscriber = subscriber { self.subscribers = self.subscribers.filter { $0.value !== subscriber } } @@ -261,7 +261,7 @@ final class MQTTService: NSObject, MQTTServiceProtocol, CocoaMQTTDelegate, Conne } func removeAllSubscribers() { - queuePool.subscribersQueue.async(flags: .barrier) { [unowned self] in + queuePool.subscribersQueue.async { [unowned self] in self.subscribers.removeAll() } } -- GitLab From 8c67d39b79d8d691a2152b2c16aef4cc28385f69 Mon Sep 17 00:00:00 2001 From: Volodymyr Hryhoriev Date: Mon, 19 Nov 2018 17:13:16 +0200 Subject: [PATCH 68/68] Use 'main queue' for work with responses. --- Nynja.xcodeproj/project.pbxproj | 14 +++++++------- .../MQTT/Extensions/MQTTService+QueuePool.swift | 8 +++++--- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/Nynja.xcodeproj/project.pbxproj b/Nynja.xcodeproj/project.pbxproj index e2f9099bb..0f0747971 100644 --- a/Nynja.xcodeproj/project.pbxproj +++ b/Nynja.xcodeproj/project.pbxproj @@ -17349,7 +17349,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_ENTITLEMENTS = "Nynja-Share/Resources/Nynja-Share.entitlements"; - CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; @@ -17529,7 +17529,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Nynja/Resources/Nynja.entitlements; - CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; @@ -17617,7 +17617,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Nynja/Resources/Nynja.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Manual; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; @@ -17629,8 +17629,8 @@ OTHER_SWIFT_FLAGS = "$(inherited) -D SQLITE_HAS_CODEC -D GRDBCIPHER $(inherited) \"-D\" \"COCOAPODS\" -Xfrontend -debug-time-function-bodies -Xfrontend -warn-long-expression-type-checking=20 -Xfrontend -warn-long-function-bodies=20"; PRODUCT_BUNDLE_IDENTIFIER = "$(BundleIdentifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE = "1971b115-2b2c-48b6-a20f-3686249e0a88"; - PROVISIONING_PROFILE_SPECIFIER = ProductionBundle_Appstore; + PROVISIONING_PROFILE = "e08a86e7-d3c6-4351-bfa9-7e563b2b7cba"; + PROVISIONING_PROFILE_SPECIFIER = ProductionBundle_Dev; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OBJC_BRIDGING_HEADER = "Nynja-Bridging-Header.h"; @@ -17662,8 +17662,8 @@ OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" -DSHARE_EXTENSION"; PRODUCT_BUNDLE_IDENTIFIER = "$(ExtensionBundleIdentifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE = "2a318f9e-d0ab-41dc-968a-e1cb13de4de5"; - PROVISIONING_PROFILE_SPECIFIER = ProductionBundle_AppstoreExt; + PROVISIONING_PROFILE = "3e08568d-8d8f-426c-bf4c-0f12f19252e5"; + PROVISIONING_PROFILE_SPECIFIER = ProductionBundle_DevExt; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_COMPILATION_MODE = wholemodule; diff --git a/Nynja/Services/MQTT/Extensions/MQTTService+QueuePool.swift b/Nynja/Services/MQTT/Extensions/MQTTService+QueuePool.swift index 81257299c..077d7db17 100644 --- a/Nynja/Services/MQTT/Extensions/MQTTService+QueuePool.swift +++ b/Nynja/Services/MQTT/Extensions/MQTTService+QueuePool.swift @@ -16,8 +16,10 @@ extension MQTTService { let processingQueue = DispatchQueue( label: String.label(withSuffix: "mqtt-service.processing-queue"), qos: .utility) - let receivingQueue = DispatchQueue( - label: String.label(withSuffix: "mqtt-service.receiving-queue"), - qos: .utility) + let receivingQueue = DispatchQueue.main + // NOTE: can't use background queue because of undetermined crash. +// DispatchQueue( +// label: String.label(withSuffix: "mqtt-service.receiving-queue"), +// qos: .utility) } } -- GitLab