diff --git a/Nynja-Share/Services/LogService/LogService.swift b/Nynja-Share/Services/LogService/LogService.swift new file mode 100644 index 0000000000000000000000000000000000000000..449c24e4fcdc2d8d4adbba693edbdbc80bfe8e74 --- /dev/null +++ b/Nynja-Share/Services/LogService/LogService.swift @@ -0,0 +1,14 @@ +// +// LogService.swift +// Nynja-Share +// +// Created by Volodymyr Hryhoriev on 12/6/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + +// NOTE: Stub for NynjaUnitTests target. +final class LogService: LogServiceProtocol { + static func log(topic: LogServiceTopic, block: (() -> String)?) {} +} diff --git a/Nynja.xcodeproj/project.pbxproj b/Nynja.xcodeproj/project.pbxproj index a572cf8e3c38e1a0b1ef134030e2afb0b160bd62..7adb2f034087c2f1f660341e913a436dcd2ab73b 100644 --- a/Nynja.xcodeproj/project.pbxproj +++ b/Nynja.xcodeproj/project.pbxproj @@ -143,8 +143,6 @@ 2606F3BC20BFE20500CF7F15 /* MessageInteractor+Translation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2606F3BB20BFE20400CF7F15 /* MessageInteractor+Translation.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 */; }; @@ -618,6 +616,7 @@ 4B4266BF204D916000194BC1 /* ActionsView+Action.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4266BE204D916000194BC1 /* ActionsView+Action.swift */; }; 4B4266C1204D917800194BC1 /* ActionsView+Layout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4266C0204D917800194BC1 /* ActionsView+Layout.swift */; }; 4B4266C3204D923400194BC1 /* Array+UIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4266C2204D923400194BC1 /* Array+UIView.swift */; }; + 4B4D519421B7F62E00797C5D /* UpdateSeenByLogic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4D519321B7F62E00797C5D /* UpdateSeenByLogic.swift */; }; 4B5A0B73216E3BDD002C4160 /* ActionsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B5A0B6F216E3BDD002C4160 /* ActionsView.swift */; }; 4B5A0B74216E3BDD002C4160 /* ForwardAvatarViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B5A0B70216E3BDD002C4160 /* ForwardAvatarViewModel.swift */; }; 4B5A0B75216E3BDD002C4160 /* ProgressHUD.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B5A0B71216E3BDD002C4160 /* ProgressHUD.swift */; }; @@ -664,7 +663,6 @@ 4B7C73F9215A5522007924DB /* DebugLogs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B7C73F6215A5522007924DB /* DebugLogs.swift */; }; 4B7C73FA215A5522007924DB /* UIView+Debug.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B7C73F7215A5522007924DB /* UIView+Debug.swift */; }; 4B7C73FB215A5522007924DB /* UILabel+Debug.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B7C73F8215A5522007924DB /* UILabel+Debug.swift */; }; - 4B7C73FC215A552C007924DB /* LogService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B7C73EF215A5508007924DB /* LogService.swift */; }; 4B7C73FD215A553F007924DB /* LogWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B7C73EE215A5508007924DB /* LogWriter.swift */; }; 4B7E93382170D1BC001558CF /* RootNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B7E93372170D1BC001558CF /* RootNavigationController.swift */; }; 4B7E933B2170D410001558CF /* ForwardSelectorWireFrame.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B7E933A2170D410001558CF /* ForwardSelectorWireFrame.swift */; }; @@ -717,6 +715,18 @@ 4BB0EFBC2151347900704136 /* AlertImageViewControllerConstraints.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB0EFB92151347900704136 /* AlertImageViewControllerConstraints.swift */; }; 4BB0EFBD2151347900704136 /* AlertManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB0EFBA2151347900704136 /* AlertManager.swift */; }; 4BB35E23219AF46E0007C18E /* RosterRelatedQueryArgs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB35E22219AF46E0007C18E /* RosterRelatedQueryArgs.swift */; }; + 4BB5920321B923FA001FB393 /* Feed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8566BB10215BC39500320E15 /* Feed.swift */; }; + 4BB5920921B92702001FB393 /* MQTTMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB5920821B92702001FB393 /* MQTTMessage.swift */; }; + 4BB5920A21B9270E001FB393 /* MQTTMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB5920821B92702001FB393 /* MQTTMessage.swift */; }; + 4BB5920B21B92729001FB393 /* MQTTMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB5920821B92702001FB393 /* MQTTMessage.swift */; }; + 4BB5920C21B928AA001FB393 /* Collection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8512349121221B9E000129A2 /* Collection.swift */; }; + 4BB5920F21B9295D001FB393 /* LogService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB5920E21B9295D001FB393 /* LogService.swift */; }; + 4BB5921021B929CC001FB393 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4F3DAA22084935400FF71C7 /* Constants.swift */; }; + 4BB5921121B92CCD001FB393 /* LogService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB5920E21B9295D001FB393 /* LogService.swift */; }; + 4BB5921721B93022001FB393 /* MessageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB5921621B93022001FB393 /* MessageTests.swift */; }; + 4BB5921A21B93323001FB393 /* Message+SeenBy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB5921921B93323001FB393 /* Message+SeenBy.swift */; }; + 4BB5921B21B9332C001FB393 /* Message+SeenBy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB5921921B93323001FB393 /* Message+SeenBy.swift */; }; + 4BB5921C21B9332C001FB393 /* Message+SeenBy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB5921921B93323001FB393 /* Message+SeenBy.swift */; }; 4BBAEBBA21AC62740089B703 /* LengthValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BBAEBB921AC62740089B703 /* LengthValidator.swift */; }; 4BBAEBBD21AC68FD0089B703 /* Validator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BBAEBBC21AC68FD0089B703 /* Validator.swift */; }; 4BBAEBBF21AC6DF10089B703 /* ClosureValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BBAEBBE21AC6DF10089B703 /* ClosureValidator.swift */; }; @@ -779,6 +789,8 @@ 5A48445F21178E33000657ED /* AlertTextFieldViewControllerLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A48445E21178E33000657ED /* AlertTextFieldViewControllerLayout.swift */; }; 5A6237362268CC9BD4792230 /* EditUsernameViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B772E08B9E40EB48DD87082 /* EditUsernameViewController.swift */; }; 5AD8110B5B87B1AB9F1C5B52 /* CreateGroupPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CBACEAABEE65D7EC5572C4E /* CreateGroupPresenter.swift */; }; + 5B4F14102194A8810083E105 /* NynjaSDK.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5B4F13FE219470F70083E105 /* NynjaSDK.framework */; }; + 5B4F14112194A8810083E105 /* NynjaSDK.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 5B4F13FE219470F70083E105 /* NynjaSDK.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 5B5EE777EF301CFC1FDCF307 /* CreateGroupInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFC76E2B3DD0BCA0A622A5CD /* CreateGroupInteractor.swift */; }; 5BBEF53C212DE09F00F10768 /* ringback.m4a in Resources */ = {isa = PBXBuildFile; fileRef = 5BBEF53B212DE09F00F10768 /* ringback.m4a */; }; 5BC1D37920D3B4A8002A44B3 /* GroupCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BC1D37420D3B4A6002A44B3 /* GroupCollectionViewCell.swift */; }; @@ -1231,7 +1243,20 @@ 990A25B2C84CE09B4CE64533 /* MyGroupAliasPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC9D0CBC2BAD6DC6C7047A26 /* MyGroupAliasPresenter.swift */; }; 99B9D27D2F0EFE051E6581ED /* CreateGroupProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE9DC6ADA0E71241C49A328 /* CreateGroupProtocols.swift */; }; 9B0C32F12153CF1600094ECF /* HintView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B0C32F02153CF1600094ECF /* HintView.swift */; }; + 9B294510219F1F1500F30EA7 /* ParticipantHistoryHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B29450F219F1F1500F30EA7 /* ParticipantHistoryHeaderView.swift */; }; 9B2C6693216F82AB00116486 /* NynjaJoinByLinkService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B2C6692216F82AB00116486 /* NynjaJoinByLinkService.swift */; }; + 9B517995218333B2006E1A4B /* CallHistoryCellModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B517994218333B2006E1A4B /* CallHistoryCellModel.swift */; }; + 9B517997218333E1006E1A4B /* CallHistoryTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B517996218333E1006E1A4B /* CallHistoryTableViewCell.swift */; }; + 9B51799C21834BA7006E1A4B /* CallHistoryDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B51799B21834BA7006E1A4B /* CallHistoryDataSource.swift */; }; + 9B804AB9219326A1000606EC /* CallHistoryDataController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B804AB8219326A1000606EC /* CallHistoryDataController.swift */; }; + 9B804ABB2195A93E000606EC /* EmptyCallHistoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B804ABA2195A93E000606EC /* EmptyCallHistoryView.swift */; }; + 9B804AC0219D6AFA000606EC /* CallHistoryItemsFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B804ABF219D6AF9000606EC /* CallHistoryItemsFactory.swift */; }; + 9B804AD5219EF9E1000606EC /* ParticipantsHistoryProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B804AD4219EF9E1000606EC /* ParticipantsHistoryProtocols.swift */; }; + 9B804AD8219EFA66000606EC /* ParticipantsHistoryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B804AD6219EFA66000606EC /* ParticipantsHistoryViewController.swift */; }; + 9B804AD9219EFA66000606EC /* ParticipantsHistoryViewControllerLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B804AD7219EFA66000606EC /* ParticipantsHistoryViewControllerLayout.swift */; }; + 9B804ADB219EFBE8000606EC /* ParticipantsHistoryPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B804ADA219EFBE8000606EC /* ParticipantsHistoryPresenter.swift */; }; + 9B804ADD219EFC7B000606EC /* ParticipantsHistoryInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B804ADC219EFC7B000606EC /* ParticipantsHistoryInteractor.swift */; }; + 9B804ADF219EFCD1000606EC /* ParticipantsHistoryWireframe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B804ADE219EFCD1000606EC /* ParticipantsHistoryWireframe.swift */; }; 9B81AD92215A5EEA00993A8C /* ActiveSpeakerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B81AD91215A5EEA00993A8C /* ActiveSpeakerView.swift */; }; 9B96705F214BE3FE0058E98F /* MultiPageCollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B96705E214BE3FE0058E98F /* MultiPageCollectionView.swift */; }; 9B967098215151760058E98F /* LeaveVoiceMessageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B967097215151760058E98F /* LeaveVoiceMessageViewController.swift */; }; @@ -1239,6 +1264,7 @@ 9B96709C215151B50058E98F /* LeaveVoiceMessageInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B96709B215151B50058E98F /* LeaveVoiceMessageInteractor.swift */; }; 9B96709E215151D20058E98F /* LeaveVoiceMessageWireFrame.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B96709D215151D20058E98F /* LeaveVoiceMessageWireFrame.swift */; }; 9B9670A02152356D0058E98F /* LeaveVoiceMessageProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B96709F2152356D0058E98F /* LeaveVoiceMessageProtocols.swift */; }; + 9B9D439521A327550000A189 /* CallsItemsFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B9D439421A327550000A189 /* CallsItemsFactory.swift */; }; 9BB33F3E2146A14B009FB252 /* HoldToSpeakView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BB33F3D2146A14B009FB252 /* HoldToSpeakView.swift */; }; 9BB33F412146CC7C009FB252 /* CallInProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BB33F402146CC7C009FB252 /* CallInProgressView.swift */; }; 9BC9657620FF042E00052AE1 /* CallInProgressProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BC9657520FF042D00052AE1 /* CallInProgressProtocols.swift */; }; @@ -1247,6 +1273,11 @@ 9BD8E41120F39AE3001384EC /* CallInProgressPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BD8E41020F39AE3001384EC /* CallInProgressPresenter.swift */; }; 9BD8E41320F3A2E2001384EC /* CallInProgressInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BD8E41220F3A2E2001384EC /* CallInProgressInteractor.swift */; }; 9BE521222189B2E10070C664 /* ThreeButtonHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BE521212189B2E10070C664 /* ThreeButtonHeaderView.swift */; }; + 9BFFE60D21778ABF004FE2CA /* CallHistoryInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BFFE60C21778ABF004FE2CA /* CallHistoryInteractor.swift */; }; + 9BFFE60F21778ACD004FE2CA /* CallHistoryPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BFFE60E21778ACD004FE2CA /* CallHistoryPresenter.swift */; }; + 9BFFE61121778AD7004FE2CA /* CallHistoryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BFFE61021778AD6004FE2CA /* CallHistoryViewController.swift */; }; + 9BFFE61321778AE2004FE2CA /* CallHistoryWireFrame.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BFFE61221778AE2004FE2CA /* CallHistoryWireFrame.swift */; }; + 9BFFE6152177935C004FE2CA /* CallHistoryProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BFFE6142177935C004FE2CA /* CallHistoryProtocols.swift */; }; 9BFFE61B2178DD00004FE2CA /* BannerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BFFE61A2178DCFF004FE2CA /* BannerView.swift */; }; 9E9DD4C7F700872D7CCEE227 /* ProfileInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = EFF6A30BDE89BF7887B67DA0 /* ProfileInteractor.swift */; }; A1AD6864F4F49D9FC8997D59 /* SelectCountryPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5522F1F73FC8C564BF0254BF /* SelectCountryPresenter.swift */; }; @@ -2340,6 +2371,27 @@ remoteGlobalIDString = 8514D4C020EE27080002378A; remoteInfo = NynjaUIKit; }; + 5B4F13FD219470F70083E105 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 5B4F13F8219470F70083E105 /* sdk.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 5B87E4082088DE100063D5BB; + remoteInfo = NynjaSDK; + }; + 5B4F140E2194A80F0083E105 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 5B4F13F8219470F70083E105 /* sdk.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 5B87E4072088DE100063D5BB; + remoteInfo = NynjaSDK; + }; + 5B4F14122194A8810083E105 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 5B4F13F8219470F70083E105 /* sdk.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 5B87E4072088DE100063D5BB; + remoteInfo = NynjaSDK; + }; 5B80A2842102177B0008D6AD /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 85C65C6620EE58EC00C468B2 /* NynjaUIKit.xcodeproj */; @@ -2411,6 +2463,7 @@ dstPath = ""; dstSubfolderSpec = 10; files = ( + 5B4F14112194A8810083E105 /* NynjaSDK.framework in Embed Frameworks */, 85C65C7520EE5A5A00C468B2 /* NynjaUIKit.framework in Embed Frameworks */, ); name = "Embed Frameworks"; @@ -2925,6 +2978,7 @@ 4B4266BE204D916000194BC1 /* ActionsView+Action.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ActionsView+Action.swift"; sourceTree = ""; }; 4B4266C0204D917800194BC1 /* ActionsView+Layout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ActionsView+Layout.swift"; sourceTree = ""; }; 4B4266C2204D923400194BC1 /* Array+UIView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Array+UIView.swift"; sourceTree = ""; }; + 4B4D519321B7F62E00797C5D /* UpdateSeenByLogic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateSeenByLogic.swift; sourceTree = ""; }; 4B5A0B6F216E3BDD002C4160 /* ActionsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionsView.swift; sourceTree = ""; }; 4B5A0B70216E3BDD002C4160 /* ForwardAvatarViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ForwardAvatarViewModel.swift; sourceTree = ""; }; 4B5A0B71216E3BDD002C4160 /* ProgressHUD.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProgressHUD.swift; sourceTree = ""; }; @@ -2963,7 +3017,7 @@ 4B7C73EA215A5508007924DB /* SMSCodeProviding.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SMSCodeProviding.swift; sourceTree = ""; }; 4B7C73EC215A5508007924DB /* MotionManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MotionManager.swift; sourceTree = ""; }; 4B7C73EE215A5508007924DB /* LogWriter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LogWriter.swift; sourceTree = ""; }; - 4B7C73EF215A5508007924DB /* LogService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LogService.swift; sourceTree = ""; }; + 4B7C73EF215A5508007924DB /* LogService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = LogService.swift; path = LogService/LogService.swift; sourceTree = ""; }; 4B7C73F6215A5522007924DB /* DebugLogs.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DebugLogs.swift; sourceTree = ""; }; 4B7C73F7215A5522007924DB /* UIView+Debug.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIView+Debug.swift"; sourceTree = ""; }; 4B7C73F8215A5522007924DB /* UILabel+Debug.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UILabel+Debug.swift"; sourceTree = ""; }; @@ -3011,6 +3065,10 @@ 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 = ""; }; 4BB35E22219AF46E0007C18E /* RosterRelatedQueryArgs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RosterRelatedQueryArgs.swift; sourceTree = ""; }; + 4BB5920821B92702001FB393 /* MQTTMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MQTTMessage.swift; sourceTree = ""; }; + 4BB5920E21B9295D001FB393 /* LogService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogService.swift; sourceTree = ""; }; + 4BB5921621B93022001FB393 /* MessageTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageTests.swift; sourceTree = ""; }; + 4BB5921921B93323001FB393 /* Message+SeenBy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Message+SeenBy.swift"; sourceTree = ""; }; 4BBAEBB921AC62740089B703 /* LengthValidator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LengthValidator.swift; sourceTree = ""; }; 4BBAEBBC21AC68FD0089B703 /* Validator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Validator.swift; sourceTree = ""; }; 4BBAEBBE21AC6DF10089B703 /* ClosureValidator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClosureValidator.swift; sourceTree = ""; }; @@ -3064,6 +3122,7 @@ 5AEEB3D82E9CF02760DA4CE7 /* Pods-Nynja-Share.channels.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Nynja-Share.channels.xcconfig"; path = "Pods/Target Support Files/Pods-Nynja-Share/Pods-Nynja-Share.channels.xcconfig"; sourceTree = ""; }; 5B377AA90A6B6BA0120C31F1 /* EditProfileProtocols.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = EditProfileProtocols.swift; sourceTree = ""; }; 5B4DFF6C21919FA000E89D17 /* SpotifyConfig.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = SpotifyConfig.xcconfig; sourceTree = ""; }; + 5B4F13F8219470F70083E105 /* sdk.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = sdk.xcodeproj; path = "../mobile-sdk/src/sdk/ios/sdk.xcodeproj"; sourceTree = ""; }; 5BBEF53B212DE09F00F10768 /* ringback.m4a */ = {isa = PBXFileReference; lastKnownFileType = file; path = ringback.m4a; sourceTree = ""; }; 5BC1D37420D3B4A6002A44B3 /* GroupCollectionViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GroupCollectionViewCell.swift; sourceTree = ""; }; 5BC1D37620D3B4A7002A44B3 /* GroupAddParticipantsCollectionViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GroupAddParticipantsCollectionViewCell.swift; sourceTree = ""; }; @@ -3478,7 +3537,20 @@ 92F29C1A91BF5FD3A0AEEA0D /* AddContactInteractor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AddContactInteractor.swift; sourceTree = ""; }; 997E1A59FCAB5602A049C6E7 /* EditUsernamePresenter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = EditUsernamePresenter.swift; sourceTree = ""; }; 9B0C32F02153CF1600094ECF /* HintView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HintView.swift; sourceTree = ""; }; + 9B29450F219F1F1500F30EA7 /* ParticipantHistoryHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParticipantHistoryHeaderView.swift; sourceTree = ""; }; 9B2C6692216F82AB00116486 /* NynjaJoinByLinkService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NynjaJoinByLinkService.swift; sourceTree = ""; }; + 9B517994218333B2006E1A4B /* CallHistoryCellModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallHistoryCellModel.swift; sourceTree = ""; }; + 9B517996218333E1006E1A4B /* CallHistoryTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallHistoryTableViewCell.swift; sourceTree = ""; }; + 9B51799B21834BA7006E1A4B /* CallHistoryDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallHistoryDataSource.swift; sourceTree = ""; }; + 9B804AB8219326A1000606EC /* CallHistoryDataController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallHistoryDataController.swift; sourceTree = ""; }; + 9B804ABA2195A93E000606EC /* EmptyCallHistoryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyCallHistoryView.swift; sourceTree = ""; }; + 9B804ABF219D6AF9000606EC /* CallHistoryItemsFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallHistoryItemsFactory.swift; sourceTree = ""; }; + 9B804AD4219EF9E1000606EC /* ParticipantsHistoryProtocols.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ParticipantsHistoryProtocols.swift; sourceTree = ""; }; + 9B804AD6219EFA66000606EC /* ParticipantsHistoryViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ParticipantsHistoryViewController.swift; sourceTree = ""; }; + 9B804AD7219EFA66000606EC /* ParticipantsHistoryViewControllerLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ParticipantsHistoryViewControllerLayout.swift; sourceTree = ""; }; + 9B804ADA219EFBE8000606EC /* ParticipantsHistoryPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ParticipantsHistoryPresenter.swift; sourceTree = ""; }; + 9B804ADC219EFC7B000606EC /* ParticipantsHistoryInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ParticipantsHistoryInteractor.swift; sourceTree = ""; }; + 9B804ADE219EFCD1000606EC /* ParticipantsHistoryWireframe.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ParticipantsHistoryWireframe.swift; sourceTree = ""; }; 9B810991D7143259040DCA31 /* LanguageSettingsViewController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = LanguageSettingsViewController.swift; sourceTree = ""; }; 9B81AD91215A5EEA00993A8C /* ActiveSpeakerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActiveSpeakerView.swift; sourceTree = ""; }; 9B96705E214BE3FE0058E98F /* MultiPageCollectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultiPageCollectionView.swift; sourceTree = ""; }; @@ -3487,6 +3559,7 @@ 9B96709B215151B50058E98F /* LeaveVoiceMessageInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LeaveVoiceMessageInteractor.swift; sourceTree = ""; }; 9B96709D215151D20058E98F /* LeaveVoiceMessageWireFrame.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LeaveVoiceMessageWireFrame.swift; sourceTree = ""; }; 9B96709F2152356D0058E98F /* LeaveVoiceMessageProtocols.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LeaveVoiceMessageProtocols.swift; sourceTree = ""; }; + 9B9D439421A327550000A189 /* CallsItemsFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallsItemsFactory.swift; sourceTree = ""; }; 9BB33F3D2146A14B009FB252 /* HoldToSpeakView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HoldToSpeakView.swift; sourceTree = ""; }; 9BB33F402146CC7C009FB252 /* CallInProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallInProgressView.swift; sourceTree = ""; }; 9BC9657520FF042D00052AE1 /* CallInProgressProtocols.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallInProgressProtocols.swift; sourceTree = ""; }; @@ -3495,6 +3568,11 @@ 9BD8E41020F39AE3001384EC /* CallInProgressPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallInProgressPresenter.swift; sourceTree = ""; }; 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 = ""; }; + 9BFFE60C21778ABF004FE2CA /* CallHistoryInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallHistoryInteractor.swift; sourceTree = ""; }; + 9BFFE60E21778ACD004FE2CA /* CallHistoryPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallHistoryPresenter.swift; sourceTree = ""; }; + 9BFFE61021778AD6004FE2CA /* CallHistoryViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallHistoryViewController.swift; sourceTree = ""; }; + 9BFFE61221778AE2004FE2CA /* CallHistoryWireFrame.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallHistoryWireFrame.swift; sourceTree = ""; }; + 9BFFE6142177935C004FE2CA /* CallHistoryProtocols.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallHistoryProtocols.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 = ""; }; @@ -4413,6 +4491,7 @@ files = ( 85C65C7420EE5A5A00C468B2 /* NynjaUIKit.framework in Frameworks */, 63E6537BBBD814F6DF3DC589 /* Pods_Nynja.framework in Frameworks */, + 5B4F14102194A8810083E105 /* NynjaSDK.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -5973,9 +6052,10 @@ 359EB2721F9A27EB00147437 /* Services */ = { isa = PBXGroup; children = ( - 4B7E933C2170D4F0001558CF /* ServiceFactory */, 4B5A0B79216E3C6F002C4160 /* AttachmentProvider */, 356275751F9D337400D2A7F0 /* Handlers */, + 4BB5920D21B9294A001FB393 /* LogService */, + 4B7E933C2170D4F0001558CF /* ServiceFactory */, ); path = Services; sourceTree = ""; @@ -6051,7 +6131,6 @@ 3A768E1C1ECD152300108F7C /* Services */ = { isa = PBXGroup; children = ( - 4B2C502521B568FD00FBA9B1 /* MigrationManager */, 851769D420D584CA008ACF6B /* Amazon */, 26ABCA3D21189DA400EA4782 /* Aps.swift */, 4BE2C5CE2142EAC500A73DD9 /* Audio */, @@ -6081,6 +6160,7 @@ 26E3229120E4F18100271413 /* MessageParser */, E7EC77A21FD1B9BD00DC8245 /* MessageProcessing */, F11786D220A98D0A007A9A1B /* MessageSendingService */, + 4B2C502521B568FD00FBA9B1 /* MigrationManager */, 3AC321761EEAC4700068F3C8 /* Models */, 3A8045CC1F60C8E200AED866 /* MQTT */, A458FAC020EBA4D70075D55E /* MuteChatService */, @@ -6121,8 +6201,8 @@ 3A8045CC1F60C8E200AED866 /* MQTT */ = { isa = PBXGroup; children = ( - 4B030F392195CF7600F293B7 /* Entities */, 4B030F342195CD4A00F293B7 /* API */, + 4B030F392195CF7600F293B7 /* Entities */, 4B030F382195CDDA00F293B7 /* Extensions */, 3A8045CD1F60C8E200AED866 /* MQTTService.swift */, 4B030F312195CD4500F293B7 /* MQTTServiceDelegate.swift */, @@ -6205,6 +6285,7 @@ 3ABCE8E41EC9330D00A80B15 = { isa = PBXGroup; children = ( + 5B4F13F8219470F70083E105 /* sdk.xcodeproj */, 85C65C6620EE58EC00C468B2 /* NynjaUIKit.xcodeproj */, A4A242442060370E00B0A804 /* Shared */, 3ABCE8EF1EC9330D00A80B15 /* Nynja */, @@ -6450,6 +6531,7 @@ 8ED0F3C21FBC5CF1004916AB /* GroupsList */, 91732B7DCE35ABC02702095D /* GroupStorage */, F370781E99F6E0BC86F82844 /* History */, + 9BFFE5FE21777453004FE2CA /* CallHistory */, 82A72F9E24BABF2B9ACE8532 /* ImagePreview */, B79FA03021091EA400F286BF /* Interpretation */, B77C11E0210924F800CCB42E /* InterpretationType */, @@ -6459,6 +6541,7 @@ E90801F5EE14C0F1931F1B94 /* Main */, 6755CFB6BC25D884E80ED261 /* Map */, 4188F5659F19255180FB387D /* MapSearch */, + 9B804ACF219EF96F000606EC /* ParticipantsHistory */, B79FA025210772CF00F286BF /* Marketplace */, A45F10AE20B4218D00F45004 /* Message */, 26C1A3DD2031A9330009F7F0 /* OtherUser */, @@ -6515,14 +6598,14 @@ 4B030F342195CD4A00F293B7 /* API */ = { isa = PBXGroup; children = ( - FBCE83D420E52396003B7558 /* MQTTServiceWallet.swift */, + 3A8045D01F60C8E200AED866 /* MQTTServiceAuth.swift */, 3A8045D51F60C93D00AED866 /* MQTTServiceChat.swift */, 3A8045CE1F60C8E200AED866 /* MQTTServiceFriend.swift */, + A48C153E20EF765E002DA994 /* MQTTServiceLink.swift */, 3A8045CF1F60C8E200AED866 /* MQTTServiceProfile.swift */, - 3A8045D01F60C8E200AED866 /* MQTTServiceAuth.swift */, 0008E9122032D5AC003E316E /* MQTTServiceSchedule.swift */, 855EF422202CC85300541BE3 /* MQTTServiceStars.swift */, - A48C153E20EF765E002DA994 /* MQTTServiceLink.swift */, + FBCE83D420E52396003B7558 /* MQTTServiceWallet.swift */, ); path = API; sourceTree = ""; @@ -6540,6 +6623,7 @@ isa = PBXGroup; children = ( 4B030F3A2195CF8100F293B7 /* Host.swift */, + 4BB5920821B92702001FB393 /* MQTTMessage.swift */, ); path = Entities; sourceTree = ""; @@ -6652,7 +6736,9 @@ 4B1D7DFF2029C4A900703228 /* Options */, 4B06D30B2028A25D003B275B /* HomeItemsFactory.swift */, 4B1D7DFB2029C37900703228 /* FavoritesItemsFactory.swift */, + 9B9D439421A327550000A189 /* CallsItemsFactory.swift */, 4B1D7E062029D00000703228 /* OtherUserProfileItemsFactory.swift */, + 9B804ABF219D6AF9000606EC /* CallHistoryItemsFactory.swift */, B7F5051C2061252100C28FA1 /* DataAndStorageItemsFactory.swift */, ); name = Factory; @@ -6766,6 +6852,7 @@ 4B2C502F21B56AE900FBA9B1 /* CorrectMessageIdTypeInStarTable.swift */, 4B2C503121B56B2300FBA9B1 /* RemoveRoomMemberTable.swift */, 4B2C503821B573A100FBA9B1 /* RemoveP2pAndMucTables.swift */, + 4B4D519321B7F62E00797C5D /* UpdateSeenByLogic.swift */, ); path = Migrations; sourceTree = ""; @@ -7061,8 +7148,10 @@ 4B7C73ED215A5508007924DB /* LogService */ = { isa = PBXGroup; children = ( - 4BF090B721635B3700DCCA5C /* LogService */, - 4B7C73EE215A5508007924DB /* LogWriter.swift */, + 4B7C73EF215A5508007924DB /* LogService.swift */, + 4BF090B821635B4700DCCA5C /* LogServiceProtocol.swift */, + 4BF090BA21635B6600DCCA5C /* LogServiceTopic.swift */, + 4BB5920521B92528001FB393 /* LogWriter */, ); path = LogService; sourceTree = ""; @@ -7305,6 +7394,31 @@ path = AlertImageViewController; sourceTree = ""; }; + 4BB5920521B92528001FB393 /* LogWriter */ = { + isa = PBXGroup; + children = ( + 4B7C73EE215A5508007924DB /* LogWriter.swift */, + 2611CEF62182090900FFD4DD /* LogWriterProtocol.swift */, + ); + path = LogWriter; + sourceTree = ""; + }; + 4BB5920D21B9294A001FB393 /* LogService */ = { + isa = PBXGroup; + children = ( + 4BB5920E21B9295D001FB393 /* LogService.swift */, + ); + path = LogService; + sourceTree = ""; + }; + 4BB5921321B92FD9001FB393 /* Models */ = { + isa = PBXGroup; + children = ( + 4BB5921621B93022001FB393 /* MessageTests.swift */, + ); + path = Models; + sourceTree = ""; + }; 4BBAEBBB21AC68F00089B703 /* Validator */ = { isa = PBXGroup; children = ( @@ -7446,17 +7560,6 @@ path = Services/NynjaCalls; sourceTree = ""; }; - 4BF090B721635B3700DCCA5C /* LogService */ = { - isa = PBXGroup; - children = ( - 4B7C73EF215A5508007924DB /* LogService.swift */, - 4BF090B821635B4700DCCA5C /* LogServiceProtocol.swift */, - 2611CEF62182090900FFD4DD /* LogWriterProtocol.swift */, - 4BF090BA21635B6600DCCA5C /* LogServiceTopic.swift */, - ); - path = LogService; - sourceTree = ""; - }; 4BF2C3EE2189F58100E59F6C /* Entities */ = { isa = PBXGroup; children = ( @@ -7588,6 +7691,14 @@ path = WireFrame; sourceTree = ""; }; + 5B4F13F9219470F70083E105 /* Products */ = { + isa = PBXGroup; + children = ( + 5B4F13FE219470F70083E105 /* NynjaSDK.framework */, + ); + name = Products; + sourceTree = ""; + }; 5B80A2812102177B0008D6AD /* Products */ = { isa = PBXGroup; children = ( @@ -9735,6 +9846,64 @@ path = View; sourceTree = ""; }; + 9B2399F9217F0D4F00769770 /* TableView */ = { + isa = PBXGroup; + children = ( + 9B517994218333B2006E1A4B /* CallHistoryCellModel.swift */, + 9B517996218333E1006E1A4B /* CallHistoryTableViewCell.swift */, + 9B51799B21834BA7006E1A4B /* CallHistoryDataSource.swift */, + 9B804AB8219326A1000606EC /* CallHistoryDataController.swift */, + 9B804ABA2195A93E000606EC /* EmptyCallHistoryView.swift */, + ); + path = TableView; + sourceTree = ""; + }; + 9B804ACF219EF96F000606EC /* ParticipantsHistory */ = { + isa = PBXGroup; + children = ( + 9B804AD4219EF9E1000606EC /* ParticipantsHistoryProtocols.swift */, + 9B804AD3219EF9A8000606EC /* View */, + 9B804AD2219EF99E000606EC /* Presenter */, + 9B804AD1219EF992000606EC /* Interactor */, + 9B804AD0219EF98B000606EC /* WireFrame */, + ); + path = ParticipantsHistory; + sourceTree = ""; + }; + 9B804AD0219EF98B000606EC /* WireFrame */ = { + isa = PBXGroup; + children = ( + 9B804ADE219EFCD1000606EC /* ParticipantsHistoryWireframe.swift */, + ); + path = WireFrame; + sourceTree = ""; + }; + 9B804AD1219EF992000606EC /* Interactor */ = { + isa = PBXGroup; + children = ( + 9B804ADC219EFC7B000606EC /* ParticipantsHistoryInteractor.swift */, + ); + path = Interactor; + sourceTree = ""; + }; + 9B804AD2219EF99E000606EC /* Presenter */ = { + isa = PBXGroup; + children = ( + 9B804ADA219EFBE8000606EC /* ParticipantsHistoryPresenter.swift */, + ); + path = Presenter; + sourceTree = ""; + }; + 9B804AD3219EF9A8000606EC /* View */ = { + isa = PBXGroup; + children = ( + 9B804AD6219EFA66000606EC /* ParticipantsHistoryViewController.swift */, + 9B804AD7219EFA66000606EC /* ParticipantsHistoryViewControllerLayout.swift */, + 9B29450F219F1F1500F30EA7 /* ParticipantHistoryHeaderView.swift */, + ); + path = View; + sourceTree = ""; + }; 9B96709221514CA30058E98F /* LeaveVoiceMessage */ = { isa = PBXGroup; children = ( @@ -9830,6 +9999,51 @@ path = WireFrame; sourceTree = ""; }; + 9BFFE5FE21777453004FE2CA /* CallHistory */ = { + isa = PBXGroup; + children = ( + 9BFFE6142177935C004FE2CA /* CallHistoryProtocols.swift */, + 9BFFE60221778281004FE2CA /* View */, + 9BFFE60121778277004FE2CA /* Presenter */, + 9BFFE6002177826B004FE2CA /* Interactor */, + 9BFFE5FF21778261004FE2CA /* WireFrame */, + ); + path = CallHistory; + sourceTree = ""; + }; + 9BFFE5FF21778261004FE2CA /* WireFrame */ = { + isa = PBXGroup; + children = ( + 9BFFE61221778AE2004FE2CA /* CallHistoryWireFrame.swift */, + ); + path = WireFrame; + sourceTree = ""; + }; + 9BFFE6002177826B004FE2CA /* Interactor */ = { + isa = PBXGroup; + children = ( + 9BFFE60C21778ABF004FE2CA /* CallHistoryInteractor.swift */, + ); + path = Interactor; + sourceTree = ""; + }; + 9BFFE60121778277004FE2CA /* Presenter */ = { + isa = PBXGroup; + children = ( + 9BFFE60E21778ACD004FE2CA /* CallHistoryPresenter.swift */, + ); + path = Presenter; + sourceTree = ""; + }; + 9BFFE60221778281004FE2CA /* View */ = { + isa = PBXGroup; + children = ( + 9B2399F9217F0D4F00769770 /* TableView */, + 9BFFE61021778AD6004FE2CA /* CallHistoryViewController.swift */, + ); + path = View; + sourceTree = ""; + }; 9CB68C9C1271BC0D3657FDC0 /* Presenter */ = { isa = PBXGroup; children = ( @@ -10810,13 +11024,14 @@ A45F114220B421AB00F45004 /* Message */ = { isa = PBXGroup; children = ( - 85458CE1212D730E00BA8814 /* MessageIdentifiers.swift */, - A45F114320B421AB00F45004 /* MessageExtension.swift */, 8551CF0D2170856B00829CF1 /* Message+Construct.swift */, 85458CF4212D770100BA8814 /* Message+Files.swift */, 4BF090C421635E8600DCCA5C /* Message+LinkedId.swift */, 4BF090C721635F3300DCCA5C /* Message+LocalStatus.swift */, + 4BB5921921B93323001FB393 /* Message+SeenBy.swift */, 4BF090CB21635FDC00DCCA5C /* Message+Type.swift */, + A45F114320B421AB00F45004 /* MessageExtension.swift */, + 85458CE1212D730E00BA8814 /* MessageIdentifiers.swift */, ); path = Message; sourceTree = ""; @@ -13439,6 +13654,7 @@ F1C37AAA209A1BF4005EA197 /* NynjaUnitTests */ = { isa = PBXGroup; children = ( + 4BB5921321B92FD9001FB393 /* Models */, 4B348FE92163802100CCB0E3 /* Modules */, A421BB74210B4EC2006F2608 /* Extensions */, A4AB8E4F2105EB39005F9B0C /* InputsCachePolicy */, @@ -14283,9 +14499,11 @@ buildRules = ( ); dependencies = ( + 5B4F140F2194A80F0083E105 /* PBXTargetDependency */, 264FFA921FC590580028243D /* PBXTargetDependency */, 85C65C7220EE5A2800C468B2 /* PBXTargetDependency */, 85C65C7720EE5A5A00C468B2 /* PBXTargetDependency */, + 5B4F14132194A8810083E105 /* PBXTargetDependency */, ); name = Nynja; productName = Nynja; @@ -14401,6 +14619,10 @@ ProductGroup = 5B80A2812102177B0008D6AD /* Products */; ProjectRef = 85C65C6620EE58EC00C468B2 /* NynjaUIKit.xcodeproj */; }, + { + ProductGroup = 5B4F13F9219470F70083E105 /* Products */; + ProjectRef = 5B4F13F8219470F70083E105 /* sdk.xcodeproj */; + }, ); projectRoot = ""; targets = ( @@ -14413,6 +14635,13 @@ /* End PBXProject section */ /* Begin PBXReferenceProxy section */ + 5B4F13FE219470F70083E105 /* NynjaSDK.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = NynjaSDK.framework; + remoteRef = 5B4F13FD219470F70083E105 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; 5B80A2852102177B0008D6AD /* NynjaUIKit.framework */ = { isa = PBXReferenceProxy; fileType = wrapper.framework; @@ -14596,7 +14825,6 @@ "${BUILT_PRODUCTS_DIR}/MQTTClient/MQTTClient.framework", "${BUILT_PRODUCTS_DIR}/MaterialComponents/MaterialComponents.framework", "${BUILT_PRODUCTS_DIR}/MulticastDelegateSwift/MulticastDelegateSwift.framework", - "${PODS_ROOT}/NynjaSDK/NynjaSDK.framework", "${BUILT_PRODUCTS_DIR}/QRCode/QRCode.framework", "${BUILT_PRODUCTS_DIR}/SDWebImage/SDWebImage.framework", "${BUILT_PRODUCTS_DIR}/SQLCipher/SQLCipher.framework", @@ -14620,7 +14848,6 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MQTTClient.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MaterialComponents.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MulticastDelegateSwift.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/NynjaSDK.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/QRCode.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SDWebImage.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SQLCipher.framework", @@ -14809,6 +15036,7 @@ 261522642084D5EC00AF72A5 /* Testable.swift in Sources */, 267D465A20AB4C4000D42242 /* FeatureExtension.swift in Sources */, A42CE57E20692EDB000889CC /* userTask.swift in Sources */, + 4BB5920F21B9295D001FB393 /* LogService.swift in Sources */, A4679B9120B2DA640021FE9C /* SelectorCell.swift in Sources */, 35B98FA21F9CB948009B8DEC /* Layout.swift in Sources */, A42CE5AE20692EDB000889CC /* StringAtom.swift in Sources */, @@ -14842,6 +15070,7 @@ 4B5A0B7D216E3D20002C4160 /* AttachmentProvider.swift in Sources */, 26C0C1E72073DABB00C530DA /* DateExtensions.swift in Sources */, 26A8561E2074C38F00C642EA /* NavigationView.swift in Sources */, + 4BB5921C21B9332C001FB393 /* Message+SeenBy.swift in Sources */, 359EB2571F9A1E5000147437 /* BaseMQTTModel.swift in Sources */, A42CE5FE20692EDB000889CC /* Test_Spec.swift in Sources */, A42CE58C20692EDB000889CC /* sequenceFlow.swift in Sources */, @@ -14943,7 +15172,6 @@ 26A856292074C7BE00C642EA /* ActionsView+Action.swift in Sources */, 26A0CFE2200513B4006F6617 /* MemberExtension+BERT.swift in Sources */, A4F3DA9D2084910C00FF71C7 /* ContactHandler.swift in Sources */, - 4B7C73FC215A552C007924DB /* LogService.swift in Sources */, 4B7E933B2170D410001558CF /* ForwardSelectorWireFrame.swift in Sources */, 35B1ABB01FA34B2600E65233 /* SearchModel.swift in Sources */, 4B052CB12036193900BC2A9B /* StringAtomExtension.swift in Sources */, @@ -15060,6 +15288,7 @@ 8524C4D7217772CD003BF374 /* Member+Construct.swift in Sources */, 85D669E820BD95B300FBD803 /* UIView+Shadow.swift in Sources */, 2651094020ADBB0200F1B38B /* NotificationSettingProtocol.swift in Sources */, + 4BB5920A21B9270E001FB393 /* MQTTMessage.swift in Sources */, F11DF06920BD9E7900F3E005 /* NavigationProtocol.swift in Sources */, 2600CCB1216B8DC600EDC9C3 /* Language.swift in Sources */, 26A8562A2074C7FB00C642EA /* UIView+SafeArea.swift in Sources */, @@ -15127,10 +15356,12 @@ A42D52B6206A53AA00EEB952 /* Service_Spec.swift in Sources */, 4B8996C8204ECE9B00DCB183 /* ContactDAO.swift in Sources */, 4B6D20EC2164D4AB003ADB29 /* DeliveryStatus.swift in Sources */, + 9B804AD9219EFA66000606EC /* ParticipantsHistoryViewControllerLayout.swift in Sources */, 4B7C73F3215A5509007924DB /* LogWriter.swift in Sources */, 262D43872033417F002F1E45 /* FriendExtansion+BERT.swift in Sources */, 2600CCC1216D47DC00EDC9C3 /* OptionallyActionCellViewModel.swift in Sources */, FE58F9B1208F00FE004AFDD3 /* MessageEditActionTable.swift in Sources */, + 9B804ADF219EFCD1000606EC /* ParticipantsHistoryWireframe.swift in Sources */, 4B87713B219328780014AD09 /* QueryArgs.swift in Sources */, F105C6A0209F71BF0091786A /* CameraInteractor.swift in Sources */, 4B4266BA204D898900194BC1 /* ForwardSelectorDisplayMode.swift in Sources */, @@ -15138,6 +15369,7 @@ A42D51B6206A361400EEB952 /* iter.swift in Sources */, 9B967098215151760058E98F /* LeaveVoiceMessageViewController.swift in Sources */, 2603139E20A0A4BA009AC66D /* ChatLanguageSettingsPresenter.swift in Sources */, + 9B804AD8219EFA66000606EC /* ParticipantsHistoryViewController.swift in Sources */, 4B1D7DFC2029C37900703228 /* FavoritesItemsFactory.swift in Sources */, 26771CC3212ED109006112B5 /* DBConvertMessage.swift in Sources */, 269848CA200E9F1300590D6F /* StarModels.swift in Sources */, @@ -15207,6 +15439,7 @@ 00102F3C202C8B6600A877A9 /* NynjaSegmentedControl.swift in Sources */, E76D132F1FA35D2900B07F0E /* ProfilePlaceholderCellLayout.swift in Sources */, 8E47DBEB200EB05900E612B0 /* MapViewControllerLayout.swift in Sources */, + 9BFFE61121778AD7004FE2CA /* CallHistoryViewController.swift in Sources */, 260225DF20F390C6004FC238 /* ConvertionInfoView.swift in Sources */, FEA655CF2167777E00B44029 /* SeedVerificationWalletViewController.swift in Sources */, A42CE5AF20692EDB000889CC /* TypeSpec.swift in Sources */, @@ -15360,6 +15593,7 @@ 00E9824C205C1E19008BF03D /* SecurityItemsFactory.swift in Sources */, 26342CA920ECBAEF00D2196B /* TranscribeNetworkClient.swift in Sources */, 852003F620D4194A007C0036 /* DBRecentSticker.swift in Sources */, + 9B804ADD219EFC7B000606EC /* ParticipantsHistoryInteractor.swift in Sources */, 267BE2831FDE905D00C47E18 /* SettingsProtocols.swift in Sources */, 264638231FFFE269002590E6 /* RepliesHeaderView.swift in Sources */, 263D66331FE8D95100A509F8 /* TypingHandler.swift in Sources */, @@ -15404,6 +15638,7 @@ 85E1DD2720BEE961008AD211 /* ScalableCell.swift in Sources */, 5BC1D37B20D3B4A8002A44B3 /* GroupAddParticipantsCollectionViewCell.swift in Sources */, 269D9DF01FC3AF0D00324263 /* CGSizeExtension.swift in Sources */, + 9B804AD5219EF9E1000606EC /* ParticipantsHistoryProtocols.swift in Sources */, 4BB0EFBD2151347900704136 /* AlertManager.swift in Sources */, D30EB73829E48C0B1C1FD1C9 /* LoginViewController.swift in Sources */, FEA65471216775CD00B44029 /* ServiceWalletExtension.swift in Sources */, @@ -15411,6 +15646,7 @@ FEA655F02167777E00B44029 /* TransferHistoryTableModel.swift in Sources */, A42D51BA206A361400EEB952 /* io.swift in Sources */, 4B6D20E82164D4AB003ADB29 /* ProgressDisplayable.swift in Sources */, + 9BFFE60D21778ABF004FE2CA /* CallHistoryInteractor.swift in Sources */, 3A1EB9A51F3A848A00658E93 /* HistoryHandler.swift in Sources */, 850FC5F42032F4CE00832D87 /* ForwardTargets.swift in Sources */, 85788C422044237B003600C9 /* BuildNumberViewController.swift in Sources */, @@ -15626,6 +15862,7 @@ E785EF2A1FB9D99400F0C689 /* PinView.swift in Sources */, A42D51D0206A361400EEB952 /* Star.swift in Sources */, 85C16C3E20D2794500EDB77E /* BubbleImageSizeCalculatable.swift in Sources */, + 9B804ABB2195A93E000606EC /* EmptyCallHistoryView.swift in Sources */, A43B25D620AB1EE400FF8107 /* NewChannelPresenter.swift in Sources */, E7598F691FA1D8B90082FBE7 /* ProfileScheduledMesssageCellLayout.swift in Sources */, E7C1D3681F683A7D007D4E1E /* MainNavigationItem.swift in Sources */, @@ -15685,12 +15922,14 @@ 85E1DD2520BEBE17008AD211 /* MessageVC+StickerInputModuleDelegate.swift in Sources */, 266AD07A20F51AAE00EA275F /* TranscriptionInfo.swift in Sources */, 8E9601951FF2A04E00E0C21D /* ItemSelectorCell.swift in Sources */, + 9B804AC0219D6AFA000606EC /* CallHistoryItemsFactory.swift in Sources */, FEA655F72167777E00B44029 /* PaymentViewControllerModel.swift in Sources */, FB351F9220AC22A70042ACB1 /* ImagePreviewTransitionHelpers.swift in Sources */, 2683F75E203F36150003181A /* actExtension+BERT.swift in Sources */, 4B8996E4204EEC5A00DCB183 /* MessageDAOProtocol.swift in Sources */, E76491961F7A529D001E741C /* WheelContainer.swift in Sources */, 2648C4122069B52100863614 /* ChangeNumberStep3ViewController.swift in Sources */, + 9B804AB9219326A1000606EC /* CallHistoryDataController.swift in Sources */, 8E9601931FF295DF00E0C21D /* ItemsSelector.swift in Sources */, A42D51C9206A361400EEB952 /* writer.swift in Sources */, 2648C3E62069B49000863614 /* UITextField+Extension.swift in Sources */, @@ -15856,6 +16095,7 @@ F11786E020A9F11D007A9A1B /* UploadOperation.swift in Sources */, A42D52D8206A53AB00EEB952 /* task_Spec.swift in Sources */, 859B863920486068003272B2 /* CarouselPickerViewControllerLayout.swift in Sources */, + 9BFFE61321778AE2004FE2CA /* CallHistoryWireFrame.swift in Sources */, 85D669E520BD956000FBD803 /* UIButtonExtensions.swift in Sources */, E74EC9ED1FC2DA6E007268E6 /* RoomTable.swift in Sources */, A42D52C8206A53AB00EEB952 /* Auth_Spec.swift in Sources */, @@ -15897,6 +16137,7 @@ 8551CF1121708F7500829CF1 /* Array+Desc.swift in Sources */, A497F56720EFA538005CC60F /* HandlerFactory.swift in Sources */, 8557988820932401007050B8 /* StickerStaticMenuActionCellModel.swift in Sources */, + 9B9D439521A327550000A189 /* CallsItemsFactory.swift in Sources */, 267BE2921FDEA0C100C47E18 /* SettingsGroupInteractor.swift in Sources */, A43B25AB20AB1DFA00FF8107 /* ALTextView.swift in Sources */, 4BDC7E61203492CA00BCD381 /* TopSwipable.swift in Sources */, @@ -16007,6 +16248,7 @@ 4B7C73F2215A5509007924DB /* MotionManager.swift in Sources */, 4B030F322195CD4500F293B7 /* MQTTServiceDelegate.swift in Sources */, F11786BB20A8A63F007A9A1B /* CoordinatorProtocol.swift in Sources */, + 4B4D519421B7F62E00797C5D /* UpdateSeenByLogic.swift in Sources */, F105C6BE20A1347E0091786A /* PhotoPreviewInteractor.swift in Sources */, F18AEAFD20C15792004FE01C /* SelectAvatarCoordinator.swift in Sources */, 85D66A1220BD965300FBD803 /* UserMentionTableViewCell.swift in Sources */, @@ -16164,6 +16406,7 @@ 853E594F20D6AED2007799B9 /* Desc+Messages.swift in Sources */, A460324F2105C9A1009783DA /* InputsCachePolicy.swift in Sources */, 855AC534208E441500DC2335 /* StickersInputProtocols.swift in Sources */, + 9BFFE60F21778ACD004FE2CA /* CallHistoryPresenter.swift in Sources */, 266F04CB2015050400B97A83 /* DBStarMessage.swift in Sources */, 1D31D13E6E53E71F8279C55C /* HistoryProtocols.swift in Sources */, A4CE80C020C9318700400713 /* EmptyStateTableViewDS.swift in Sources */, @@ -16324,6 +16567,7 @@ F119E67920D27EA50043A532 /* VideoPreviewCVCell.swift in Sources */, 8502DB542061030100613C8C /* WheelPositionPickerViewController.swift in Sources */, 4B752B612163A5C900E852B9 /* DescExtension.swift in Sources */, + 9B517997218333E1006E1A4B /* CallHistoryTableViewCell.swift in Sources */, A45F116120B422AF00F45004 /* Message+System.swift in Sources */, 4B06D30C2028A25D003B275B /* HomeItemsFactory.swift in Sources */, 266AE8C3203496B60096A12C /* AsyncOperation.swift in Sources */, @@ -16331,6 +16575,7 @@ F119E67220D24BE40043A532 /* MultiplePreviewInteractor.swift in Sources */, 8503B525205046A6006F0593 /* NotificationSettingsPresenter.swift in Sources */, 85458CFD212D7B8C00BA8814 /* Desc+Construct.swift in Sources */, + 9B51799C21834BA7006E1A4B /* CallHistoryDataSource.swift in Sources */, 85433F2C204D5AA500B373A7 /* NynjaCloseButton.swift in Sources */, 8524C4D6217772C8003BF374 /* Member+Construct.swift in Sources */, A44B4D5A20CE9BDF00CA700A /* SwitchCell.swift in Sources */, @@ -16357,6 +16602,7 @@ A432CF1620B4347D00993AFB /* FloatingPlaceholderProvider.swift in Sources */, E7302A931FC83477002892F8 /* Desc+DescMime.swift in Sources */, 8509FC7B2158CCA800734D93 /* MessageInteractor+Reply.swift in Sources */, + 9B517995218333B2006E1A4B /* CallHistoryCellModel.swift in Sources */, A42D51CB206A361400EEB952 /* Job.swift in Sources */, E7F68D271FA22C45009C98D1 /* EditProfileVCStrings.swift in Sources */, 8ECC06801FC5C80C002CF225 /* MessagesProcessingManager.swift in Sources */, @@ -16372,6 +16618,7 @@ 26D621F42069778400595E13 /* ChatWheelItemView.swift in Sources */, E7E06C661F792AEF00BFC8FA /* LoginWheelContainerDelegate.swift in Sources */, FB16E79D20EFCF15009FA203 /* CryptoMoney.swift in Sources */, + 9BFFE6152177935C004FE2CA /* CallHistoryProtocols.swift in Sources */, 9B96709E215151D20058E98F /* LeaveVoiceMessageWireFrame.swift in Sources */, 4B8996F2204EF5E900DCB183 /* ChatCheckpointDAO.swift in Sources */, 265F5D2E209B8C1C008ACCC8 /* MessageEditActionDAO.swift in Sources */, @@ -16496,6 +16743,7 @@ 0062D9422062EC4100B915AC /* InviteFriendsSelectionViewModel.swift in Sources */, A4679BA520B2DD0F0021FE9C /* SubscribersSelectorPresenter.swift in Sources */, F105C6BC20A1347E0091786A /* PhotoPreviewWireframeProtocol.swift in Sources */, + 9B294510219F1F1500F30EA7 /* ParticipantHistoryHeaderView.swift in Sources */, A43B25A620AB1DFA00FF8107 /* RecordingAudioWaveform.swift in Sources */, 26C1A3EB2031AAD20009F7F0 /* OtherUserInteractor.swift in Sources */, 26D8317520EA65200067C5B4 /* TranslationInfo.swift in Sources */, @@ -16547,6 +16795,7 @@ FEA655CE2167777E00B44029 /* SeedVerificationWalletCollectionViewCell.swift in Sources */, 8E6C4BE81FFB9B23009C8374 /* RoundProgressIndicator.swift in Sources */, 3D7B572828F83EAFEDA78CEA /* MapViewController.swift in Sources */, + 4BB5921A21B93323001FB393 /* Message+SeenBy.swift in Sources */, 54FFFD58388E2B660C1E5A05 /* MapPresenter.swift in Sources */, A42D52D6206A53AB00EEB952 /* serviceTask_Spec.swift in Sources */, 0AB08BA89A51118248FA3233 /* MapInteractor.swift in Sources */, @@ -16651,6 +16900,7 @@ 2689CDEA20C48AD8007816B9 /* TranslationManualView.swift in Sources */, 4B2C502A21B569B500FBA9B1 /* AddSeenByColumnToMessage.swift in Sources */, 6C25C4720B043D98729C02C8 /* TopUpAccountPresenter.swift in Sources */, + 4BB5920921B92702001FB393 /* MQTTMessage.swift in Sources */, A42D51CE206A361400EEB952 /* Search.swift in Sources */, B77C11DB2109242200CCB42E /* AssigningInterpreterPresenter.swift in Sources */, 26342CB220ECDDC400D2196B /* Encodable+Dictionary.swift in Sources */, @@ -16792,6 +17042,7 @@ 8562853220D140FC000C9739 /* InputBar+ButtonType.swift in Sources */, A43B25AC20AB1DFA00FF8107 /* BaseInputView.swift in Sources */, F127F3BA20BF03BF007A6F87 /* DateFormatterExtension.swift in Sources */, + 9B804ADB219EFBE8000606EC /* ParticipantsHistoryPresenter.swift in Sources */, 4B3B35D9217119BF005A214A /* AmazonInitializerImpl.swift in Sources */, B7B546AE210D9C8C002DCA55 /* CircleView.swift in Sources */, E79061B81FBF2243009FD83A /* FeatureTable.swift in Sources */, @@ -16965,23 +17216,29 @@ buildActionMask = 2147483647; files = ( FB816EF520B5B87300093DCD /* Bert.swift in Sources */, + 4BB5920321B923FA001FB393 /* Feed.swift in Sources */, FB61D664214FF96200CB2A1F /* ColorsConstants.swift in Sources */, 85458CE8212D73E600BA8814 /* MucExtension.swift in Sources */, 4BF2C3E32188BA7B00E59F6C /* IdentifierComponentValue.swift in Sources */, 4B348FFB2163836D00CCB0E3 /* ReversableDataSource.swift in Sources */, + 4BB5920C21B928AA001FB393 /* Collection.swift in Sources */, FB61D64E214FEC7D00CB2A1F /* FontsConstants.swift in Sources */, FB816EF220B5B6F100093DCD /* BaseMQTTModel.swift in Sources */, 4B8FC31D2163D6A700602D6B /* Cloneable.swift in Sources */, + 4BB5921121B92CCD001FB393 /* LogService.swift in Sources */, 4B752B5E2163A4F900E852B9 /* GApiResponse.swift in Sources */, FB816EF020B5B36D00093DCD /* HistoryRequestModelTests.swift in Sources */, 85458CEA212D742300BA8814 /* muc.swift in Sources */, 8513F072218D0753003B901B /* BERTEncodable.swift in Sources */, A4CB153B21039C1100C3B68B /* JailbreakDetectorProtocol.swift in Sources */, 4B8C05952164A9D60034D8F3 /* ChatCellModelMock.swift in Sources */, + 4BB5921B21B9332C001FB393 /* Message+SeenBy.swift in Sources */, + 4BB5921721B93022001FB393 /* MessageTests.swift in Sources */, 85458D01212D7C1A00BA8814 /* StringAtomExtension.swift in Sources */, A4AB8E522105EC46005F9B0C /* TextField.swift in Sources */, 85458D00212D7C0C00BA8814 /* Int+AnyObject.swift in Sources */, 4B6D20F02164D4B0003ADB29 /* ChatCellModelType.swift in Sources */, + 4BB5920B21B92729001FB393 /* MQTTMessage.swift in Sources */, FB816EF820B5B89700093DCD /* StringAtom.swift in Sources */, 4BF090BE21635B9400DCCA5C /* LogServiceProtocol.swift in Sources */, 4B8FC3122163C50E00602D6B /* SpeedStringRepresentable.swift in Sources */, @@ -16996,7 +17253,7 @@ 4BF090CE21635FEE00DCCA5C /* Message+Type.swift in Sources */, A49EE6D7210B110800B700B1 /* Link.swift in Sources */, 4B8FC31C2163CD8C00602D6B /* ChatCellModel.swift in Sources */, - 2611CEF92182090900FFD4DD /* LogWriterProtocol.swift in Sources */, + 4BB5921021B929CC001FB393 /* Constants.swift in Sources */, 85458CE4212D731300BA8814 /* MessageIdentifiers.swift in Sources */, FB61D651214FEC8200CB2A1F /* LocalizableConstants.swift in Sources */, A4330A562109D60D0060BD93 /* QueryFactoryProtocol.swift in Sources */, @@ -17043,7 +17300,6 @@ 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 */, @@ -17073,6 +17329,16 @@ name = NynjaUIKit; targetProxy = 26DC81EC213838CD003E5FD9 /* PBXContainerItemProxy */; }; + 5B4F140F2194A80F0083E105 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = NynjaSDK; + targetProxy = 5B4F140E2194A80F0083E105 /* PBXContainerItemProxy */; + }; + 5B4F14132194A8810083E105 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = NynjaSDK; + targetProxy = 5B4F14122194A8810083E105 /* PBXContainerItemProxy */; + }; 85C65C7220EE5A2800C468B2 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = NynjaUIKit; diff --git a/Nynja/CallHistoryItemsFactory.swift b/Nynja/CallHistoryItemsFactory.swift new file mode 100644 index 0000000000000000000000000000000000000000..af01d5d7d193f2f00e50c0ca28e1ca1442d63c43 --- /dev/null +++ b/Nynja/CallHistoryItemsFactory.swift @@ -0,0 +1,28 @@ +// +// CallHistoryItemsFactory.swift +// Nynja +// +// Created by Bozhko Terziev on 11/15/18. +// Copyright © 2018 Softavail. All rights reserved. +// + +class CallHistoryItemsFactory: WCBaseItemsFactory { + + // MARK: - Second lvl + override var secondLevelItems: ItemModels { + return [clearHistory] + } + + override var actions: ImageActionItemModel { + let item = super.actions + item.state = .selected + return item + } + + var clearHistory: ImageActionItemModel { + let item = ImageActionItemModel(nameImage: "ic_delete_context_menu", navItem: .clearHistory, isSelectable: false, action: { [weak navigateDelegate] (item, indexPath) in + navigateDelegate?.clearCallHistory(indexPath: indexPath) + }) + return item + } +} diff --git a/Nynja/CallsItemsFactory.swift b/Nynja/CallsItemsFactory.swift new file mode 100644 index 0000000000000000000000000000000000000000..52d523b88c662ff7a6e9bb4f707d1cc4611d250e --- /dev/null +++ b/Nynja/CallsItemsFactory.swift @@ -0,0 +1,33 @@ +// +// GroupChatsItemsFactory.swift +// Nynja +// +// Created by Volodymyr Hryhoriev on 2/5/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +class CallsItemsFactory: WCBaseItemsFactory { + + // MARK: - Second lvl + override var secondLevelItems: ItemModels { + return [history, video, voice] + } + + var voice: ImageActionItemModel { + return ImageActionItemModel(nameImage: "ic_calls", navItem: .voiceCall, action: { [weak navigateDelegate] (item, indexPath) in + navigateDelegate?.conferenceVoiceCall(indexPath: indexPath) + }) + } + + var video: ImageActionItemModel { + return ImageActionItemModel(nameImage: "ic_video", navItem: .videoCall, action: { [weak navigateDelegate] (item, indexPath) in + navigateDelegate?.unavailableFunctionality() + }) + } + + var history: ImageActionItemModel { + return ImageActionItemModel(nameImage: "ic_history", navItem: .callHistory, action: { [weak navigateDelegate] (item, indexPath) in + navigateDelegate?.callHistory(indexPath: indexPath) + }) + } +} diff --git a/Nynja/ChatService/ChatService.swift b/Nynja/ChatService/ChatService.swift index ddd931eddb21f5a9a86dff48a1ec2caf0976f5b4..8ab32037d8859952b48c53c5f86e92fa08f9aaa9 100644 --- a/Nynja/ChatService/ChatService.swift +++ b/Nynja/ChatService/ChatService.swift @@ -108,7 +108,7 @@ final class ChatService { room.hasMentions || message.hasMentions, case let .updated(mentions) = RoomDAO.updatedMentions(with: message, roomId: roomId) { - room.mentions = mentions as [AnyObject]? + room.mentions = mentions } do { @@ -155,7 +155,7 @@ final class ChatService { if let phoneId = storageService.phoneId, RoomDAO.isCurrentUserMentioned(in: message, phoneId: phoneId) { mentions.insert(link) } - room.mentions = mentions.sorted() as [AnyObject]? + room.mentions = mentions.sorted() RoomDAO.updateColumns([.mentions], room: room) } diff --git a/Nynja/DB/Models/DBMessage.swift b/Nynja/DB/Models/DBMessage.swift index 71380ce5e480fd1ef4b9bf4ebcaf28488a6a7d3b..19f229f12a9f0d73068a537d8ba2b0501ce481d9 100644 --- a/Nynja/DB/Models/DBMessage.swift +++ b/Nynja/DB/Models/DBMessage.swift @@ -57,17 +57,14 @@ final class DBMessage: Record, DBModel { self.created = message.created self.type = message.types.joinedByCommaIfNotEmpty() self.link = message.linkedId - self.seenBy = message.seenby? - .compactMap { id -> String? in - if let id = id as? Int64 { - return String(id) - } else if let id = id as? String { - return id - } - return nil - }.joinedByComma() + self.seenBy = message.seenby?.joinedByComma() self.status = message.statusString + + if let rosterId = StorageService.sharedInstance.rosterId { + message.addDeletedLocalStatusIfNeeded(rosterId: rosterId) + } + self.localStatus = message.localStatus self.isTrusted = message.isTrusted diff --git a/Nynja/DB/Models/DBMessageAction.swift b/Nynja/DB/Models/DBMessageAction.swift index 099a5176bbfd25d711f5c81a2274cbdbe2837c6e..f7ce52ce559311ce35b280ee32b4fcd3b5f27855 100644 --- a/Nynja/DB/Models/DBMessageAction.swift +++ b/Nynja/DB/Models/DBMessageAction.swift @@ -16,13 +16,11 @@ final class DBMessageAction: Record, DBModel { var messageId: MessageServerId var seenBy: Int64 - var phoneId: String var action: Action - init(messageId: MessageServerId, seenBy: Int64, phoneId: String, action: Action) { + init(messageId: MessageServerId, seenBy: Int64, action: Action) { self.messageId = messageId self.seenBy = seenBy - self.phoneId = phoneId self.action = action super.init() @@ -35,19 +33,17 @@ final class DBMessageAction: Record, DBModel { } required init(row: Row) { - messageId = row[MessageActionTable.Column.messageId.title] - seenBy = row[MessageActionTable.Column.seenBy.title] - phoneId = row[MessageActionTable.Column.phoneId.title] - action = Action(rawValue: row[MessageActionTable.Column.action.title])! + messageId = row[Column.messageId] + seenBy = row[Column.seenBy] + action = Action(rawValue: row[Column.action])! super.init() } override func encode(to container: inout PersistenceContainer) { - container[MessageActionTable.Column.messageId.title] = messageId - container[MessageActionTable.Column.seenBy.title] = seenBy - container[MessageActionTable.Column.phoneId.title] = phoneId - container[MessageActionTable.Column.action.title] = action.rawValue + container[Column.messageId] = messageId + container[Column.seenBy] = seenBy + container[Column.action] = action.rawValue } @@ -61,17 +57,4 @@ final class DBMessageAction: Record, DBModel { func deleteAggregate(_ db: Database) throws -> Bool { return try self.delete(db) } - - - // MARK: - Fetching - - static func action(_ db: Database, messageId: Int64) throws -> DBMessageAction? { - let messageIdColumn = Column(MessageActionTable.Column.messageId.title) - - guard let message = try DBMessageAction.filter(messageIdColumn == messageId).fetchOne(db) else { - return nil - } - - return message - } } diff --git a/Nynja/DB/Tables/MessageActionTable.swift b/Nynja/DB/Tables/MessageActionTable.swift index ed0bd81cf5dcb9bb44333a6a571d6daac5b50d3d..1d8af19505eb7acf02ab96bca3d480f1bc45bcf6 100644 --- a/Nynja/DB/Tables/MessageActionTable.swift +++ b/Nynja/DB/Tables/MessageActionTable.swift @@ -18,19 +18,7 @@ final class MessageActionTable: Table { try db.create(self) { t in t.column(Column.messageId, .integer).primaryKey(onConflict: .replace, autoincrement: false) t.column(Column.seenBy, .integer) - t.column(Column.phoneId, .text) t.column(Column.action, .text) } } } - -// MARK: Column -extension MessageActionTable { - - enum Column: Int, Describable { - case messageId - case seenBy - case phoneId - case action - } -} diff --git a/Nynja/Extensions/Models/Desc/Desc+DescMime.swift b/Nynja/Extensions/Models/Desc/Desc+DescMime.swift index cdbfc90341cfada9d0f7ccdbb54663079687d459..79d55d64ce0c1072d59eb15cf9f9cb5cca3f9fe5 100644 --- a/Nynja/Extensions/Models/Desc/Desc+DescMime.swift +++ b/Nynja/Extensions/Models/Desc/Desc+DescMime.swift @@ -47,8 +47,8 @@ extension Desc { setupFile(.file, filename: filename, filepath: filepath, size: size) case .payment(let mime, let payload, let notes): setupPayment(mime, payload: payload, notes: notes) - case .call(let members): - setupCall(members) + case .call( _): break + } self.id = IdBuilder(format: .defaultId).build() @@ -142,11 +142,6 @@ extension Desc { self.data = Feature.fileRepresentation(size: Int64(size), filename: filename) } - private func setupCall(_ members: [String]) { - self.mime = SendMessageType.audioCall.rawValue - self.data = Feature.callRepresentation(members: members) - } - private func setupPayment(_ mime: SendMessageType, payload: String, notes: String?) { self.mime = mime.rawValue self.payload = payload diff --git a/Nynja/Extensions/Models/Feature/FeatureExtension.swift b/Nynja/Extensions/Models/Feature/FeatureExtension.swift index 554992abd611e6e3e75baf6672db576dda1d7b08..c5acf5182153c9181d99f7716e1cdc36c7d3801e 100644 --- a/Nynja/Extensions/Models/Feature/FeatureExtension.swift +++ b/Nynja/Extensions/Models/Feature/FeatureExtension.swift @@ -71,8 +71,13 @@ enum FeatureKeys { } enum Call: String { - case users = "USERS" + case duration = "DURATION" + case starttime = "START_TIME" + case conferenceid = "CONFERENCE_ID" + case endedby = "ENDED_BY" + case count = "COUNT" } + enum Payment: String { case description = "DESCRIPTION" case text = "text" @@ -208,17 +213,6 @@ extension Feature { fileDataFeature(forKey: FeatureKeys.File.File.filename, value: filename)] } - static func callRepresentation(members: [String]) -> [Feature] { - let _members = members.joinedByComma() - let feature = Feature() - feature.id = IdBuilder.init(format: .defaultId) - .build() - feature.key = FeatureKeys.File.Call.users.rawValue - feature.group = FeatureGroup.fileData.rawValue - feature.value = _members - return [feature] - } - static func payment(notes: String) -> [Feature] { let feature = Feature() feature.id = IdBuilder.init(format: .featureId) diff --git a/Nynja/Extensions/Models/Message/Message+DB.swift b/Nynja/Extensions/Models/Message/Message+DB.swift index 9ede343ec7b4e19daaa0bdd302e6407a9c0fdb2d..e193514386c2a2e83b969be0e0156ef232f9a9e5 100644 --- a/Nynja/Extensions/Models/Message/Message+DB.swift +++ b/Nynja/Extensions/Models/Message/Message+DB.swift @@ -9,10 +9,10 @@ import Foundation extension Message { - + convenience init(message: DBMessage) { self.init() - + self.id = message.serverId self.container = StringAtom(string: message.container) self.prev = message.prev @@ -23,31 +23,26 @@ extension Message { self.created = message.created self.types = Set(message.type?.components(separatedBy: Constants.commaSeparator) ?? []) self.linkedId = message.link - - if message.feedType == FeedType.p2p.rawValue { - self.seenby = message.seenBy?.splitByComma { $0 as AnyObject } - } else { - self.seenby = message.seenBy?.splitIntegerIdentifiers() - } - + + self.seenby = message.seenBy?.splitIntegerIdentifiers() self.repliedby = message.repliedBy?.splitIntegerIdentifiers() self.mentioned = message.mentioned?.splitIntegerIdentifiers() self.status = StringAtom(string: message.status) self.localStatus = message.localStatus - + self.files = message.files.map { Desc(desc: $0) } - + if let from = message.from, let to = message.to { self.feed_id = try? FeedCreator.create(from: from, to: to, rawValue: message.feedType) as AnyObject } - + self.isTrusted = message.isTrusted } - + convenience init(message: DBStarMessage) { self.init() - + self.id = message.serverId self.container = StringAtom(string: message.container) self.prev = message.prev @@ -63,21 +58,21 @@ extension Message { self.mentioned = message.mentioned?.splitIntegerIdentifiers() self.status = StringAtom(string: message.status) - + self.files = message.files.map { Desc(desc: $0) } - + if let from = message.from, let to = message.to { self.feed_id = try? FeedCreator.create(from: from, to: to, rawValue: message.feedType) as AnyObject } - + self.senderAvatar = message.senderAvatar self.senderName = message.senderName self.feedName = message.feedName } - + convenience init(message: DBJobMessage) { self.init() - + self.id = message.serverId self.container = StringAtom(string: message.container) self.prev = message.prev @@ -93,7 +88,7 @@ extension Message { self.mentioned = message.mentioned?.splitIntegerIdentifiers() self.status = StringAtom(string: message.status) - + self.files = message.files.map { Desc(desc: $0) } if let from = message.from, let to = message.to { @@ -103,11 +98,11 @@ extension Message { } extension Message: DBModelConvertible { - + var dbMessage: DBMessage? { return DBMessage(message: self) } - + var databaseModel: DBModel? { return dbMessage } diff --git a/Nynja/Generated/AssetsConstants.swift b/Nynja/Generated/AssetsConstants.swift index a69f5cd74ad4e24c8f55efe8d5279c197503364f..4767e42a7220107af3d15bf842c7d5ec8d8be859 100644 --- a/Nynja/Generated/AssetsConstants.swift +++ b/Nynja/Generated/AssetsConstants.swift @@ -658,6 +658,52 @@ internal extension Image { static var btnTakePhotoHighlighted: ImageAsset { return ImageAsset(name: "btn_take_photo_highlighted") } /// "btn_wheel_done" static var btnWheelDone: ImageAsset { return ImageAsset(name: "btn_wheel_done") } + enum CallBubbles { + /// "ic_canceled_video_bubble" + static var icCanceledVideoBubble: ImageAsset { return ImageAsset(name: "ic_canceled_video_bubble") } + /// "ic_canceled_voice_bubble" + static var icCanceledVoiceBubble: ImageAsset { return ImageAsset(name: "ic_canceled_voice_bubble") } + /// "ic_incoming_video_bubble" + static var icIncomingVideoBubble: ImageAsset { return ImageAsset(name: "ic_incoming_video_bubble") } + /// "ic_incoming_voice_bubble" + static var icIncomingVoiceBubble: ImageAsset { return ImageAsset(name: "ic_incoming_voice_bubble") } + /// "ic_missed_video_bubble" + static var icMissedVideoBubble: ImageAsset { return ImageAsset(name: "ic_missed_video_bubble") } + /// "ic_missed_voice_bubble" + static var icMissedVoiceBubble: ImageAsset { return ImageAsset(name: "ic_missed_voice_bubble") } + /// "ic_no_answer_video_bubble" + static var icNoAnswerVideoBubble: ImageAsset { return ImageAsset(name: "ic_no_answer_video_bubble") } + /// "ic_no_answer_voice_bubble" + static var icNoAnswerVoiceBubble: ImageAsset { return ImageAsset(name: "ic_no_answer_voice_bubble") } + /// "ic_outgoing_video_bubble" + static var icOutgoingVideoBubble: ImageAsset { return ImageAsset(name: "ic_outgoing_video_bubble") } + /// "ic_outgoing_voice_bubble" + static var icOutgoingVoiceBubble: ImageAsset { return ImageAsset(name: "ic_outgoing_voice_bubble") } + } + enum CallHistory { + /// "ic_call_history_empty" + static var icCallHistoryEmpty: ImageAsset { return ImageAsset(name: "ic_call_history_empty") } + /// "ic_canceled_video" + static var icCanceledVideo: ImageAsset { return ImageAsset(name: "ic_canceled_video") } + /// "ic_canceled_voice" + static var icCanceledVoice: ImageAsset { return ImageAsset(name: "ic_canceled_voice") } + /// "ic_incoming_video" + static var icIncomingVideo: ImageAsset { return ImageAsset(name: "ic_incoming_video") } + /// "ic_incoming_voice" + static var icIncomingVoice: ImageAsset { return ImageAsset(name: "ic_incoming_voice") } + /// "ic_missed_video" + static var icMissedVideo: ImageAsset { return ImageAsset(name: "ic_missed_video") } + /// "ic_missed_voice" + static var icMissedVoice: ImageAsset { return ImageAsset(name: "ic_missed_voice") } + /// "ic_no_answer_video" + static var icNoAnswerVideo: ImageAsset { return ImageAsset(name: "ic_no_answer_video") } + /// "ic_no_answer_voice" + static var icNoAnswerVoice: ImageAsset { return ImageAsset(name: "ic_no_answer_voice") } + /// "ic_outgoing_video" + static var icOutgoingVideo: ImageAsset { return ImageAsset(name: "ic_outgoing_video") } + /// "ic_outgoing_voice" + static var icOutgoingVoice: ImageAsset { return ImageAsset(name: "ic_outgoing_voice") } + } /// "callKitImage" static var callKitImage: ImageAsset { return ImageAsset(name: "callKitImage") } /// "camera" diff --git a/Nynja/Generated/LocalizableConstants.swift b/Nynja/Generated/LocalizableConstants.swift index 2066e5d6ed6188f5175c66a918afb5bf09d3e40e..a6c6d8c4d762c37e056074e5be7bf7d541160a41 100644 --- a/Nynja/Generated/LocalizableConstants.swift +++ b/Nynja/Generated/LocalizableConstants.swift @@ -132,6 +132,36 @@ internal extension String { static var callDisconnected: String { return localizable.tr("Localizable", "call_disconnected") } /// Connection dropped static var callFailed: String { return localizable.tr("Localizable", "call_failed") } + /// All + static var callHistoryButtonAllTitle: String { return localizable.tr("Localizable", "call_history_button_all_title") } + /// Incoming + static var callHistoryButtonIncomingTitle: String { return localizable.tr("Localizable", "call_history_button_incoming_title") } + /// Missed + static var callHistoryButtonMissedTitle: String { return localizable.tr("Localizable", "call_history_button_missed_title") } + /// Outgoing + static var callHistoryButtonOutgoingTitle: String { return localizable.tr("Localizable", "call_history_button_outgoing_title") } + /// Are you sure you want to clear all call history? + static var callHistoryClearQuestionAlert: String { return localizable.tr("Localizable", "call_history_clear_question_alert") } + /// Call history is empty + static var callHistoryEmptyTitle: String { return localizable.tr("Localizable", "call_history_empty_title") } + /// Incoming Call + static var callHistoryLogDirInbound: String { return localizable.tr("Localizable", "call_history_log_dir_inbound") } + /// Outgoing Call + static var callHistoryLogDirOutbound: String { return localizable.tr("Localizable", "call_history_log_dir_outbound") } + /// Canceled Call + static var callHistoryLogStatusCanceled: String { return localizable.tr("Localizable", "call_history_log_status_canceled") } + /// Declined Call + static var callHistoryLogStatusDeclined: String { return localizable.tr("Localizable", "call_history_log_status_declined") } + /// Missed Call + static var callHistoryLogStatusMissed: String { return localizable.tr("Localizable", "call_history_log_status_missed") } + /// No Аnswer + static var callHistoryLogStatusNoanswer: String { return localizable.tr("Localizable", "call_history_log_status_noanswer") } + /// started a call + static var callHistoryParticipantStartedCall: String { return localizable.tr("Localizable", "call_history_participant_started_call") } + /// call history + static var callHistoryTitle: String { return localizable.tr("Localizable", "call_history_title") } + /// Yesterday + static var callHistoryYesterdayTitle: String { return localizable.tr("Localizable", "call_history_yesterday_title") } /// Incoming Voice Call... static var callIncoming: String { return localizable.tr("Localizable", "call_incoming") } /// Audio Conference @@ -834,6 +864,10 @@ internal extension String { static var outgoingCall: String { return localizable.tr("Localizable", "outgoing_call") } /// Group Participants static var participants: String { return localizable.tr("Localizable", "participants") } + /// %d participants + static func participantsCount(_ p1: Int) -> String { + return localizable.tr("Localizable", "participants_count", p1) + } /// photo static var photo: String { return localizable.tr("Localizable", "photo") } /// Your photo is not saved to gallery @@ -1207,6 +1241,8 @@ internal extension String { /// VIDEO RESOLUTION static var videoResolution: String { return localizable.tr("Localizable", "VIDEO RESOLUTION") } /// Video Call + static var videoCall: String { return localizable.tr("Localizable", "video_call") } + /// Video Call static var videoCallStatus: String { return localizable.tr("Localizable", "video_call_status") } /// Video Messages static var videoMessages: String { return localizable.tr("Localizable", "video_messages") } @@ -1348,6 +1384,8 @@ internal extension String { static var wheelItemByUsername: String { return localizable.tr("Localizable", "wheel_item_byUsername") } /// Call static var wheelItemCall: String { return localizable.tr("Localizable", "wheel_item_call") } + /// Call History + static var wheelItemCallHistory: String { return localizable.tr("Localizable", "wheel_item_callHistory") } /// Calls static var wheelItemCalls: String { return localizable.tr("Localizable", "wheel_item_calls") } /// Camera @@ -1360,6 +1398,8 @@ internal extension String { static var wheelItemChatOptions: String { return localizable.tr("Localizable", "wheel_item_chatOptions") } /// Chats static var wheelItemChats: String { return localizable.tr("Localizable", "wheel_item_chats") } + /// Clear History + static var wheelItemClearHistory: String { return localizable.tr("Localizable", "wheel_item_clear_history") } /// Contact static var wheelItemContact: String { return localizable.tr("Localizable", "wheel_item_contact") } /// Contacts diff --git a/Nynja/HomeItemsFactory.swift b/Nynja/HomeItemsFactory.swift index c4c64d1b6e9dbe58880dea14b42813f21752a771..039ada8fd7521013bbafd796fe5845a3835de808 100644 --- a/Nynja/HomeItemsFactory.swift +++ b/Nynja/HomeItemsFactory.swift @@ -30,7 +30,7 @@ class HomeItemsFactory: WCBaseItemsFactory { navigateDelegate?.helpFeedBack(indexPath: indexPath) }) - call.subitems = [videoCall, voiceCall] + call.subitems = [callHistory, videoCall, voiceCall] return [call, edit, myQR, help] } @@ -45,10 +45,16 @@ class HomeItemsFactory: WCBaseItemsFactory { var videoCall: ImageFilledItemModel { return ImageFilledItemModel(nameImage: "ic_video", navItem: .videoCall, isSelectable: false, action: { [weak navigateDelegate] (item, indexPath) in navigateDelegate?.unavailableFunctionality() -// navigateDelegate?.conferenceVideoCall(indexPath: indexPath) }) } + var callHistory: ImageFilledItemModel { + return ImageFilledItemModel(nameImage: "ic_history", navItem: .callHistory, isSelectable: false, action: { [weak navigateDelegate] (item, indexPath) in + navigateDelegate?.callHistory(indexPath: indexPath) + }) + } + + // MARK: - Edit Profile var photo: ImageFilledItemModel { diff --git a/Nynja/Library/MessageFactory/MessageFactory.swift b/Nynja/Library/MessageFactory/MessageFactory.swift index 2b7161bd274ba33f9710c8cf345f900571511d9c..e82f109ede7a3bed41791cfec53a866c386316d9 100644 --- a/Nynja/Library/MessageFactory/MessageFactory.swift +++ b/Nynja/Library/MessageFactory/MessageFactory.swift @@ -73,7 +73,7 @@ final class MessageFactory: MessageFactoryProtocol, InitializeInjectable { message.messageStatus = .edit message.markAsEdited() - message.mentioned = newInputText.mentions.map { $0.memberId }.uniqueWithPreservedOrder() as [AnyObject]? + message.mentioned = newInputText.mentions.map { $0.memberId }.uniqueWithPreservedOrder() return message } @@ -86,7 +86,7 @@ final class MessageFactory: MessageFactoryProtocol, InitializeInjectable { message.messageStatus = .edit message.markAsEdited() - message.mentioned = action.mentioned?.splitByComma(Int64.self)?.uniqueWithPreservedOrder() as [AnyObject]? + message.mentioned = action.mentioned?.splitIntegerIdentifiers().uniqueWithPreservedOrder() return message } @@ -239,15 +239,7 @@ final class MessageFactory: MessageFactoryProtocol, InitializeInjectable { return message } - func makeCallMessage(members: [String], room: Room?) -> Message { - let descMimes = [DescMime.call(members: members)] - let descs = descMimes.map { Desc(mime: $0) } - let phoneId = storageService.phoneId - - return makeMessage(descs: descs, phoneId: phoneId, contact: nil, room: room) - } - - func makeMessageForDelete(message: Message, seenBy: [AnyObject]) -> Message { + func makeMessageForDelete(message: Message, seenBy: [Int64]) -> Message { let message = Message(message: message) message.from = storageService.phoneId message.seenby = seenBy @@ -264,7 +256,7 @@ private extension MessageFactory { message.created = Date.currentTimestamp message.files = descs message.messageStatus = nil - message.mentioned = mentioned as [AnyObject]? + message.mentioned = mentioned return message } diff --git a/Nynja/Library/MessageFactory/MessageFactoryProtocol.swift b/Nynja/Library/MessageFactory/MessageFactoryProtocol.swift index 3e19e0772ee4d4213ebe0581ec0244f0100ab4ed..384e6636ef007f6ed7096acfb29ad11872489ddb 100644 --- a/Nynja/Library/MessageFactory/MessageFactoryProtocol.swift +++ b/Nynja/Library/MessageFactory/MessageFactoryProtocol.swift @@ -38,9 +38,7 @@ protocol MessageFactoryProtocol: class { func makePaymentMessage(inputText: String, contact: Contact, notes: String?) -> Message - func makeCallMessage(members: [String], room: Room?) -> Message - - func makeMessageForDelete(message: Message, seenBy: [AnyObject]) -> Message + func makeMessageForDelete(message: Message, seenBy: [Int64]) -> Message } extension MessageFactoryProtocol { diff --git a/Nynja/Library/UI/ContextMenu/NynjaContextMenuItemsFactory+Messages.swift b/Nynja/Library/UI/ContextMenu/NynjaContextMenuItemsFactory+Messages.swift index a031bd1651cc52971a91472a7d3b6b284d58fa96..c54a2543f8c95aff32d0df5ebd53d0011ec9afbb 100644 --- a/Nynja/Library/UI/ContextMenu/NynjaContextMenuItemsFactory+Messages.swift +++ b/Nynja/Library/UI/ContextMenu/NynjaContextMenuItemsFactory+Messages.swift @@ -27,7 +27,7 @@ extension NynjaContextMenuItemsFactory { rows = NynjaContextMenuItemsFactory.locationMessageItems(for: model) case .sticker: rows = NynjaContextMenuItemsFactory.stickerMessageItems(for: model) - case .audioCall: + case .audioCall, .videoCall: rows = NynjaContextMenuItemsFactory.audioCallMessageItems(for: model) case .transfer: rows = NynjaContextMenuItemsFactory.makeTransferMessageItems(for: model) diff --git a/Nynja/Library/UI/Extensions/String+Split.swift b/Nynja/Library/UI/Extensions/String+Split.swift index 62ee4b13b0bd30e5f1e97bf7147645c38781640d..40baba1aa37fdf71c43337d2a617ec6b34c64633 100644 --- a/Nynja/Library/UI/Extensions/String+Split.swift +++ b/Nynja/Library/UI/Extensions/String+Split.swift @@ -13,7 +13,7 @@ protocol StringInitializable { extension Int64: StringInitializable { } extension String { - func splitByComma(_ type: T.Type) -> [T]? { + func splitByComma(_ type: T.Type) -> [T] { return components(separatedBy: ",").compactMap(T.init) } @@ -25,8 +25,8 @@ extension String { return components(separatedBy: ",") } - func splitIntegerIdentifiers() -> [AnyObject]? { - return splitByComma(Int64.self) as [AnyObject]? + func splitIntegerIdentifiers() -> [Int64] { + return splitByComma(Int64.self) as [Int64] } func split(by length: Int) -> [String] { diff --git a/Nynja/Library/UI/Lists/TableView/Cells/ChatListMessageCell/Model/ChatListMessageCellModel.swift b/Nynja/Library/UI/Lists/TableView/Cells/ChatListMessageCell/Model/ChatListMessageCellModel.swift index 05bb8883879443a75d35211db9240ecff94bdab3..25f0594a2cf75646d36bca13a93173eb179eee46 100644 --- a/Nynja/Library/UI/Lists/TableView/Cells/ChatListMessageCell/Model/ChatListMessageCellModel.swift +++ b/Nynja/Library/UI/Lists/TableView/Cells/ChatListMessageCell/Model/ChatListMessageCellModel.swift @@ -71,7 +71,7 @@ final class ChatListMessageCellModel: CellViewModel { setupSticker(file, in: cell) case .audio, .image, .video, .emoji, .location, .place, .videoCall, .file, .thumbnails, .contact, .reply: setup(mime: type, in: cell) - case .audioCall: + case .audioCall, .videoCall: setup(mime: type, in: cell) case .translate, .autotranslate, .transcribe, .transfer: break diff --git a/Nynja/MQTTModels/MessageExtension+BERT.swift b/Nynja/MQTTModels/MessageExtension+BERT.swift index d93009058d7d9891b4456a9a7e5821116d2baceb..c3567014830a84e3fb9c4713d375fdbb9c8620e1 100644 --- a/Nynja/MQTTModels/MessageExtension+BERT.swift +++ b/Nynja/MQTTModels/MessageExtension+BERT.swift @@ -25,17 +25,7 @@ extension Message: BERTEncodable { let localID = Bert.getBin(msg_id) var _seenBy: BertObject = BertNil() - - if let se = seenby as? [String] { - var obj = [BertObject]() - for i in se { - if let item = Bert.getBin(i) as? BertBinary { - obj.append(item) - } - } - _seenBy = BertList(fromElements: obj) - } - if let se = seenby as? [Int64] { + if let se = seenby { var obj = [BertObject]() for i in se { if let item = Bert.getBin(i) as? BertNumber { @@ -88,7 +78,7 @@ extension Message: BERTEncodable { } let mentioned: BertObject - if let objects = (self.mentioned as? [Int64])?.compactMap({ Bert.getBin($0) as? BertNumber }), !objects.isEmpty { + if let objects = self.mentioned?.compactMap({ Bert.getBin($0) as? BertNumber }), !objects.isEmpty { mentioned = BertList(fromElements: objects) } else { mentioned = BertNil() diff --git a/Nynja/MQTTModels/RoomExtension+BERT.swift b/Nynja/MQTTModels/RoomExtension+BERT.swift index cb713168136ab20b6c03000da72edba79df44d73..687839e3b0eb9a34080bd4afe462d6d7c1f6dd3a 100644 --- a/Nynja/MQTTModels/RoomExtension+BERT.swift +++ b/Nynja/MQTTModels/RoomExtension+BERT.swift @@ -31,7 +31,7 @@ extension Room: BERTEncodable { _name = Bert.getBin(name) } - if let objects = (self.mentions as? [Int64])?.compactMap({ Bert.getBin($0) as? BertNumber }) { + if let objects = self.mentions?.compactMap({ Bert.getBin($0) as? BertNumber }) { _mentions = BertList(fromElements: objects) } diff --git a/Nynja/MessageActionDAO.swift b/Nynja/MessageActionDAO.swift index 32f2cb49a974296e0a2fcd67a625569d98dbb0ac..baae7b46cdee9cd4b22e0cf837779bbffa5fbf8b 100644 --- a/Nynja/MessageActionDAO.swift +++ b/Nynja/MessageActionDAO.swift @@ -14,14 +14,16 @@ final class MessageActionDAO: MessageActionDAOProtocol { // MARK: -- Action static func fetchMessageAction(by messageServerId: MessageServerId) -> DBMessageAction? { return dbManager.fetch { db in - return try DBMessageAction.action(db, messageId: messageServerId) + return try DBMessageAction + .filter(Column.messageId == messageServerId) + .fetchOne(db) } } static func containsDeleteAction(for messageServerId: MessageServerId) -> Bool { return dbManager.rowExists( in: MessageActionTable.self, - where: "\(MessageActionTable.Column.messageId) = ? AND \(MessageActionTable.Column.action) = ?", + where: "\(Column.messageId) = ? AND \(Column.action) = ?", arguments: [messageServerId, DBMessageAction.Action.delete.rawValue] ) } diff --git a/Nynja/MessageBackgroundTaskHandler.swift b/Nynja/MessageBackgroundTaskHandler.swift index eddc6be663462860863a941b78068adf2ad80bc5..ee6560132dd6ba933ce45fa2c46ff7be8c72e5bc 100644 --- a/Nynja/MessageBackgroundTaskHandler.swift +++ b/Nynja/MessageBackgroundTaskHandler.swift @@ -113,13 +113,7 @@ final class MessageBackgroundTaskHandler: BackgroundTaskHandler { return nil } - let messageForDelete: Message - if action.phoneId.isEmpty { - messageForDelete = messageFactory.makeMessageForDelete(message: message, seenBy: [action.seenBy] as [AnyObject]) - } else { - let seenBy = action.seenBy == -1 ? -1 as AnyObject : action.phoneId as AnyObject - messageForDelete = messageFactory.makeMessageForDelete(message: message, seenBy: [seenBy] ) - } + let messageForDelete = messageFactory.makeMessageForDelete(message: message, seenBy: [action.seenBy]) return messageForDelete } diff --git a/Nynja/MessageDAO.swift b/Nynja/MessageDAO.swift index b15690f940ff789a1ece80afdf9adcad28c035e6..56f9cc31f71ba487a0c820bc1e6cfb8e926da0fe 100644 --- a/Nynja/MessageDAO.swift +++ b/Nynja/MessageDAO.swift @@ -10,6 +10,10 @@ import GRDBCipher final class MessageDAO: MessageDAOProtocol { + private static var userInfo: UserInfo { + return StorageService.sharedInstance + } + // MARK: - Save @@ -105,7 +109,7 @@ final class MessageDAO: MessageDAOProtocol { // MARK: -- Messages static func fetchMessages(to: String) -> [Message] { - guard let from = StorageService.sharedInstance.phoneId else { + guard let from = userInfo.phoneId else { return [] } @@ -272,21 +276,6 @@ final class MessageDAO: MessageDAOProtocol { return shouldMarkMessageAsDelete } - static func isMessageDeletedForAll(_ message: Message) -> Bool { - guard let seenBy = message.seenby else { - return false - } - return seenBy.contains { id in - if let id = id as? String { - return id == "-1" - } else if let id = id as? Int64 { - return id == -1 - } else { - return false - } - } - } - // MARK: - Primary Key @@ -305,31 +294,12 @@ final class MessageDAO: MessageDAOProtocol { } static func shouldMarkMessageAsDeleted(_ message: Message) -> Bool { - guard let seenBy = message.seenby else { + guard let rosterId = userInfo.rosterId else { + assertionFailure("rosterId should exist") return false } - guard !isMessageDeletedForAll(message) else { - return true - } - guard let phoneId = StorageService.sharedInstance.phoneId else { - assertionFailure("Check phoneId") - return false - } - - if message.p2pFeed != nil { - if let identifiers = seenBy as? [String] { - return identifiers.contains(phoneId) - } - } else if let roomId = message.mucFeed?.name, - let member = MemberDAO.findMemberBy(roomId: roomId, phoneId: phoneId), - let memberId = member.id { - - if let identifiers = message.seenby as? [Int64] { - return identifiers.contains(memberId) - } - } - return false + return !message.isSeenBy(rosterId: rosterId) } diff --git a/Nynja/MessageDAOProtocol.swift b/Nynja/MessageDAOProtocol.swift index ab7372b184a78f98f520bd62fcbb1dfc5274d332..e299f754436dee4cb5e2b473b2e1723f805cfdb6 100644 --- a/Nynja/MessageDAOProtocol.swift +++ b/Nynja/MessageDAOProtocol.swift @@ -61,7 +61,6 @@ protocol MessageDAOProtocol: DAOProtocol { // MARK: - Remove static func removeMessage(using message: Message) -> Bool - static func isMessageDeletedForAll(_ message: Message) -> Bool static func shouldMarkMessageAsDeleted(_ message: Message) -> Bool //MARK: - Primary Key diff --git a/Nynja/MigrationManager/Migrations/UpdateSeenByLogic.swift b/Nynja/MigrationManager/Migrations/UpdateSeenByLogic.swift new file mode 100644 index 0000000000000000000000000000000000000000..1d5a93f08724387857c153c9d2e7daa2fb1ba1a3 --- /dev/null +++ b/Nynja/MigrationManager/Migrations/UpdateSeenByLogic.swift @@ -0,0 +1,98 @@ +// +// UpdateSeenByLogic.swift +// Nynja +// +// Created by Volodymyr Hryhoriev on 12/5/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import GRDBCipher + +final class UpdateSeenByLogic: Migration { + + func migrate(_ db: Database) throws { + try updateSeenByValuesInMessageTable(db) + try removePhoneIdFromMessageActionTable(db) + } + + private func updateSeenByValuesInMessageTable(_ db: Database) throws { + try updateSeenByForP2pAndBoth(db) + try updateSeenByForMuc(db) + } + + private func updateSeenByForP2pAndBoth(_ db: Database) throws { + let sql = """ + update \(MessageTable.name) + set \(Column.seenBy) = + case + when \(Column.seenBy) = '-1' then '0' + when \(Column.feedType) = 0 and \(Column.seenBy) <> '-1' then '-' || substr(\(Column.seenBy), instr(\(Column.seenBy), '_') + 1) + else \(Column.seenBy) + end + """ + + try db.execute(sql) + } + + private func updateSeenByForMuc(_ db: Database) throws { + let rows = try fetchSeenByRows(db) + try updateSeenByForMuc(db, rows: rows) + } + + private func updateSeenByForMuc(_ db: Database, rows: [SeenByRow]) throws { + try rows.forEach { row in + let ids = try fetchMemberIdRows(db, seenBy: row.seenBy) + try updateSeenBy(db, messageLocalId: row.localId, ids: ids) + } + } + + private func fetchSeenByRows(_ db: Database) throws -> [SeenByRow] { + let columns: [Column] = [.localId, .seenBy] + return try DBMessage + .filter(Column.feedType == FeedType.muc.rawValue) + .filter(Column.seenBy != nil) + .filter(Column.seenBy != "0") + .select(columns) + .asRequest(of: SeenByRow.self) + .fetchAll(db) + } + + private func fetchMemberIdRows(_ db: Database, seenBy: String) throws -> [Int64] { + let sql = """ + select substr(\(Column.phoneId), instr(\(Column.phoneId), '_') + 1) + from \(MemberTable.name) + where \(Column.id) in (\(seenBy)) + """ + + return try SQLRequest(sql).asRequest(of: Int64.self).fetchAll(db) + } + + private func updateSeenBy(_ db: Database, messageLocalId: MessageLocalId, ids: [Int64]) throws { + let joinedsIds = ids + .map { -$0 } + .joinedByComma() + + let seenBy = joinedsIds.isEmpty ? "NULL" : "'\(joinedsIds)'" + + let sql = """ + update \(MessageTable.name) + set \(Column.seenBy) = \(seenBy) + where \(Column.localId) = '\(messageLocalId)' + """ + + try db.execute(sql) + } + + private func removePhoneIdFromMessageActionTable(_ db: Database) throws { + let columns: [Column] = [.messageId, .seenBy, .action] + try db.recreate(table: MessageActionTable.self, sharedColumns: Set(columns)) + } +} + +extension UpdateSeenByLogic { + + struct SeenByRow: RowConvertible, Codable { + let localId: String + let seenBy: String + } +} diff --git a/Nynja/MigrationManager/MigrationsProvider/MigrationsProviderImpl.swift b/Nynja/MigrationManager/MigrationsProvider/MigrationsProviderImpl.swift index cdc25bc2c2495c713caef13532ec351436d56ee1..58babc166ea6ee829b95529a5d9a59341caf5de6 100644 --- a/Nynja/MigrationManager/MigrationsProvider/MigrationsProviderImpl.swift +++ b/Nynja/MigrationManager/MigrationsProvider/MigrationsProviderImpl.swift @@ -14,6 +14,7 @@ final class MigrationsProviderImpl: MigrationsProvider { AddAutoColumnToConvertMessage(), CorrectMessageIdTypeInStarTable(), RemoveRoomMemberTable(), - RemoveP2pAndMucTables() + RemoveP2pAndMucTables(), + UpdateSeenByLogic() ] } diff --git a/Nynja/Modules/Call/CallScreens/CallInProgress/Interactor/CallInProgressInteractor.swift b/Nynja/Modules/Call/CallScreens/CallInProgress/Interactor/CallInProgressInteractor.swift index aeca6525f63a90acb28ae1b26c29e0503451ea0d..c3417d011baef092dcea0580230ecb000fdf4b00 100644 --- a/Nynja/Modules/Call/CallScreens/CallInProgress/Interactor/CallInProgressInteractor.swift +++ b/Nynja/Modules/Call/CallScreens/CallInProgress/Interactor/CallInProgressInteractor.swift @@ -8,98 +8,73 @@ class CallInProgressInteractor: CallInProgressInteractorInputProtocol, NynjaCallDelegate { - + weak var presenter: CallInProgressInteractorOutputProtocol! - + var timer: Timer? private let callService = NynjaCommunicatorService.sharedInstance private let audioSessionManager = AudioSessionManager.shared - + init() { callService.callDelegate = self - + NotificationCenter.default.addObserver(self, selector: #selector(applicationDidBecomeActive), name: UIApplication.didBecomeActiveNotification, object: nil) - + NotificationCenter.default.addObserver(self, selector: #selector(applicationWillResignActive), name: UIApplication.willResignActiveNotification, object: nil) - + } - + deinit { NotificationCenter.default.removeObserver(self, name: UIApplication.willResignActiveNotification, object: nil) - + NotificationCenter.default.removeObserver(self, name: UIApplication.didBecomeActiveNotification, object: nil) } - + var room: Room? { guard let roomId = nynCall?.externalInfo else { return nil } return RoomDAO.findRoom(by: roomId) } - + weak var nynCall: NYNCall? var duration = 0 - - let storageService: StorageService = .sharedInstance - let payloadBuilder: MessagePayloadBuilderInput = MessagePayloadBuilder() - private let mqttService: MQTTService = .sharedInstance - let processingManager = DefaultMessagesProcessingManager.shared - - private(set) lazy var messageFactory: MessageFactoryProtocol = { - let dependencies = MessageFactory.Dependencies( - storageService: storageService, - payloadBuilder: payloadBuilder - ) - - return MessageFactory(dependencies: dependencies) - }() - - private(set) lazy var messageSendingService: MessageSendingServiceProtocol = { - let dependencies = MessageSendingService.Dependencies( - mqttService: mqttService, - storageService: storageService, - processingManager: processingManager, - typingSenderService: TypingSenderService(dependencies: .init(mqttService: mqttService, - storageService: storageService)) - ) - return MessageSendingService(dependencies: dependencies) - }() - + private func callClosed(isMissed: Bool) { try? audioSessionManager.resetAudioSession() presenter.callClosed(isMissed: isMissed) } - + func acceptCall() { guard let ncall = self.nynCall else { return } callService.acceptConference(call: ncall) } - + func rejectCall() { if let nc = self.nynCall { callService.rejectConference(call: nc) callClosed(isMissed: false) } } - + private func declineConferenceCall(call: NYNCall) { if call.isModerator { presenter.askEndOrLeave() } else { call.hangup() } - + callClosed(isMissed: false) } - + private func declineTwoPartycall(call: NYNCall) { if call.isOutgoing() { call.hangup() @@ -108,40 +83,40 @@ class CallInProgressInteractor: CallInProgressInteractorInputProtocol, NynjaCall } else { call.hangup() } - + callClosed(isMissed: false) } - + func declineCall() { guard let nc = self.nynCall else { return } - + if nc.isConference() { declineConferenceCall(call: nc) } else { declineTwoPartycall(call: nc) } } - + func hangupCall() { if let nc = self.nynCall { nc.hangup() } } - + func activeSpeakerText()->String { var final = "" if let nc = self.nynCall { final = nc.participants.compactMap() { - + guard $0.isSpeaking, let name = $0.name, !name.isEmpty else { return nil } return name }.joined(separator: ", ") } return final } - + func groupCallSubject()->String { var final = "" if let nc = self.nynCall { @@ -149,7 +124,7 @@ class CallInProgressInteractor: CallInProgressInteractorInputProtocol, NynjaCall } return final } - + func isCallConnected() -> Bool { return self.nynCall?.isRunning ?? false } @@ -163,7 +138,7 @@ class CallInProgressInteractor: CallInProgressInteractorInputProtocol, NynjaCall nc.end() } } - + func speakerAction() { if audioSessionManager.speaker == .loud { audioSessionManager.speaker = .soft @@ -171,49 +146,49 @@ class CallInProgressInteractor: CallInProgressInteractorInputProtocol, NynjaCall audioSessionManager.speaker = .loud } } - + func messageAction() { stopTimer() self.nynCall?.muteCamera() } - + func startRinging() { presenter.setRingingStatus() } - + func microphoneAction() { nynCall?.toggleMicrophone() } - + func toggleMicrophone() { nynCall?.toggleMicrophone() } - + func isMuted()->Bool { - + var muted:Bool = false - + if let nc = self.nynCall { muted = nc.isMuted } - + return muted } - + func switchCamera() { callService.switchCamera() } - + // MARK: - VoxServiceDelegate func remoteVideoStreamDeleted () { presenter.remoteVideoStreamStopped() } - + func startTimer() { stopTimer() timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(runTimedCode), userInfo: nil, repeats: true) } - + @objc func runTimedCode() { guard let call = self.nynCall else { return } if let refTime = call.isConference() ? call.startTime : call.acceptTime { @@ -221,23 +196,23 @@ class CallInProgressInteractor: CallInProgressInteractorInputProtocol, NynjaCall self.presenter?.updateTime(text:text) } } - + func didAddVideoStreamForCall(call: NYNCall) { self.presenter.didAddRemoteVideoStream() } - + func didRemoveVideoStreamForCall(call: NYNCall) { self.presenter.didRemoveRemoteVideoStream() } - + func didStartLocalCapturerForCall(call: NYNCall) { self.presenter.didStartLocalCapturer() } - + func didStopLocalCapturerForCall(call: NYNCall) { self.presenter.didStopLocalCapturer() } - + func didUpdateSpeakingParticipants(call: NYNCall) { self.presenter.didUpdateSpeakingParticipants() } @@ -249,22 +224,22 @@ class CallInProgressInteractor: CallInProgressInteractorInputProtocol, NynjaCall func addRemoteVideoRenderer(inView view: UIView) { callService.attachRemoteVideoRenderer(inView: view) } - + func removeRemoteVideoRenderer(inView view: UIView) { callService.detachRemoteVideoRenderer(inView: view) } - + func attachLocalVideoPreview(inView view: UIView) { callService.attachLocalVideoPreview(inView: view) } - + func detachLocalVideoPreview(inView view: UIView) { callService.detachLocalVideoPreview(inView: view) } - + func cameraAction() { LogService.log(topic: .callSystem) { return "call interactor cameraAction" } - + if let call = self.nynCall { if call.isCameraRunning() { LogService.log(topic: .callSystem) { return "call interactor cameraAction, call will stop camera"} @@ -277,43 +252,43 @@ class CallInProgressInteractor: CallInProgressInteractorInputProtocol, NynjaCall LogService.log(topic: .callSystem) { return "call interactor cameraAction, no current call"} } } - + func isCameraRunning() -> Bool { if let call = self.nynCall { return call.isCameraRunning() } - + return false } - + func hasRemoteVideo() -> Bool { if let call = self.nynCall { return call.hasRemoteVideo() } - + return false } - + func unmuteCamera() { self.nynCall?.unmuteCamera() } - + func muteCamera() { self.nynCall?.muteCamera() } - + func updateCallStatus() { if let c = self.nynCall { self.stateDidChange(call: c, state: c.callState) } } - + func fetchCallParticipants() { if let ncall = self.nynCall { self.presenter.update(participants: ncall.participants) } } - + func stopTimer() { if timer != nil { timer?.invalidate() @@ -321,18 +296,18 @@ class CallInProgressInteractor: CallInProgressInteractorInputProtocol, NynjaCall duration = 0 } } - + func callEnded(call: NYNCall, isMissed: Bool) { stopTimer() callClosed(isMissed: isMissed) } - + func readyToStart(call: NYNCall) { call.start() } - + func stateDidChange(call: NYNCall, state: NYNCallState) { - + if let ncall = self.nynCall { if ncall.callId.elementsEqual(call.callId) { switch state { @@ -367,56 +342,42 @@ class CallInProgressInteractor: CallInProgressInteractorInputProtocol, NynjaCall } } } - + func participantsUpdated(call: NYNCall) { - + if let ncall = self.nynCall, call.callId.elementsEqual(ncall.callId) { self.presenter.update(participants: ncall.participants) } } - func conferenceCreated(call: NYNCall) { - sendCall(ncall: call) - } - - func sendCall(ncall: NYNCall) { - let room = Room() - room.id = ncall.externalInfo - let membersIds = ncall.participants.map({ $0.address ?? "" }) - let message = messageFactory.makeCallMessage(members: membersIds, room: room) - try? storageService.perform(action: .save, with: message) - messageSendingService.sendMessage(message) - } - func updateGroupCall(contacts: [Contact]) { - + if let ncall = self.nynCall { - + for ctc in contacts { callService.addConferenceMember(conferenceId: ncall.callId, phoneId: ctc.phone_id!, name: ctc.name ?? "") } } } - + func removeCallMember(memberId: String) { if let ncall = self.nynCall { callService.removeConferenceMember(conferenceId: ncall.callId, memberId: memberId) } } - + func muteParticipant(participantId: String, mute: Bool) { if let ncall = self.nynCall { mute ? ncall.muteParticipant(participantId) : ncall.unmuteParticipant(participantId) } } - + @objc func applicationDidBecomeActive() { self.nynCall?.unmuteCamera() } - + @objc func applicationWillResignActive() { self.nynCall?.muteCamera() } - -} +} diff --git a/Nynja/Modules/Call/CallScreens/CallInProgress/View/CallInProgressViewController.swift b/Nynja/Modules/Call/CallScreens/CallInProgress/View/CallInProgressViewController.swift index f39088ebdd29a62ff269b80e54786e046c871ac2..f53fdd672bedbc5c4b8d254c2eb9f917084c4f2f 100644 --- a/Nynja/Modules/Call/CallScreens/CallInProgress/View/CallInProgressViewController.swift +++ b/Nynja/Modules/Call/CallScreens/CallInProgress/View/CallInProgressViewController.swift @@ -332,7 +332,6 @@ class CallInProgressViewController: BaseVC, CallInProgressViewProtocol, AudioSes if let contact = self.contact { presenter.messageAcion(with: contact, isVideo: false) } else { - presenter.messageAcion(with: roomId, isVideo: false) } } diff --git a/Nynja/Modules/CallHistory/CallHistoryProtocols.swift b/Nynja/Modules/CallHistory/CallHistoryProtocols.swift new file mode 100644 index 0000000000000000000000000000000000000000..c788ade28b1667acf8d915a93d1b68b9acf261d4 --- /dev/null +++ b/Nynja/Modules/CallHistory/CallHistoryProtocols.swift @@ -0,0 +1,73 @@ +// +// CallHistoryProtocols.swift +// Nynja +// +// Created by Bozhko Terziev on 19.09.18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import UIKit + +protocol CallHistoryWireFrameProtocol: class { + + /** + * Add here your methods for communication PRESENTER -> WIREFRAME + */ + + func closeController() + func showContact(contact:Contact) + func show(callLog: NYNCallLog) + func callBackFor(contact:Contact) + func callBackVideoFor(contact:Contact) + func callBackFor(room:Room) +} + +protocol CallHistoryViewProtocol: class { + + /** + * Add here your methods for communication PRESENTER -> VIEW + */ + + var presenter: CallHistoryPresenterProtocol! { get set } +} + +protocol CallHistoryPresenterProtocol: BasePresenterProtocol, NavigationProtocol { + + /** + * Add here your methods for communication VIEW -> PRESENTER + */ + + var view: CallHistoryViewProtocol! { get set } + var interactor: CallHistoryInteractorInputProtocol! { get set } + var wireFrame: CallHistoryWireFrameProtocol! { get set } + func closeController() + func removeCallLogBy(index:UInt) -> NYNCallHistoryCode + func findContactBy(phoneId:String) -> Contact? + func findRoomBy(roomId:String) -> Room? + func showContact(contact: Contact) + func show(callLog: NYNCallLog) + func clearCallHistory() -> NYNCallHistoryCode + func callBackFor(contact:Contact) + func callBackVideoFor(contact:Contact) + func callBackFor(room:Room) +} + +protocol CallHistoryInteractorOutputProtocol: class { + + /** + * Add here your methods for communication INTERACTOR -> PRESENTER + */ +} + +protocol CallHistoryInteractorInputProtocol: class { + + /** + * Add here your methods for communication PRESENTER -> INTERACTOR + */ + + var presenter: CallHistoryInteractorOutputProtocol! { get set } + func removeCallLogBy(index:UInt) -> NYNCallHistoryCode + func findContactBy(phoneId:String) -> Contact? + func findRoomBy(roomId:String) -> Room? + func clearCallHistory() -> NYNCallHistoryCode +} diff --git a/Nynja/Modules/CallHistory/Interactor/CallHistoryInteractor.swift b/Nynja/Modules/CallHistory/Interactor/CallHistoryInteractor.swift new file mode 100644 index 0000000000000000000000000000000000000000..d499b9ebb18961bc6e48590224735263f7ca5b78 --- /dev/null +++ b/Nynja/Modules/CallHistory/Interactor/CallHistoryInteractor.swift @@ -0,0 +1,31 @@ +// +// CallHistoryInteractor.swift +// Nynja +// +// Created by Bozhko Terziev on 18.09.18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import UIKit + +class CallHistoryInteractor: CallHistoryInteractorInputProtocol { + + weak var presenter: CallHistoryInteractorOutputProtocol! + let callService:NynjaCommunicatorService = NynjaCommunicatorService.sharedInstance + + func removeCallLogBy(index:UInt) -> NYNCallHistoryCode { + return callService.removeCallLogBy(index:index) + } + + func findContactBy(phoneId:String) -> Contact? { + return ContactDAO.findContactBy(phoneId: phoneId) + } + + func findRoomBy(roomId:String) -> Room? { + return RoomDAO.findRoom(by:roomId) + } + + func clearCallHistory() -> NYNCallHistoryCode { + return callService.clearCallHistory() + } +} diff --git a/Nynja/Modules/CallHistory/Presenter/CallHistoryPresenter.swift b/Nynja/Modules/CallHistory/Presenter/CallHistoryPresenter.swift new file mode 100644 index 0000000000000000000000000000000000000000..41b5a1f1dd5eb8e33e8356385de0c56fcd349df5 --- /dev/null +++ b/Nynja/Modules/CallHistory/Presenter/CallHistoryPresenter.swift @@ -0,0 +1,66 @@ +// +// CallHistoryPresenter.swift +// Nynja +// +// Created by Bozhko Terziev on 18.09.18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import UIKit + +class CallHistoryPresenter: BasePresenter, CallHistoryPresenterProtocol, CallHistoryInteractorOutputProtocol { + override var itemsFactory: WCItemsFactory? { + + return CallHistoryItemsFactory() + } + + weak var view: CallHistoryViewProtocol! + var interactor: CallHistoryInteractorInputProtocol! + var wireFrame: CallHistoryWireFrameProtocol! + + func closeController() { + wireFrame.closeController() + } + + // MARK: NavigationProtocol + func back() { + closeController() + } + + func removeCallLogBy(index:UInt) -> NYNCallHistoryCode { + return interactor.removeCallLogBy(index:index) + } + + func findContactBy(phoneId:String) -> Contact? { + return interactor.findContactBy(phoneId:phoneId) + } + + func findRoomBy(roomId:String) -> Room? { + return interactor.findRoomBy(roomId:roomId) + } + + func showContact(contact: Contact) { + self.wireFrame.showContact(contact: contact) + } + + func show(callLog: NYNCallLog) { + self.wireFrame.show(callLog: callLog) + } + + func clearCallHistory() -> NYNCallHistoryCode { + return interactor.clearCallHistory() + } + + func callBackFor(contact:Contact) { + self.wireFrame.callBackFor(contact:contact) + } + + func callBackVideoFor(contact:Contact) { + self.wireFrame.callBackVideoFor(contact:contact) + } + + func callBackFor(room:Room) { + self.wireFrame.callBackFor(room:room) + } + +} diff --git a/Nynja/Modules/CallHistory/View/CallHistoryViewController.swift b/Nynja/Modules/CallHistory/View/CallHistoryViewController.swift new file mode 100644 index 0000000000000000000000000000000000000000..117adcc45ce3498a156645159b4b54edd2c7eb64 --- /dev/null +++ b/Nynja/Modules/CallHistory/View/CallHistoryViewController.swift @@ -0,0 +1,515 @@ +// +// CallHistoryViewController.swift +// Nynja +// +// Created by Bozhko Terziev on 18.09.18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import UIKit + +enum CallHistoryMode { + case all + case outgoing + case incoming + case missed +} + +class CallHistoryViewController: BaseVC, CallHistoryViewProtocol, UIScrollViewDelegate, CallHistoryDataSourceDelegate, UITableViewDelegate, NynjaCallHistoryManagerDelegate { + + var presenter: CallHistoryPresenterProtocol! { + didSet { + _presenter = presenter + } + } + + let buttonFontSize: CGFloat = CGFloat(17.0.adjustedByWidth) + var contentWidth: CGFloat = Constraints.button.buttonOffset + var callHistoryMode: CallHistoryMode = .all + + var dataController: CallHistoryDataController? + var tableViewDataSource: CallHistoryDataSource? + + private struct Constraints { + + static let offset: CGFloat = 10.0 + static let rowHeight: CGFloat = 10.0 + static let separatorHeight: CGFloat = 1.0 + + enum tableView { + static let topInset = 16.0 + static let rowHeight = 64.0 + } + + enum scrollView { + static let height: CGFloat = 68.0 + static let bottomInset: CGFloat = 14.0 + static let rightInset: CGFloat = 14.0 + } + + enum button { + static let height: CGFloat = 44.0 + static let buttonOffset: CGFloat = 10.0 + } + } + + private func cleanUpBeforeReloading() { + tableViewDataSource = nil + } + + private func reloadTableView() { + if NynjaCommunicatorService.sharedInstance.synchronizeHistory() == .PENDING { + showSpinner() + } else { + buildDataSource() + } + } + + private func buildDataSource() { + self.dataController?.buildDataSourceWith(mode: self.callHistoryMode, completionHandler: { (dataSource) -> Void in + self.cleanUpBeforeReloading() + self.tableViewDataSource = CallHistoryDataSource(tableView:self.tableView, dataSource: dataSource) + self.tableViewDataSource?.delegate = self + + self.emptyHistoryView.should(shown: dataSource.isEmpty) + self.tableView.isHidden = dataSource.isEmpty + self.tableView.reloadData() + }) + } + + override func viewDidLoad() { + super.viewDidLoad() + self.dataController = CallHistoryDataController() + reloadTableView() + } + + private func deselectAllButtons() { + buttonAll.isSelected = false + buttonOutgoing.isSelected = false + buttonIncoming.isSelected = false + buttonMissed.isSelected = false + } + + private func updateScrollViewContentSize() { + self.scrollView.contentSize = CGSize(width: contentWidth, + height: CGFloat(Constraints.scrollView.height.adjustedByWidth)) + } + + private func buttonWidthBy(text:String) -> CGFloat { + + var width:CGFloat = 0.0 + + if let font = UIFont.makeFont(with: FontFamily.NotoSans.medium.name, height:buttonFontSize) { + let fontAttributes = [NSAttributedString.Key.font: font] + let size = (text as NSString).size(withAttributes: fontAttributes) + width = size.width + 4*Constraints.button.buttonOffset + } + + return width + } + + private lazy var emptyHistoryView: EmptyCallHistoryView = { + let ehv = EmptyCallHistoryView() + + ehv.isHidden = false + ehv.backgroundColor = .clear + self.view.addSubview(ehv) + + ehv.snp.makeConstraints { (make) in + make.top.equalTo(navigationView.snp.bottom) + make.left.right.equalToSuperview() + make.bottom.equalTo(scrollView.snp.top).offset(-2*Constraints.offset) + } + + return ehv + }() + + private lazy var tableView: UITableView = { + let tblView = UITableView.default + + tblView.clipsToBounds = true + tblView.keyboardDismissMode = .interactive + tblView.backgroundColor = .clear + tblView.rowHeight = CGFloat(Constraints.tableView.rowHeight) + tblView.estimatedRowHeight = CGFloat(Constraints.tableView.rowHeight) + tblView.register(CallHistoryTableViewCell.self, forCellReuseIdentifier: CallHistoryTableViewCell.cellId) + tblView.separatorColor = UIColor.nynja.backgroundGray + tblView.separatorStyle = .singleLine + tblView.delegate = self + + self.view.addSubview(tblView) + tblView.snp.makeConstraints { (make) in + make.top.equalTo(navigationView.snp.bottom) + make.left.right.equalToSuperview() + make.bottom.equalTo(scrollView.snp.top).offset(-2*Constraints.offset) + } + + return tblView + }() + + private lazy var scrollView: UIScrollView = { + let scroll = UIScrollView() + scroll.backgroundColor = .clear + scroll.delegate = self + scroll.contentSize = CGSize(width: UIScreen.main.bounds.size.width - CGFloat(Constraints.scrollView.rightInset.adjustedByWidth + Constraints.scrollView.height.adjustedByWidth), + height: CGFloat(Constraints.scrollView.height.adjustedByWidth)) + scroll.isPagingEnabled = false + scroll.showsVerticalScrollIndicator = false + scroll.showsHorizontalScrollIndicator = false + + self.view.addSubview(scroll) + + scroll.snp.makeConstraints({ (make) in + make.left.equalToSuperview() + make.height.equalTo(Constraints.scrollView.height.adjustedByWidth) + make.right.equalToSuperview().offset(-Constraints.scrollView.rightInset.adjustedByWidth - Constraints.scrollView.height.adjustedByWidth) + adjustVerticalInset(.bottom, make: make, offset: -Constraints.scrollView.bottomInset) + }) + + return scroll + }() + + private lazy var buttonAll: UIButton = { + let butotnText = String.localizable.callHistoryButtonAllTitle.uppercased() + let width = buttonWidthBy(text: butotnText) + let height = CGFloat(Constraints.button.height.adjustedByWidth) + + let button = UIButton() + button.titleLabel?.font = UIFont.makeFont(with: FontFamily.NotoSans.medium.name, height:buttonFontSize) + button.setTitle(butotnText, for: .normal) + button.setTitleColor(UIColor.nynja.mainRed, for: .normal) + button.setTitleColor(UIColor.nynja.white, for: .selected) + button.setTitleColor(UIColor.nynja.white, for: .highlighted) + button.setBackgroundImage(UIImage.imageWithColor(color: UIColor.nynja.clear), for: .normal) + button.setBackgroundImage(UIImage.imageWithColor(color: UIColor.nynja.mainRed), for: .selected) + button.setBackgroundImage(UIImage.imageWithColor(color: UIColor.nynja.mainRed), for: .highlighted) + + button.addTarget(self, action: #selector(allTapped(_:)), for: .touchUpInside) + button.backgroundColor = .clear + button.layer.cornerRadius = height/2 + button.layer.borderWidth = 1.0 + button.layer.borderColor = UIColor.nynja.mainRed.cgColor + button.clipsToBounds = true + self.scrollView.addSubview(button) + contentWidth += (CGFloat(width) + Constraints.button.buttonOffset) + updateScrollViewContentSize() + button.snp.makeConstraints({ (make) in + make.width.equalTo(width) + make.height.equalTo(height) + + make.centerY.equalToSuperview() + make.left.equalToSuperview().offset(Constraints.button.buttonOffset) + + }) + + return button + }() + + private lazy var buttonOutgoing: UIButton = { + let butotnText = String.localizable.callHistoryButtonOutgoingTitle.uppercased() + let width = buttonWidthBy(text: butotnText) + let height = CGFloat(Constraints.button.height.adjustedByWidth) + + let button = UIButton() + button.titleLabel?.font = UIFont.makeFont(with: FontFamily.NotoSans.medium.name, height:buttonFontSize) + button.setTitle(butotnText, for: .normal) + button.setTitleColor(UIColor.nynja.mainRed, for: .normal) + button.setTitleColor(UIColor.nynja.white, for: .selected) + button.setTitleColor(UIColor.nynja.white, for: .highlighted) + button.setBackgroundImage(UIImage.imageWithColor(color: UIColor.nynja.clear), for: .normal) + button.setBackgroundImage(UIImage.imageWithColor(color: UIColor.nynja.mainRed), for: .selected) + button.setBackgroundImage(UIImage.imageWithColor(color: UIColor.nynja.mainRed), for: .highlighted) + + button.addTarget(self, action: #selector(outgoingTapped(_:)), for: .touchUpInside) + button.backgroundColor = .clear + button.layer.cornerRadius = height/2 + button.layer.borderWidth = 1.0 + button.layer.borderColor = UIColor.nynja.mainRed.cgColor + button.clipsToBounds = true + self.scrollView.addSubview(button) + contentWidth += (CGFloat(width) + Constraints.button.buttonOffset) + updateScrollViewContentSize() + button.snp.makeConstraints({ (make) in + make.width.equalTo(width) + make.height.equalTo(height) + + make.centerY.equalToSuperview() + make.left.equalTo(buttonAll.snp.right).offset(Constraints.button.buttonOffset) + }) + + return button + }() + + private lazy var buttonIncoming: UIButton = { + let butotnText = String.localizable.callHistoryButtonIncomingTitle.uppercased() + let width = buttonWidthBy(text: butotnText) + let height = CGFloat(Constraints.button.height.adjustedByWidth) + + let button = UIButton() + button.titleLabel?.font = UIFont.makeFont(with: FontFamily.NotoSans.medium.name, height:buttonFontSize) + button.setTitle(butotnText, for: .normal) + button.setTitleColor(UIColor.nynja.mainRed, for: .normal) + button.setTitleColor(UIColor.nynja.white, for: .selected) + button.setTitleColor(UIColor.nynja.white, for: .highlighted) + button.setBackgroundImage(UIImage.imageWithColor(color: UIColor.nynja.clear), for: .normal) + button.setBackgroundImage(UIImage.imageWithColor(color: UIColor.nynja.mainRed), for: .selected) + button.setBackgroundImage(UIImage.imageWithColor(color: UIColor.nynja.mainRed), for: .highlighted) + + button.addTarget(self, action: #selector(incomingTapped(_:)), for: .touchUpInside) + button.backgroundColor = .clear + button.layer.cornerRadius = height/2 + button.layer.borderWidth = 1.0 + button.layer.borderColor = UIColor.nynja.mainRed.cgColor + button.clipsToBounds = true + self.scrollView.addSubview(button) + contentWidth += (CGFloat(width) + Constraints.button.buttonOffset) + updateScrollViewContentSize() + button.snp.makeConstraints({ (make) in + make.width.equalTo(width) + make.height.equalTo(height) + + make.centerY.equalToSuperview() + make.left.equalTo(buttonOutgoing.snp.right).offset(Constraints.button.buttonOffset) + }) + + return button + }() + + private lazy var buttonMissed: UIButton = { + let butotnText = String.localizable.callHistoryButtonMissedTitle.uppercased() + let width = buttonWidthBy(text: butotnText) + let height = CGFloat(Constraints.button.height.adjustedByWidth) + + let button = UIButton() + button.titleLabel?.font = UIFont.makeFont(with: FontFamily.NotoSans.medium.name, height:buttonFontSize) + button.setTitle(butotnText, for: .normal) + button.setTitleColor(UIColor.nynja.mainRed, for: .normal) + button.setTitleColor(UIColor.nynja.white, for: .selected) + button.setTitleColor(UIColor.nynja.white, for: .highlighted) + button.setBackgroundImage(UIImage.imageWithColor(color: UIColor.nynja.clear), for: .normal) + button.setBackgroundImage(UIImage.imageWithColor(color: UIColor.nynja.mainRed), for: .selected) + button.setBackgroundImage(UIImage.imageWithColor(color: UIColor.nynja.mainRed), for: .highlighted) + + button.addTarget(self, action: #selector(missedTapped(_:)), for: .touchUpInside) + button.backgroundColor = .clear + button.layer.cornerRadius = height/2 + button.layer.borderWidth = 1.0 + button.layer.borderColor = UIColor.nynja.mainRed.cgColor + button.clipsToBounds = true + self.scrollView.addSubview(button) + contentWidth += (CGFloat(width) + Constraints.button.buttonOffset) + updateScrollViewContentSize() + button.snp.makeConstraints({ (make) in + make.width.equalTo(width) + make.height.equalTo(height) + + make.centerY.equalToSuperview() + make.left.equalTo(buttonIncoming.snp.right).offset(Constraints.button.buttonOffset) + }) + + return button + }() + + public func clearHistory() { + AlertManager.sharedInstance.showAlertWithTwoActions(title: "", + message: String.localizable.callHistoryClearQuestionAlert, + firstActionTitle: String.localizable.no, + secondActionTitle: String.localizable.yes, + firstAction: nil, + secondAction: { + + if .PENDING == self.presenter.clearCallHistory() { + self.showSpinner() + } else { + self.reloadTableView() + } + }) + } + + public init() { + super.init(nibName: nil, bundle: nil) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func initialize() { + super.initialize() + + setupUI() + + NynjaCommunicatorService.sharedInstance.setCallHistory(delegate: self) + } + + private func setupUI() { + navigationView.isSeparatorVisible = true + tableView.isHidden = false + scrollView.isHidden = false + buttonMissed.isHidden = false + + DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.3) { + self.buttonAll.isHidden = false + } + + let title:String = String.localizable.callHistoryTitle.uppercased() + let backBtnImage:UIImage? = UIImage.nynja.icBackNavigation.image + let navHandler:NavigationProtocol? = presenter + + navigationView.configure(config: NavigationView.Config( + isVisibleSeparator: navigationView.isSeparatorVisible ?? false, + isVisibleBackButton: (nil != backBtnImage), + title: title, + navigationHandler: navHandler, + backButtonImage: backBtnImage) + ) + + screenTitle = title + allTapped(buttonAll) + } + + private func callBackFor(indexPath: IndexPath) { + let cell:CallHistoryTableViewCell = tableView.cellForRow(at: indexPath) as! CallHistoryTableViewCell + + if let m = cell.model { + if m.kind == .DIRECT { + if let contact = presenter.findContactBy(phoneId: m.address) { + if m.media == .VIDEO { + presenter.callBackVideoFor(contact: contact) + } else { + presenter.callBackFor(contact: contact) + } + } + } + } + } + + // MARK UIScrollView Delegates + + func scrollViewDidScroll(_ scrollView: UIScrollView) { + } + + func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { + } + + // MARK Cal History Data Source Delegates + + func showMenuWith(callHistoryCell: CallHistoryTableViewCell) { + var arrActions = [UIAlertAction]() + + let cancelAction = UIAlertAction(title: String.localizable.cancel, style: .cancel) + let deleteAction = UIAlertAction(title: String.localizable.delete, style: .default) { [weak self] _ in + + if let m = callHistoryCell.model { + if let ds = self?.tableViewDataSource { + if let ip = self?.tableView.indexPath(for: callHistoryCell) { + if let slf = self { + if .PENDING != slf.presenter.removeCallLogBy(index:m.index) { + ds.dataSource.remove(at: ip.row) + slf.tableView.deleteRows(at: [ip], with: UITableView.RowAnimation.middle) + } + } + } + } + } + } + + arrActions.append(deleteAction) + arrActions.append(cancelAction) + + AlertManager.sharedInstance.showActionSheet(title: nil, message: nil, actions: arrActions) + } + + func showInfoWith(callHistoryCell: CallHistoryTableViewCell) { + if let m = callHistoryCell.model { + if m.kind == .DIRECT { + if let contact = presenter.findContactBy(phoneId: m.address) { + presenter.showContact(contact: contact) + } + } else { + + switch NynjaCommunicatorService.sharedInstance.getCallHistoryRecordMembers(id: m.log.identifier) { + case .PENDING: + self.showSpinner() + break + case .OK: + presenter.show(callLog: m.log) + case .UNIMPLEMENTED,.UNKNOWN: + break + } + } + } + } + + // MARK: - Actions + + @objc private func allTapped(_ button: UIButton) { + deselectAllButtons() + button.isSelected = !button.isSelected + callHistoryMode = .all + reloadTableView() + } + + @objc private func outgoingTapped(_ button: UIButton) { + deselectAllButtons() + button.isSelected = !button.isSelected + callHistoryMode = .outgoing + reloadTableView() + } + + @objc private func incomingTapped(_ button: UIButton) { + deselectAllButtons() + button.isSelected = !button.isSelected + callHistoryMode = .incoming + reloadTableView() + } + + @objc private func missedTapped(_ button: UIButton) { + deselectAllButtons() + button.isSelected = !button.isSelected + callHistoryMode = .missed + reloadTableView() + } + + // MARK: UITableView Delegates + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + tableView.deselectRow(at: indexPath, animated: false) + callBackFor(indexPath: indexPath) + } + + // MARK: NynjaCallHistoryManager Delegates + func managerHistoryDidUpdate(_ manager: NynjaCallHistoryManager) { + DispatchQueue.main.async { + self.hideSpinner() + self.buildDataSource() + } + } + + func managerHistoryDidClear(_ manager: NynjaCallHistoryManager) { + DispatchQueue.main.async { + self.hideSpinner() + self.reloadTableView() + } + } + + func manager(_ manager: NynjaCallHistoryManager, didDeleteRecordWith index: UInt) { + DispatchQueue.main.async { + self.reloadTableView() + } + } + + func manager(_ manager: NynjaCallHistoryManager, didFetchParticipantsWithRecordId identifier: String) { + DispatchQueue.main.async { + self.hideSpinner() + + for cell in self.tableView.visibleCells { + if let histCell = cell as? CallHistoryTableViewCell, + let model = histCell.model, model.log.identifier.elementsEqual(identifier) { + self.presenter.show(callLog: model.log) + break + } + } + } + } +} diff --git a/Nynja/Modules/CallHistory/View/TableView/CallHistoryCellModel.swift b/Nynja/Modules/CallHistory/View/TableView/CallHistoryCellModel.swift new file mode 100644 index 0000000000000000000000000000000000000000..b1374f755fb232c71f9ff49bbe3bfca501d8e771 --- /dev/null +++ b/Nynja/Modules/CallHistory/View/TableView/CallHistoryCellModel.swift @@ -0,0 +1,151 @@ +// +// CallHistoryCellModel.swift +// Nynja +// +// Created by Bozhko Terziev on 26.10.18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import UIKit + +class CallHistoryCellModel: NSObject { + + private var _log: NYNCallLog + + static let todayDateFormatter: DateFormatter = { + let formatter = DateFormatter() + formatter.dateStyle = .none + formatter.timeStyle = .short + return formatter + }() + + static let lastWeekDateFormatter: DateFormatter = { + let formatter = DateFormatter() + formatter.dateFormat = "EEEE" + formatter.timeZone = TimeZone.current + return formatter + }() + + static let dateFormatter: DateFormatter = { + let formatter = DateFormatter() + formatter.dateStyle = .short + formatter.timeStyle = .none + return formatter + }() + + var index: UInt {get{return log.index}} + var identifier: String {get{return log.identifier}} + var kind: NYNCallLogKind {get{return log.kind}} + var status: NYNCallLogStatus {get{return log.status}} + var direction: NYNCallLogDir {get{return log.direction}} + var media: NYNCallLogMedia {get{return log.media}} + + var subject: String {get{return log.subject}} + var address: String {get{return log.address}} + var startTime: Date {get{return log.startTime}} + var duration: UInt {get{return log.duration}} + var participantsCount: UInt {get{return log.participantsCount}} + + init(log: NYNCallLog) { + self._log = log + } + + var log:NYNCallLog { + get { + return _log + } + } + + class func isDateInLastWeek(date:Date) ->Bool { + let calendar = NSCalendar.current + let unitFlags = Set([.day]) + let components = calendar.dateComponents(unitFlags, from: date, to: Date()) + var isInLastWeek:Bool = false + + if let d = components.day { + isInLastWeek = (d < 7) + } + + return isInLastWeek + } + + class func stringFrom(date:Date) ->String { + if Calendar.current.isDateInToday(date) { + return todayDateFormatter.string(from:date) + } else if Calendar.current.isDateInYesterday(date) { + return String.localizable.callHistoryYesterdayTitle + } else if CallHistoryCellModel.isDateInLastWeek(date:date) { + return lastWeekDateFormatter.string(from:date) + } else { + return dateFormatter.string(from:date) + } + } + + class func stringFrom(duration:UInt)-> String? { + let formatter = DateComponentsFormatter() + formatter.unitsStyle = .abbreviated + formatter.allowedUnits = [.hour, .minute, .second] + formatter.zeroFormattingBehavior = [ .default ] + + let formattedDuration = formatter.string(from: Double(duration)) + + return formattedDuration + } + + class func stringFrom(status:NYNCallLogStatus, direction:NYNCallLogDir, duration:UInt)-> String { + switch status { + case .ANSWERED: + if direction == .INBOUND { + if duration > 0, let duration = stringFrom(duration:duration) { + return String(format: "%@ (%@)", String.localizable.callHistoryLogDirInbound, duration) + } else { + return String.localizable.callHistoryLogDirInbound + } + } else { + if duration > 0, let duration = stringFrom(duration:duration) { + return String(format: "%@ (%@)", String.localizable.callHistoryLogDirOutbound, duration) + } else { + return String.localizable.callHistoryLogDirOutbound + } + } + case .CANCELED: + return String.localizable.callHistoryLogStatusCanceled + case .DECLINED: + return String.localizable.callHistoryLogStatusDeclined + case .MISSED: + return String.localizable.callHistoryLogStatusMissed + case .UNANSWERED: + return String.localizable.callHistoryLogStatusNoanswer + } + } + +class func imageFrom(status:NYNCallLogStatus, media:NYNCallLogMedia, direction:NYNCallLogDir)-> UIImage { + if media == .AUDIO { + switch status { + case .ANSWERED: + return (direction == .INBOUND) ? UIImage.nynja.CallHistory.icIncomingVoice.image : UIImage.nynja.CallHistory.icOutgoingVoice.image + case .CANCELED: + return UIImage.nynja.CallHistory.icCanceledVoice.image + case .DECLINED: + return UIImage.nynja.CallHistory.icCanceledVoice.image + case .MISSED: + return UIImage.nynja.CallHistory.icMissedVoice.image + case .UNANSWERED: + return UIImage.nynja.CallHistory.icNoAnswerVoice.image + } + } else { + switch status { + case .ANSWERED: + return (direction == .INBOUND) ? UIImage.nynja.CallHistory.icIncomingVideo.image : UIImage.nynja.CallHistory.icOutgoingVideo.image + case .CANCELED: + return UIImage.nynja.CallHistory.icCanceledVideo.image + case .DECLINED: + return UIImage.nynja.CallHistory.icCanceledVideo.image + case .MISSED: + return UIImage.nynja.CallHistory.icMissedVideo.image + case .UNANSWERED: + return UIImage.nynja.CallHistory.icNoAnswerVideo.image + } + } + } +} diff --git a/Nynja/Modules/CallHistory/View/TableView/CallHistoryDataController.swift b/Nynja/Modules/CallHistory/View/TableView/CallHistoryDataController.swift new file mode 100644 index 0000000000000000000000000000000000000000..d411efe812945609f5ec0e1f26b863979c08df99 --- /dev/null +++ b/Nynja/Modules/CallHistory/View/TableView/CallHistoryDataController.swift @@ -0,0 +1,44 @@ +// +// CallHistoryDataController.swift +// Nynja +// +// Created by Bozhko Terziev on 7.11.18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import UIKit + +typealias CompletionHandler = (_ dataSource:Array) -> Void + +class CallHistoryDataController: NSObject { + + func buildDataSourceWith(mode: CallHistoryMode, completionHandler: CompletionHandler) { + + let dataSource = NSMutableArray() + var results:NYNCallLogFetchedResults? = nil + + switch mode { + case .all: + results = NynjaCommunicatorService.sharedInstance.callHistoryFetchAll() + case .missed: + results = NynjaCommunicatorService.sharedInstance.callHistoryFetchMissed() + case .outgoing: + results = NynjaCommunicatorService.sharedInstance.callHistoryFetchOutgoing() + case .incoming: + results = NynjaCommunicatorService.sharedInstance.callHistoryFetchIncoming() + } + + if let r = results { + let count = r.count() + + for i in 0.. Void + func showInfoWith( callHistoryCell: CallHistoryTableViewCell) -> Void +} + +class CallHistoryDataSource: NSObject, CallHistoryTableViewCellDelegate { + + var dataSource: Array + weak var delegate:CallHistoryDataSourceDelegate? + + init(tableView: UITableView, dataSource: Array) { + self.dataSource = dataSource + super.init() + tableView.dataSource = self + } + + // MARK: - Call History Cell Delegate + + func showMenuWith(callHistoryCell: CallHistoryTableViewCell) { + delegate?.showMenuWith(callHistoryCell: callHistoryCell) + } + + func showInfoWith(callHistoryCell: CallHistoryTableViewCell) { + delegate?.showInfoWith(callHistoryCell: callHistoryCell) + } +} + +extension CallHistoryDataSource: UITableViewDataSource { + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return self.dataSource.count + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: CallHistoryTableViewCell.cellId, for: indexPath) as! CallHistoryTableViewCell + cell.model = self.dataSource[indexPath.row] + cell.updateCell() + cell.delegate = self + return cell + } +} diff --git a/Nynja/Modules/CallHistory/View/TableView/CallHistoryTableViewCell.swift b/Nynja/Modules/CallHistory/View/TableView/CallHistoryTableViewCell.swift new file mode 100644 index 0000000000000000000000000000000000000000..221d70dab485fb7469e9583ec5ff15fe0788832b --- /dev/null +++ b/Nynja/Modules/CallHistory/View/TableView/CallHistoryTableViewCell.swift @@ -0,0 +1,237 @@ +// +// CallHistoryTableViewCell.swift +// Nynja +// +// Created by Bozhko Terziev on 26.10.18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import UIKit + +protocol CallHistoryTableViewCellDelegate: class { + + func showMenuWith( callHistoryCell: CallHistoryTableViewCell) -> Void + func showInfoWith( callHistoryCell: CallHistoryTableViewCell) -> Void +} + +class CallHistoryTableViewCell: UITableViewCell { + + var model:CallHistoryCellModel? + weak var delegate:CallHistoryTableViewCellDelegate? + private var longPressRecognizer: UILongPressGestureRecognizer? + + static var cellId = "CallHistoryTableViewCell" + + private lazy var avatarImageView: UIImageView = { + let av = UIImageView() + av.contentMode = .scaleToFill + av.layer.cornerRadius = CGFloat(Constraints.avatar.height/2) + av.backgroundColor = UIColor.nynja.lightGray + av.clipsToBounds = true + self.contentView.addSubview(av) + av.snp.makeConstraints { (make) in + make.left.equalToSuperview().offset(Constraints.avatar.leftOffset) + make.centerY.equalToSuperview() + make.height.width.equalTo(Constraints.avatar.height) + } + + return av + }() + + private lazy var statusImageView: UIImageView = { + let av = UIImageView() + av.contentMode = .scaleToFill + av.backgroundColor = .clear + av.image = UIImage.nynja.CallHistory.icCanceledVideo.image + + self.contentView.addSubview(av) + av.snp.makeConstraints { (make) in + make.left.equalTo(avatarImageView.snp.right).offset(Constraints.statusImageView.leftOffset) + make.bottom.equalToSuperview().offset(-Constraints.statusImageView.bottomOffset) + make.height.width.equalTo(Constraints.statusImageView.height) + } + + return av + }() + + private lazy var infoButton: UIButton = { + let button = UIButton(type: .infoLight) + button.tintColor = UIColor.nynja.lightGray + button.addTarget(self, action: #selector(onInfoTap(sender:)), for: .touchUpInside) + button.setImage(nil, for: .normal) + button.contentMode = .center + + self.contentView.addSubview(button) + button.snp.makeConstraints { (make) in + make.right.equalToSuperview().offset(-Constraints.infoButton.rightOffset) + make.height.width.equalTo(Constraints.infoButton.height) + make.centerY.equalTo(statusImageView.snp.centerY) + } + + return button + }() + + private lazy var statusLabel: UILabel = { + let label = UILabel() + label.textAlignment = .left + label.font = UIFont.makeFont(with: FontFamily.NotoSans.regular.name, height: 20.0) + label.textColor = UIColor.nynja.lightGray + label.backgroundColor = UIColor.nynja.clear + label.text = "Incomming call" + + self.contentView.addSubview(label) + label.snp.makeConstraints { (make) in + make.left.equalTo(statusImageView.snp.right).offset(Constraints.statusLabel.leftOffset) + make.right.equalTo(infoButton.snp.left).offset(-Constraints.statusLabel.rightOffset) + make.centerY.equalTo(statusImageView.snp.centerY) + } + + return label + }() + + private lazy var nameLabel: UILabel = { + let label = UILabel() + label.textAlignment = .left + label.font = UIFont.makeFont(with: FontFamily.NotoSans.regular.name, height: 22.0) + label.textColor = UIColor.nynja.white + label.backgroundColor = UIColor.nynja.clear + label.text = "Astronomy or Astrology" + + self.contentView.addSubview(label) + label.snp.makeConstraints { (make) in + make.left.equalTo(avatarImageView.snp.right).offset(Constraints.labelName.leftOffset) + make.top.equalTo(Constraints.labelName.topOffset) + } + + return label + }() + + private lazy var timeLabel: UILabel = { + let label = UILabel() + label.textAlignment = .right + label.font = UIFont.makeFont(with: FontFamily.NotoSans.regular.name, height: 16.0) + label.textColor = UIColor.nynja.lightGray + label.backgroundColor = UIColor.nynja.clear + label.text = "10:55 PM" + + self.contentView.addSubview(label) + label.snp.makeConstraints { (make) in + make.right.equalToSuperview().offset(-Constraints.labelTime.rightOffset) + make.centerY.equalTo(nameLabel.snp.centerY) + make.left.equalTo(nameLabel.snp.right).offset(Constraints.labelTime.horInset) + } + + return label + }() + + // MARK: - Init + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + baseSetup() + } + + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + baseSetup() + } + + private func baseSetup() { + self.backgroundColor = UIColor.nynja.clear + self.selectionStyle = .none + + longPressRecognizer = UILongPressGestureRecognizer.init(target: self, action: #selector(longPressed(_:))) + + if let lp = longPressRecognizer{ + lp.minimumPressDuration = 0.5 + lp.delegate = self + self.contentView.addGestureRecognizer(lp) + } + } + + // MARK: - Public + + func updateCell() { + if let m = self.model { + nameLabel.text = m.subject + timeLabel.text = CallHistoryCellModel.stringFrom(date: m.startTime) + statusLabel.text = CallHistoryCellModel.stringFrom(status: m.status, direction: m.direction, duration:m.duration) + statusImageView.image = CallHistoryCellModel.imageFrom(status: m.status, media: m.media, direction: m.direction) + + if m.kind == .DIRECT { + if let ctc = ContactDAO.findContactBy(phoneId: m.address) { + nameLabel.text = "\(ctc.names ?? "") \(ctc.surnames ?? "")" + self.avatarImageView.setImage(url: ctc.photoURL, placeHolder: UIImage.nynja.Contacts.avaPlaceholder.image) + } else { + self.avatarImageView.image = UIImage.nynja.Contacts.avaPlaceholder.image + } + } else { + if let r = RoomDAO.findRoom(by: m.address) { + self.avatarImageView.setImage(url: r.photoURL, placeHolder: UIImage.nynja.Contacts.avaPlaceholder.image) + } else { + self.avatarImageView.image = UIImage.nynja.Contacts.avaPlaceholder.image + } + } + } + + infoButton.isHidden = false + } + + // MARK: - Actions + + @objc func onInfoTap(sender: UIButton) { + delegate?.showInfoWith(callHistoryCell: self) + } + + // MARL: - Long Press Delegates + + override func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool { + return !(touch.view is UIButton) + } + + @objc private func longPressed(_ recognizer: UILongPressGestureRecognizer) { + guard recognizer.state == .began else { + return + } + + delegate?.showMenuWith(callHistoryCell: self) + } +} + +extension CallHistoryTableViewCell { + + struct Constraints { + + struct avatar { + static let leftOffset = 16.0 + static let height = 44.0 + } + + struct labelName { + static let leftOffset = 16.0 + static let topOffset = 11.0 + } + + struct labelTime { + static let rightOffset = 16.0 + static let horInset = 5.0 + } + + struct statusImageView { + static let leftOffset = 16.0 + static let bottomOffset = 11.0 + static let height = 18.0 + } + + struct statusLabel { + static let leftOffset = 8.0 + static let rightOffset = 8.0 + } + + struct infoButton { + static let rightOffset = 16.0 + static let height = 18.0 + } + } +} + diff --git a/Nynja/Modules/CallHistory/View/TableView/EmptyCallHistoryView.swift b/Nynja/Modules/CallHistory/View/TableView/EmptyCallHistoryView.swift new file mode 100644 index 0000000000000000000000000000000000000000..86d642071c2e1d5ee5127f9895aa2a84fd62238e --- /dev/null +++ b/Nynja/Modules/CallHistory/View/TableView/EmptyCallHistoryView.swift @@ -0,0 +1,69 @@ +// +// EmptyCallHistoryView.swift +// Nynja +// +// Created by Bozhko Terziev on 9.11.18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import UIKit + +class EmptyCallHistoryView: UIView { + + private lazy var callHistoryImageView: UIImageView = { + let iv = UIImageView() + iv.image = UIImage.nynja.CallHistory.icCallHistoryEmpty.image + self.addSubview(iv) + iv.snp.makeConstraints { (make) in + make.width.equalTo(Constraints.image.width) + make.height.equalTo(Constraints.image.height) + make.centerY.equalToSuperview().offset(-Constraints.image.height/4) + make.centerX.equalToSuperview() + } + + return iv + }() + + private lazy var label: UILabel = { + let label = UILabel() + label.textAlignment = .center + label.font = UIFont.makeFont(with: FontFamily.NotoSans.medium.name, height: 23.0) + label.textColor = UIColor.nynja.lightGray + label.backgroundColor = UIColor.nynja.clear + label.text = String.localizable.callHistoryEmptyTitle + + self.addSubview(label) + label.snp.makeConstraints { (make) in + make.top.equalTo(callHistoryImageView.snp.bottom).offset(Constraints.label.topOffset) + make.centerX.equalToSuperview() + make.left.greaterThanOrEqualToSuperview().offset(Constraints.label.sideOffset) + make.right.lessThanOrEqualToSuperview().offset(-Constraints.label.sideOffset) + } + + return label + }() + + func should(shown:Bool) { + callHistoryImageView.isHidden = !shown + label.isHidden = !shown + self.isHidden = !shown + } +} + +extension EmptyCallHistoryView { + + struct Constraints { + + struct image { + static let width = 64.0 + static let height = 64.0 + } + + struct label { + static let sideOffset = 16.0 + static let topOffset = 20.0 + + } + } +} + diff --git a/Nynja/Modules/CallHistory/WireFrame/CallHistoryWireFrame.swift b/Nynja/Modules/CallHistory/WireFrame/CallHistoryWireFrame.swift new file mode 100644 index 0000000000000000000000000000000000000000..2d7b5664705e5fb12b34ea3eb4c6b0fbc26e3128 --- /dev/null +++ b/Nynja/Modules/CallHistory/WireFrame/CallHistoryWireFrame.swift @@ -0,0 +1,64 @@ +// +// CallHistoryWireFrame.swift +// Nynja +// +// Created by Bozhko Terziev on 18.09.18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import UIKit + +class CallHistoryWireFrame: CallHistoryWireFrameProtocol { + + weak var navigation : UINavigationController? + weak var mainWF: MainWireFrame? + weak var view: CallHistoryViewProtocol! + + func presentCallHistory(navigation: UINavigationController, main: MainWireFrame?) { + + let view = CallHistoryViewController() + let presenter = CallHistoryPresenter() + let interactor = CallHistoryInteractor() + + self.navigation = navigation + self.view = view + self.mainWF = main + + // Connecting + view.presenter = presenter + presenter.view = view + presenter.wireFrame = self + presenter.interactor = interactor + interactor.presenter = presenter + + LogService.log(topic: .callSystem) { return "present call history screen" } + navigation.pushViewController(view as UIViewController, animated: true) + } + + func closeController() { + self.navigation?.popViewController(animated: true) + } + + func showContact(contact:Contact) { + mainWF?.showAddContact(contact: contact) + } + + func show(callLog: NYNCallLog){ + mainWF?.showAddParticipantsToViewFromHistory(callLog: callLog) + } + + func callBackFor(contact:Contact) { + self.navigation?.popViewController(animated: false) + mainWF?.callBack(contact:contact) + } + + func callBackVideoFor(contact:Contact) { + self.navigation?.popViewController(animated: false) + mainWF?.callBackVideo(contact:contact) + } + + func callBackFor(room:Room) { + self.navigation?.popViewController(animated: false) + mainWF?.callBack(room:room) + } +} diff --git a/Nynja/Modules/Main/Interactor/MainInteractor.swift b/Nynja/Modules/Main/Interactor/MainInteractor.swift index 244af25b0b4a4c74f89e9e7161a96ca7a9d55b70..29f47537640b16804587b8bf7a2880a091f50409 100644 --- a/Nynja/Modules/Main/Interactor/MainInteractor.swift +++ b/Nynja/Modules/Main/Interactor/MainInteractor.swift @@ -48,7 +48,6 @@ final class MainInteractor: BaseInteractor, MainInteractorInputProtocol, EditPho // MARK: - Calls func call(phoneId: String) { - if let ctc = ContactDAO.findContactBy(phoneId: phoneId), let name = ctc.fullName { communicatorService.call(userId: phoneId, name: name, withVideo: false) } diff --git a/Nynja/Modules/Main/MainProtocols.swift b/Nynja/Modules/Main/MainProtocols.swift index 48a9e0382f33ac5202f7c18610e72afd17441cab..4f2bd01a374be30bd04d57b2c417bc1a5264137b 100644 --- a/Nynja/Modules/Main/MainProtocols.swift +++ b/Nynja/Modules/Main/MainProtocols.swift @@ -46,6 +46,8 @@ protocol MainWireFrameProtocol: class { func showAddParticipantsToCreateCallWith(room:Room) func showAddParticipantsToCreateConferenceCall() + func showCallHistory() + func clearCallHistory() func showPayment(profile: Profile, to contact: Contact) func showScheduleMessage(with mode: ScheduledMessageMode) @@ -190,6 +192,8 @@ protocol MainPresenterProtocol: BasePresenterProtocol { func conferenceVoiceCall() func conferenceVideoCall() + func callHistory() + func clearCallHistory() // Group func showAddParticipants() @@ -205,6 +209,8 @@ protocol MainPresenterProtocol: BasePresenterProtocol { func voiceCall() func callBack(contact:Contact?) + func callBackVideo(contact:Contact?) + func callBack(room:Room?) func voiceGroupCall() func videoCall() func videoGroupCall() diff --git a/Nynja/Modules/Main/Presenter/MainPresenter.swift b/Nynja/Modules/Main/Presenter/MainPresenter.swift index f934d3da675704f4e95e6e98bccce8d898d09fbd..69bc5fd8ccb3d4999eac5d6c008cde5629586e1f 100644 --- a/Nynja/Modules/Main/Presenter/MainPresenter.swift +++ b/Nynja/Modules/Main/Presenter/MainPresenter.swift @@ -71,8 +71,24 @@ final class MainPresenter: BasePresenter, MainPresenterProtocol, MainInteractorO interactor.call(phoneId: contactId) } - func voiceGroupCall() { + func callBackVideo(contact: Contact?) { + guard let contactId = contact?.id else { + return + } + wireFrame.isVideo = true + wireFrame.isGroup = false + interactor.videoCall(phoneId: contactId) + } + + func callBack(room: Room?) { + if let r = room { + wireFrame.isVideo = false + wireFrame.isGroup = true + interactor.createGroupCall(callId: nil, contacts: (r.members?.contacts)!, room: r) + } + } + func voiceGroupCall() { if let room = wireFrame.getRoom() { wireFrame.isVideo = false wireFrame.isGroup = true @@ -198,6 +214,14 @@ final class MainPresenter: BasePresenter, MainPresenterProtocol, MainInteractorO func conferenceVideoCall() { } + + func callHistory() { + wireFrame.showCallHistory() + } + + func clearCallHistory() { + wireFrame.clearCallHistory() + } func showThemePicker() { self.wireFrame.showThemePicker() diff --git a/Nynja/Modules/Main/View/ActionsItem.swift b/Nynja/Modules/Main/View/ActionsItem.swift index c3a19ddcf229a15e83480757cf2706b3682e7110..4d0068f2617fcb29f1cc3a3667d04416c0f859cb 100644 --- a/Nynja/Modules/Main/View/ActionsItem.swift +++ b/Nynja/Modules/Main/View/ActionsItem.swift @@ -10,8 +10,7 @@ enum ActionsItem: Int { case location case camera case gallery - case voiceCall - case videoCall + case calls case contact case event } diff --git a/Nynja/Modules/Main/View/MainNavigationItem.swift b/Nynja/Modules/Main/View/MainNavigationItem.swift index 7fd4c306413810e6247d79d89c61eab97321b37b..577fbf14c09a8356007511237906bcfa0d01c15e 100644 --- a/Nynja/Modules/Main/View/MainNavigationItem.swift +++ b/Nynja/Modules/Main/View/MainNavigationItem.swift @@ -35,6 +35,7 @@ enum MainNavigationItem: String { case voiceCall = "wheel_item_voiceCall" case voiceGroupCall = "wheel_item_voiceGroupCall" case videoCall = "wheel_item_videoCall" + case callHistory = "wheel_item_callHistory" case videoGroupCall = "wheel_item_videoGroupCall" case contact = "wheel_item_contact" case event = "wheel_item_event" @@ -46,6 +47,7 @@ enum MainNavigationItem: String { case phoneNumber = "wheel_item_changeNumber" case payment = "wheel_item_transfer" case help = "wheel_item_help" + case clearHistory = "wheel_item_clear_history" // Chats section case starred = "wheel_item_starred" @@ -120,6 +122,8 @@ enum MainNavigationItem: String { anyClass = ContactsItemsFactory.self case .options: anyClass = OptionsItemsFactory.self + case .calls: + anyClass = CallsItemsFactory.self default: anyClass = nil } diff --git a/Nynja/Modules/Main/View/MainViewController+NavigateProtocol.swift b/Nynja/Modules/Main/View/MainViewController+NavigateProtocol.swift index 23e4a8d39e0b308a790ee78844f7d69ebc0a6c14..c7ca23db5508007750f412d8174107ce5cab99b1 100644 --- a/Nynja/Modules/Main/View/MainViewController+NavigateProtocol.swift +++ b/Nynja/Modules/Main/View/MainViewController+NavigateProtocol.swift @@ -41,7 +41,7 @@ extension MainViewController: NavigateProtocol { } func showCalls(indexPath: IndexPath?) { - // TODO: will be implemented in future with 'Calls' module. + openNextLevel(indexPath: indexPath) } func showMarketplace(indexPath: IndexPath?) { @@ -214,6 +214,16 @@ extension MainViewController: NavigateProtocol { closeWheel(indexPath: indexPath) } + func callHistory(indexPath: IndexPath?) { + presenter.callHistory() + closeWheel(indexPath: indexPath) + } + + func clearCallHistory(indexPath: IndexPath?) { + presenter.clearCallHistory() + closeWheel(indexPath: indexPath) + } + // MARK: - Contact Actions func showListContacts(indexPath: IndexPath?) { presenter.showMyContacts() diff --git a/Nynja/Modules/Main/View/NavigateProtocol.swift b/Nynja/Modules/Main/View/NavigateProtocol.swift index 8b2368203c20631312073fb168baf9866d840231..eb2ef2241129a64317faf2e9f877da5448957a28 100644 --- a/Nynja/Modules/Main/View/NavigateProtocol.swift +++ b/Nynja/Modules/Main/View/NavigateProtocol.swift @@ -70,7 +70,8 @@ protocol SecondLevelNavigateProtocol: class { func conferenceVoiceCall(indexPath: IndexPath?) func conferenceVideoCall(indexPath: IndexPath?) - + func callHistory(indexPath: IndexPath?) + func clearCallHistory(indexPath: IndexPath?) // MARK: - Contact Actions func showListContacts(indexPath: IndexPath?) diff --git a/Nynja/Modules/Main/WireFrame/MainWireframe.swift b/Nynja/Modules/Main/WireFrame/MainWireframe.swift index 1091b979ea6413eb3a72014ab5217ffb7d83bc58..649484fb8fd02dcd55d7a6b327e45b001c929f50 100644 --- a/Nynja/Modules/Main/WireFrame/MainWireframe.swift +++ b/Nynja/Modules/Main/WireFrame/MainWireframe.swift @@ -481,33 +481,40 @@ final class MainWireFrame: MainWireFrameProtocol, NynjaCommunicatorServiceDelega // MARK: Add Participants to group call - func showAddParticipantsToCreateCallWith(room: Room) { + func showAddParticipantsToViewFromHistory(callLog: NYNCallLog) { + ParticipantsHistoryWireFrame().present(navigation: contentNavigation!, main: self, callLog: callLog) + } + func showAddParticipantsToCreateCallWith(room: Room) { AddParticipantsWireFrame().presentAddParticipants(navigation: contentNavigation!, main: self, selectedContacts: [], delegate: self.external, mode: .createGroupCall, members: room.allMembers, room: room) } func showAddParticipantsToCreateConferenceCall() { - AddParticipantsWireFrame().presentAddParticipants(navigation: contentNavigation!, main: self, selectedContacts: [], delegate: self.external, mode: .createConferenceCall, members: [], room: nil) } + + func showCallHistory() { + CallHistoryWireFrame().presentCallHistory(navigation: contentNavigation!, main: self) + } + + func clearCallHistory() { + if let callHistory = contentNavigation.viewControllers.last as? CallHistoryViewController { + callHistory.clearHistory() + } + } func presentLeaveVoiceMessageViewForContact(contact:Contact) { LeaveVoiceMessageWireFrame().presentLeaveVoiceMessage(navigation: navigation!, contact: contact, main: self) } func presentCallInProgressViewForCall(call:NYNCall, isIncomingRingig:Bool) { - self.isVideo = call.recvVideo let callMode: CallInProgressMode = self.isVideo ? .oneToOneVideo : call.isConference() ? .groupAudio : .oneToOneAudio if callMode == .groupAudio { - CallInProgressWireframe().presentCreateGroupCall(navigation: navigation!, callInProgressMode: callMode, main: self, call: call, isIncomingRingig:isIncomingRingig) - } else { - var contact:Contact? = nil - if call.isOutgoing() { if let messageVC = contentNavigation.viewControllers.last as? MessageVC { @@ -536,6 +543,18 @@ final class MainWireFrame: MainWireFrameProtocol, NynjaCommunicatorServiceDelega view?.presenter.callBack(contact: ctc) } } + + func callBackVideo(contact:Contact?) { + if let ctc = contact { + view?.presenter.callBackVideo(contact: ctc) + } + } + + func callBack(room:Room?) { + if let r = room { + view?.presenter.callBack(room: r) + } + } func incomingCallRinging(call: NYNCall) { diff --git a/Nynja/Modules/Message/Interactor/MessageInteractor+Fetch.swift b/Nynja/Modules/Message/Interactor/MessageInteractor+Fetch.swift index 54f1d5aedf8d295b60899a4c129610f30dc4db7f..269dcb1922283f134793820c966124a743b234a0 100644 --- a/Nynja/Modules/Message/Interactor/MessageInteractor+Fetch.swift +++ b/Nynja/Modules/Message/Interactor/MessageInteractor+Fetch.swift @@ -94,21 +94,14 @@ extension MessageInteractor { } private func fetchMessagesFromStorage(feed: Feed) -> [Message] { + guard let rosterId = storageService.rosterId else { + return MessageDAO.fetchMessages(feed: feed) + } + return MessageDAO .fetchMessages(feed: feed) .filter { message in - guard let desc = message.files?.first, - desc.mime == SendMessageType.audioCall.rawValue else { - return true - } - - guard desc.data?.first?.key == FeatureKeys.File.Call.users.rawValue, - let ids = desc.data?.first?.value?.splitByComma(), - let phoneId = storageService.phoneId else { - return false - } - - return ids.contains { $0 == phoneId } + return message.isSeenBy(rosterId: rosterId) } } diff --git a/Nynja/Modules/Message/Interactor/MessageInteractor+MessageHandlerSubscriber.swift b/Nynja/Modules/Message/Interactor/MessageInteractor+MessageHandlerSubscriber.swift index 6809294bb64421e1a294749fd930a8ef31abea1b..20bd65b2f1c48abad6eea08af6726a718e77e4a1 100644 --- a/Nynja/Modules/Message/Interactor/MessageInteractor+MessageHandlerSubscriber.swift +++ b/Nynja/Modules/Message/Interactor/MessageInteractor+MessageHandlerSubscriber.swift @@ -21,7 +21,7 @@ extension MessageInteractor { !localMessage.isDelivered else { return } - if let mime = message.files?.first?.mime, mime == SendMessageType.audioCall.rawValue { + if let mime = message.files?.first?.mime, mime == SendMessageType.audioCall.rawValue || mime == SendMessageType.videoCall.rawValue { return } systemSoundManager.playOutcomingMessageSound() diff --git a/Nynja/Modules/Message/Interactor/MessageInteractor+Reply.swift b/Nynja/Modules/Message/Interactor/MessageInteractor+Reply.swift index 76a3cfb3fa928c2de0122538fdfd6841174e48de..09e77f20012439532704e63e19f0a5eab46b3ea5 100644 --- a/Nynja/Modules/Message/Interactor/MessageInteractor+Reply.swift +++ b/Nynja/Modules/Message/Interactor/MessageInteractor+Reply.swift @@ -12,7 +12,7 @@ extension MessageInteractor { let sender = self.sender(for: repliedMessage) let author = sender.nick ?? sender.fullname let mentions = payloadParser.parse(repliedMessage) - let isDeleted = repliedMessage.isDeleted && MessageDAO.isMessageDeletedForAll(repliedMessage) + let isDeleted = repliedMessage.isDeleted && repliedMessage.isDeletedForAll return RepliedMessageModel(message: repliedMessage, author: author, diff --git a/Nynja/Modules/Message/Interactor/MessageInteractor+StorageSubscriber.swift b/Nynja/Modules/Message/Interactor/MessageInteractor+StorageSubscriber.swift index 2d0a2fee00d02cb942c2854b93a6480ca0d6c65c..35f7ca7a031c43eab8dadf1c1263d64dd0e59cc1 100644 --- a/Nynja/Modules/Message/Interactor/MessageInteractor+StorageSubscriber.swift +++ b/Nynja/Modules/Message/Interactor/MessageInteractor+StorageSubscriber.swift @@ -149,8 +149,7 @@ extension MessageInteractor { // MARK: - Handle message func handleMessageDelete(_ message: Message) { - let deletedForAll = MessageDAO.isMessageDeletedForAll(message) - presenter?.removeMessage(message, isForAllUsers: deletedForAll) + presenter?.removeMessage(message, isForAllUsers: message.isDeletedForAll) } private func handleMessageInsertOrUpdate(_ message: Message) { diff --git a/Nynja/Modules/Message/Interactor/MessageInteractor.swift b/Nynja/Modules/Message/Interactor/MessageInteractor.swift index 140aa53c72ff10f2c45130679c0c3184a464c016..70f1990a42bf1a1f690d334c11ff50de8861214a 100644 --- a/Nynja/Modules/Message/Interactor/MessageInteractor.swift +++ b/Nynja/Modules/Message/Interactor/MessageInteractor.swift @@ -811,45 +811,18 @@ final class MessageInteractor: BaseInteractor, MessageInteractorInputProtocol, H func deleteMessage(localId: MessageLocalId, forBoth: Bool) { guard let message = messageBy(localId: localId), let messageId = message.id, - let phoneId = myContact?.phoneId else { + let rosterId = myContact?.rosterId else { return } - let messageAction: DBMessageAction - var seenBy: AnyObject = -1 as AnyObject - switch chat { - case is Room: - if !forBoth, let memberId = room?.allMembers?.first(where: { $0.phone_id == phoneId })?.id { - seenBy = memberId as AnyObject - } - messageAction = DBMessageAction(messageId: messageId, - seenBy: (seenBy as? Int64) ?? -1, - phoneId: "", - action: .delete) - default: - if !forBoth { - seenBy = phoneId as AnyObject - } - messageAction = DBMessageAction(messageId: messageId, - seenBy: forBoth ? -1 : 0, - phoneId: phoneId, - action: .delete) - } - - try? storageService.perform(action: .save, with: messageAction) + let seenBy: Int64 = forBoth ? 0 : -rosterId + saveDeleteAction(messageId: messageId, seenBy: seenBy) - let messageForDelete = messageFactory.makeMessageForDelete(message: message, seenBy: [seenBy]) - mqttService.sendMessage(message: messageForDelete) - - let messageForSave = Message(message: messageForDelete) - messageForSave.messageStatus = message.messageStatus - messageForSave.localStatus = .deleted + performDeleting(message: message, seenBy: seenBy) message.localStatus = .deleted - message.seenby = messageForSave.seenby - - try? storageService.perform(action: .save, with: messageForSave) + message.seenby = [seenBy] if let feed = self.feed, let newLastMessage = MessageDAO.fetchLastMessage(feed: feed) { ChatService.updateLastMessage(newLastMessage, shouldChangeUnread: false) @@ -857,7 +830,28 @@ final class MessageInteractor: BaseInteractor, MessageInteractorInputProtocol, H handleMessageDelete(message) } + + private func saveDeleteAction(messageId: MessageServerId, seenBy: Int64) { + let messageAction = DBMessageAction(messageId: messageId, + seenBy: seenBy, + action: .delete) + try? storageService.perform(action: .save, with: messageAction) + } + private func performDeleting(message: Message, seenBy: Int64) { + let messageForDelete = messageFactory.makeMessageForDelete(message: message, seenBy: [seenBy]) + mqttService.sendMessage(message: messageForDelete) + + saveDeleted(message: messageForDelete) + } + + private func saveDeleted(message: Message) { + let messageForSave = Message(message: message) + messageForSave.messageStatus = message.messageStatus + messageForSave.localStatus = .deleted + try? storageService.perform(action: .save, with: messageForSave) + } + // MARK: - Reply func prepareToReply(localId: MessageLocalId) { @@ -1068,7 +1062,7 @@ extension MessageInteractor { return } - let types: [SendMessageType] = [.location, .image, .video, .contact, .place, .audioCall] + let types: [SendMessageType] = [.location, .image, .video, .contact, .place, .audioCall, .videoCall] if let mime = message.mainFile?.mime, let type = SendMessageType(rawValue: mime), types.contains(type) { readUnreadMessages() diff --git a/Nynja/Modules/Message/Presenter/MessagePresenter+MentionUnreadCounter.swift b/Nynja/Modules/Message/Presenter/MessagePresenter+MentionUnreadCounter.swift index 225f75791e19fedbcf8e712b3bda5d701be50125..6506a3ce03286a741eca55356b39039e3f38ca8e 100644 --- a/Nynja/Modules/Message/Presenter/MessagePresenter+MentionUnreadCounter.swift +++ b/Nynja/Modules/Message/Presenter/MessagePresenter+MentionUnreadCounter.swift @@ -144,10 +144,7 @@ extension MessagePresenter { } var unique: Set = [] for mention in mentions { - guard let id = MessageServerId(mention) else { - continue - } - unique.insert(id) + unique.insert(mention) } for mention in unreadMentionIds { unique.insert(mention) diff --git a/Nynja/Modules/Message/Presenter/MessagePresenter.swift b/Nynja/Modules/Message/Presenter/MessagePresenter.swift index ab06e074e499e744319557df21316cb83290fb92..2a8a104ff75421c0a9a764ffcde574420b576a3c 100644 --- a/Nynja/Modules/Message/Presenter/MessagePresenter.swift +++ b/Nynja/Modules/Message/Presenter/MessagePresenter.swift @@ -187,6 +187,10 @@ class MessagePresenter: BasePresenter, MessagePresenterProtocol, MessageInteract wireFrame.openVideo(videoURL: videoURL) } + func callBackFor(contact:Contact, isAudio:Bool) { + wireFrame.callBackFor(contact: contact, isAudio: isAudio) + } + func openSchedule(content: InputContent, mentions: [Mention]) { let scheduleMessage: InputScheduleMessage switch content { @@ -822,6 +826,45 @@ class MessagePresenter: BasePresenter, MessagePresenterProtocol, MessageInteract break } model.transferNotes = files.data?.first?.value + case .audioCall?, .videoCall?: + guard let data = attach.data else {break} + for feature in data { + if let k = feature.key { + switch k { + case FeatureKeys.File.Call.duration.rawValue: + if let duration = feature.value { + model.callDuration = UInt(duration) + } else { + model.callDuration = 0 + } + case FeatureKeys.File.Call.starttime.rawValue: + if let starttime = feature.value { + model.callStartTime = Date(timeIntervalSince1970: TimeInterval(starttime) ?? 0) + } else { + model.callStartTime = Date(timeIntervalSince1970: 0) + } + case FeatureKeys.File.Call.conferenceid.rawValue: + guard let callId = feature.value else {break} + model.callId = callId + case FeatureKeys.File.Call.endedby.rawValue: + guard let endedby = feature.value else {break} + model.callEndedBy = endedby + case FeatureKeys.File.Call.count.rawValue: + if let count = feature.value { + model.callMembersCount = UInt(count) + } else { + model.callMembersCount = 0 + } + default: + break + } + } + } + + if let pl = attach.payload, let dur = model.callDuration, let endby = model.callEndedBy { + model.callStatus = callStatus(from: pl, duration: dur, endedby: endby, isgroup:model.isGroup, isowner:model.isOwner) + } + default: break } @@ -864,6 +907,43 @@ class MessagePresenter: BasePresenter, MessagePresenterProtocol, MessageInteract return nil } + private func callStatus(from payload:String, duration:UInt, endedby:String, isgroup:Bool, isowner:Bool) -> NYNCallLogStatus { + var status = NYNCallLogStatus.UNANSWERED + + if (!isgroup) { + if (duration == 0) { + switch endedby { + case "system": + status = isowner ? NYNCallLogStatus.UNANSWERED : NYNCallLogStatus.MISSED + break + case "callee": + status = NYNCallLogStatus.DECLINED + break + case "caller", "": + status = isowner ? NYNCallLogStatus.CANCELED : NYNCallLogStatus.MISSED + break + default: + break + } + } else { + status = NYNCallLogStatus.ANSWERED + } + } else { + if isowner, 0 == duration { + status = NYNCallLogStatus.CANCELED + } else { + if (isowner || duration > 0) { + status = NYNCallLogStatus.ANSWERED + } else { + status = NYNCallLogStatus.MISSED + } + } + } + + + return status + } + private func deliveryStatusFrom(readerID: MessageServerId?, messageID: MessageServerId?) -> DeliveryStatus { switch (readerID, messageID) { case (.none, .some): return .sent diff --git a/Nynja/Modules/Message/Protocols/MessageProtocols.swift b/Nynja/Modules/Message/Protocols/MessageProtocols.swift index 58b3050bd09a9c51dcb3e7201b5414978373161c..b2e67adc90321e1cd1d33159c079bec976bf3453 100644 --- a/Nynja/Modules/Message/Protocols/MessageProtocols.swift +++ b/Nynja/Modules/Message/Protocols/MessageProtocols.swift @@ -32,6 +32,7 @@ protocol MessageWireframeProtocol: DocumentInteractionInput { func openLocation(with type: LocationType) func presentImageModally(imageURL: URL, on view: UIViewController, with transitionInfo: ImagePreviewTransitionInfo) func openVideo(videoURL: URL) + func callBackFor(contact:Contact, isAudio:Bool) func openSchedule(with inputMessage: InputScheduleMessage) func deleteAndLeave() @@ -71,6 +72,7 @@ protocol MessagePresenterProtocol: BasePresenterProtocol, func openImage(imageURL: URL, with transitionInfo: ImagePreviewTransitionInfo) func openFile(at url: URL) func openVideo(videoURL: URL) + func callBackFor(contact:Contact, isAudio:Bool) func openSchedule(content: InputContent, mentions: [Mention]) func sendTyping(_ type: TypingModelType) func sendAudio(withUrl url: URL) diff --git a/Nynja/Modules/Message/View/MessageVC+CellDelegate.swift b/Nynja/Modules/Message/View/MessageVC+CellDelegate.swift index 816236314620e284e73b1123843def6774e419f8..5b2d5c1b5ca2cbde25daf6b7353c1339f74b618d 100644 --- a/Nynja/Modules/Message/View/MessageVC+CellDelegate.swift +++ b/Nynja/Modules/Message/View/MessageVC+CellDelegate.swift @@ -48,6 +48,14 @@ extension MessageVC: BaseChatCellDelegate { if let fileUrl = model.fileUrl { openVideo(videoURL: fileUrl) } + case .audioCall: + if !model.isGroup, let contact = presenter.interactor.contact { + callBackFor(contact: contact, isAudio: true) + } + case .videoCall: + if !model.isGroup, let contact = presenter.interactor.contact { + callBackFor(contact: contact, isAudio: false) + } default: break } diff --git a/Nynja/Modules/Message/View/MessageVC.swift b/Nynja/Modules/Message/View/MessageVC.swift index f9c89e06c5260bff27702d973a2209d85a0620b2..0de76fab6978e3557384a382d55886e8e34dbb03 100644 --- a/Nynja/Modules/Message/View/MessageVC.swift +++ b/Nynja/Modules/Message/View/MessageVC.swift @@ -854,6 +854,10 @@ final class MessageVC: BaseVC, MessageViewProtocol, ReplyPreviewDelegate, BackSw func openVideo(videoURL: URL) { presenter.openVideo(videoURL: videoURL) } + + func callBackFor(contact:Contact, isAudio:Bool) { + presenter.callBackFor(contact: contact, isAudio: isAudio) + } // MARK: - MessageViewProtocol diff --git a/Nynja/Modules/Message/View/Views/CollectionView/Cells/ChatCells/BaseChatCell/BaseChatCell.swift b/Nynja/Modules/Message/View/Views/CollectionView/Cells/ChatCells/BaseChatCell/BaseChatCell.swift index 9b7f9ca1fa0be76ba249b07fd856a2e837b6c59a..31d0e8a9a93878155324ded5df9d3d812d65abab 100644 --- a/Nynja/Modules/Message/View/Views/CollectionView/Cells/ChatCells/BaseChatCell/BaseChatCell.swift +++ b/Nynja/Modules/Message/View/Views/CollectionView/Cells/ChatCells/BaseChatCell/BaseChatCell.swift @@ -216,7 +216,12 @@ class BaseChatCell: UICollectionViewCell, MessageBaseImageViewDataSource, Messag return { (make) in make.right.equalToSuperview().offset(-Constraints.bubble.horizontalInset) make.top.equalTo(verticalInset) - make.width.lessThanOrEqualTo(BaseChatCell.Constraints.bubble.maxWidth).priority(999) + + if let m = self.model, m.type == .audioCall || m.type == .videoCall { + make.width.equalTo(BaseChatCell.Constraints.bubble.callBubbleWidth) + } else { + make.width.lessThanOrEqualTo(BaseChatCell.Constraints.bubble.maxWidth).priority(999) + } } } diff --git a/Nynja/Modules/Message/View/Views/CollectionView/Cells/ChatCells/BaseChatCell/BaseChatCellLayout.swift b/Nynja/Modules/Message/View/Views/CollectionView/Cells/ChatCells/BaseChatCell/BaseChatCellLayout.swift index b29b0c683609f071b0a14fd030376613b898ddf7..d23ec0bf3b9f0629f3da9bcdeebdc945d797173c 100644 --- a/Nynja/Modules/Message/View/Views/CollectionView/Cells/ChatCells/BaseChatCell/BaseChatCellLayout.swift +++ b/Nynja/Modules/Message/View/Views/CollectionView/Cells/ChatCells/BaseChatCell/BaseChatCellLayout.swift @@ -12,6 +12,7 @@ extension BaseChatCell { enum bubble { static let maxWidth = CGFloat(290.0.adjustedByWidth) + static let callBubbleWidth = CGFloat(240.0.adjustedByWidth) static let verticalInset = CGFloat(4.0.adjustedByWidth) static let horizontalInset = CGFloat((fromImageView.leftOffset + fromImageView.height / 2).adjustedByWidth) diff --git a/Nynja/Modules/Message/View/Views/CollectionView/Cells/ChatCells/BaseChatCell/OponentChatCell.swift b/Nynja/Modules/Message/View/Views/CollectionView/Cells/ChatCells/BaseChatCell/OponentChatCell.swift index 255242771010ab4b73ef77bf0dc2060edec8bfb5..3a9f8cea5414e057d6c4370b79c0b3ad5db9646a 100644 --- a/Nynja/Modules/Message/View/Views/CollectionView/Cells/ChatCells/BaseChatCell/OponentChatCell.swift +++ b/Nynja/Modules/Message/View/Views/CollectionView/Cells/ChatCells/BaseChatCell/OponentChatCell.swift @@ -48,7 +48,12 @@ class OponentChatCell: BaseChatCell { self.bubbleLeftToSuperviewConstraint = make.left.equalToSuperview().offset(horizontalInset).constraint self.bubbleLeftToAvatarConstraint = make.left.equalToSuperview().offset(avatarInset).constraint make.top.equalTo(verticalInset) - make.width.lessThanOrEqualTo(BaseChatCell.Constraints.bubble.maxWidth).priority(999) + + if let m = self.model, m.type == .audioCall || m.type == .videoCall { + make.width.equalTo(BaseChatCell.Constraints.bubble.callBubbleWidth) + } else { + make.width.lessThanOrEqualTo(BaseChatCell.Constraints.bubble.maxWidth).priority(999) + } } } diff --git a/Nynja/Modules/Message/View/Views/CollectionView/Cells/ChatCells/MessageCellFactory.swift b/Nynja/Modules/Message/View/Views/CollectionView/Cells/ChatCells/MessageCellFactory.swift index c0392667eb0f880c93e901377bea5e8fcd813c32..ed6c8f23989ef4d349013899dd08016b24984bbb 100644 --- a/Nynja/Modules/Message/View/Views/CollectionView/Cells/ChatCells/MessageCellFactory.swift +++ b/Nynja/Modules/Message/View/Views/CollectionView/Cells/ChatCells/MessageCellFactory.swift @@ -96,7 +96,7 @@ class MessageCellFactory { // MARK: - Helpers private static let types: [SendMessageType] = [ - .contact, .location, .place, .text, .image, .sticker, .file, .video, .audio, .transfer, .audioCall + .contact, .location, .place, .text, .image, .sticker, .file, .video, .audio, .transfer, .audioCall, .videoCall ] private static func ids(for ownerType: OwnerType) -> [String] { diff --git a/Nynja/Modules/Message/View/Views/CollectionView/Cells/Models/BaseChatCellModel/BaseChatCellModel.swift b/Nynja/Modules/Message/View/Views/CollectionView/Cells/Models/BaseChatCellModel/BaseChatCellModel.swift index 30d904cb1a4fc56616c8fac63a379271c7c5048b..b2cf2d1bfd602095a861f882d2af91c5de9492fb 100644 --- a/Nynja/Modules/Message/View/Views/CollectionView/Cells/Models/BaseChatCellModel/BaseChatCellModel.swift +++ b/Nynja/Modules/Message/View/Views/CollectionView/Cells/Models/BaseChatCellModel/BaseChatCellModel.swift @@ -79,6 +79,14 @@ final class BaseChatCellModel: ChatCellModel, AudioPlayable, ProgressObservable var links: [String]? var mentions: [MentionInfo]? + // MARK: - Bubbles + var callStatus: NYNCallLogStatus? + var callEndedBy: String? // TO DO convert to enum + var callDuration: UInt? + var callMembersCount: UInt? + var callStartTime: Date? + var callId: String? + var hasMentions: Bool { guard let mentions = mentions else { return false diff --git a/Nynja/Modules/Message/View/Views/CollectionView/Cells/Views/Base/MessageViewFactory.swift b/Nynja/Modules/Message/View/Views/CollectionView/Cells/Views/Base/MessageViewFactory.swift index f58aff1b88a720b686011845283d4648d214e41c..21049b320e78f4b3f96270643093e120cc1879ba 100644 --- a/Nynja/Modules/Message/View/Views/CollectionView/Cells/Views/Base/MessageViewFactory.swift +++ b/Nynja/Modules/Message/View/Views/CollectionView/Cells/Views/Base/MessageViewFactory.swift @@ -144,7 +144,7 @@ final class MessageViewFactory { content = MessageVoiceView(contentAppearance: dependency, delegate: dependency) case .transfer: content = MessageTransferView(contentAppearance: dependency) - case .audioCall: + case .audioCall, .videoCall: content = MessageCallView(contentAppearance: dependency) default: content = nil @@ -179,7 +179,7 @@ final class MessageViewFactory { return MessageVoiceView.self case .transfer: return MessageTransferView.self - case .audioCall: + case .audioCall, .videoCall: return MessageCallView.self default: return nil diff --git a/Nynja/Modules/Message/View/Views/CollectionView/Cells/Views/Info/InfoView.swift b/Nynja/Modules/Message/View/Views/CollectionView/Cells/Views/Info/InfoView.swift index b15d94a3b81b21109da702ce573259fe4ee7e593..19e44d494154879947ef7bd15345d91b4607778c 100644 --- a/Nynja/Modules/Message/View/Views/CollectionView/Cells/Views/Info/InfoView.swift +++ b/Nynja/Modules/Message/View/Views/CollectionView/Cells/Views/Info/InfoView.swift @@ -147,7 +147,7 @@ class InfoView: BaseView, InfoInjectible { } private func adjustBackground(_ model: BaseChatCellModel) { - let types: [SendMessageType] = [.contact, .text, .audio, .file, .place, .audioCall, .transfer] + let types: [SendMessageType] = [.contact, .text, .audio, .file, .place, .audioCall, .videoCall, .transfer] var replyAppearance: CountAppearanceModel diff --git a/Nynja/Modules/Message/View/Views/CollectionView/Cells/Views/Message/MessageCallView.swift b/Nynja/Modules/Message/View/Views/CollectionView/Cells/Views/Message/MessageCallView.swift index f572f8561b58e6dca7d4029b6837afce9cc057d9..a7aba999a21d4e65cf2865125ed604eb9d58fa11 100644 --- a/Nynja/Modules/Message/View/Views/CollectionView/Cells/Views/Message/MessageCallView.swift +++ b/Nynja/Modules/Message/View/Views/CollectionView/Cells/Views/Message/MessageCallView.swift @@ -9,31 +9,51 @@ import Foundation import SnapKit + final class MessageCallView: MessageContentView { + static let todayDateFormatter: DateFormatter = { + let formatter = DateFormatter() + formatter.dateStyle = .none + formatter.timeStyle = .short + return formatter + }() + + static let lastWeekDateFormatter: DateFormatter = { + let formatter = DateFormatter() + formatter.dateFormat = "EEEE" + formatter.timeZone = TimeZone.current + return formatter + }() + + static let dateFormatter: DateFormatter = { + let formatter = DateFormatter() + formatter.dateStyle = .short + formatter.timeStyle = .none + return formatter + }() + override var coloredViews: [UIView] { return [self, imageView, title] } - // MARK: - Constraints - private enum Constraints { - static let height = CGFloat(44.adjustedByWidth) + static let height = 44.0 + static let offset = 16.0 + + static let minViewHeight = 2.adjustedByWidth + imageView.width enum imageView { - static let verticalInset = CGFloat(8.adjustedByWidth) - static let width = CGFloat(48.adjustedByWidth) + static let verticalInset = 2.adjustedByWidth + static let width = 32.0 static let leftInset = 8.adjustedByWidth } enum nameLabel { - static let width = 188.adjustedByWidth - - static let horizontalPadding = 8.adjustedByWidth - static let topInset = 4.adjustedByWidth - static let labelHeight = CGFloat(22.adjustedByWidth) + static let verticalInset = 4.adjustedByWidth + static let fontSizeLabel = CGFloat(12.adjustedByWidth) } enum infoView { @@ -45,68 +65,303 @@ final class MessageCallView: MessageContentView { return Constraints.infoView.inset } - // MARK: - Views private lazy var imageView: UIImageView = { let imageView = UIImageView() imageView.contentMode = .scaleAspectFit - let width = Constraints.imageView.width - - let verticalInset = Constraints.imageView.verticalInset self.addSubview(imageView) imageView.snp.makeConstraints { make in - make.width.height.equalTo(width) + make.width.height.equalTo(Constraints.imageView.width) make.left.equalTo(Constraints.imageView.leftInset) - make.top.equalTo(verticalInset) - make.bottom.lessThanOrEqualTo(-verticalInset) + make.centerY.equalToSuperview() } return imageView }() private lazy var title: UILabel = { - let size = CGSize(width: 100, height: Constraints.nameLabel.labelHeight) - let label = UILabel(size: size, color: UIColor.nynja.darkLight, fontName: FontFamily.NotoSans.regular.name) + let label = UILabel() + label.textColor = UIColor.nynja.darkLight + label.backgroundColor = .yellow + label.font = MessageCallView.normalFont + label.numberOfLines = 1 + self.addSubview(label) - label.snp.makeConstraints(self.nameLabelConstraints()) + label.snp.makeConstraints { make in + make.right.equalToSuperview().offset(-Constraints.imageView.leftInset) + make.left.equalTo(imageView.snp.right).offset(Constraints.imageView.leftInset) + make.top.equalToSuperview().offset(Constraints.nameLabel.verticalInset) + } + return label }() + private lazy var participants: UILabel = { + let label = UILabel() + label.textColor = UIColor.nynja.darkLight + label.backgroundColor = .clear + label.font = MessageCallView.boldFont + label.numberOfLines = 1 + + self.addSubview(label) + label.snp.makeConstraints { make in + make.right.equalToSuperview().offset(-Constraints.imageView.leftInset) + make.left.equalTo(imageView.snp.right).offset(Constraints.imageView.leftInset) + make.top.equalTo(title.snp.bottom).offset(Constraints.nameLabel.verticalInset) + } + + return label + }() + + private lazy var duration: UILabel = { + let label = UILabel() + label.textColor = UIColor.nynja.lightGray + label.backgroundColor = .clear + label.font = MessageCallView.normalFont + label.numberOfLines = 1 + + self.addSubview(label) + label.snp.makeConstraints { make in + make.right.equalToSuperview().offset(-Constraints.imageView.leftInset) + make.left.equalTo(imageView.snp.right).offset(Constraints.imageView.leftInset) + make.top.equalTo(participants.snp.bottom).offset(Constraints.nameLabel.verticalInset) + } + + return label + }() + + private static var normalFont = UIFont(name: FontFamily.NotoSans.regular.name, size: Constraints.nameLabel.fontSizeLabel) + private static var boldFont = UIFont(name: FontFamily.NotoSans.bold.name, size: Constraints.nameLabel.fontSizeLabel) + + // MARK: - Private Methods + private func updateConstraintsAndControlValues(with model: BaseChatCellModel) { + duration.snp.removeConstraints() + participants.snp.removeConstraints() + imageView.snp.removeConstraints() + + if let cs = model.callStatus { + imageView.image = imageFrom(status: cs, isaudio: (model.type == .audioCall), isowner: model.isOwner) + } + + if let cs = model.callStatus, let cd = model.callDuration { + title.text = stringFrom(status:cs, isowner:model.isOwner, duration:cd) + } + + if model.isGroup { + if let d = model.callDuration, d > 0 { + participants.snp.makeConstraints { make in + make.right.equalToSuperview().offset(-Constraints.imageView.leftInset) + make.left.equalTo(imageView.snp.right).offset(Constraints.imageView.leftInset) + make.top.equalTo(title.snp.bottom).offset(Constraints.nameLabel.verticalInset) + } + + if let pc = model.callMembersCount { + participants.text = String.localizable.participantsCount(Int(pc)) + } + + duration.snp.makeConstraints { make in + make.right.equalToSuperview().offset(-Constraints.imageView.leftInset) + make.left.equalTo(imageView.snp.right).offset(Constraints.imageView.leftInset) + make.top.equalTo(participants.snp.bottom).offset(Constraints.nameLabel.verticalInset) + } + + duration.text = stringFrom(duration: d) + + imageView.snp.makeConstraints { make in + make.width.height.equalTo(Constraints.imageView.width) + make.left.equalTo(Constraints.imageView.leftInset) + make.centerY.equalTo(participants.snp.centerY) + } + } else { + participants.snp.makeConstraints { make in + make.right.equalToSuperview().offset(-Constraints.imageView.leftInset) + make.left.equalTo(imageView.snp.right).offset(Constraints.imageView.leftInset) + make.top.equalTo(title.snp.bottom).offset(Constraints.nameLabel.verticalInset) + } + + if let pc = model.callMembersCount { + participants.text = String.localizable.participantsCount(Int(pc)) + } + + imageView.snp.makeConstraints { make in + make.width.height.equalTo(Constraints.imageView.width) + make.left.equalTo(Constraints.imageView.leftInset) + make.centerY.equalTo(title.snp.bottom).offset(Constraints.nameLabel.verticalInset/2) + } + + duration.text = "" + duration.snp.makeConstraints { make in + make.height.width.equalTo(0) + make.top.equalTo(participants.snp.bottom) + } + } + } else { + participants.text = "" + participants.snp.makeConstraints { make in + make.height.width.equalTo(0) + make.top.equalTo(title.snp.bottom) + } + + if let d = model.callDuration, d > 0 { + duration.snp.makeConstraints { make in + make.right.equalToSuperview().offset(-Constraints.imageView.leftInset) + make.left.equalTo(imageView.snp.right).offset(Constraints.imageView.leftInset) + make.top.equalTo(title.snp.bottom).offset(Constraints.nameLabel.verticalInset) + } + + duration.text = stringFrom(duration: d) + + imageView.snp.makeConstraints { make in + make.width.height.equalTo(Constraints.imageView.width) + make.left.equalTo(Constraints.imageView.leftInset) + make.centerY.equalTo(title.snp.bottom).offset(Constraints.nameLabel.verticalInset/2) + } + + title.snp.removeConstraints() + title.snp.makeConstraints { make in + make.right.equalToSuperview().offset(-Constraints.imageView.leftInset) + make.left.equalTo(imageView.snp.right).offset(Constraints.imageView.leftInset) + make.top.equalToSuperview().offset(Constraints.nameLabel.verticalInset) + } + } else { + duration.text = "" + duration.snp.makeConstraints { make in + make.height.width.equalTo(0) + make.top.equalTo(title.snp.bottom) + } + + imageView.snp.makeConstraints { make in + make.width.height.equalTo(Constraints.imageView.width) + make.left.equalTo(Constraints.imageView.leftInset) + make.top.equalToSuperview().offset(Constraints.nameLabel.verticalInset + Constraints.nameLabel.verticalInset/2) + } + + title.snp.removeConstraints() + title.snp.makeConstraints { make in + make.right.equalToSuperview().offset(-Constraints.imageView.leftInset) + make.left.equalTo(imageView.snp.right).offset(Constraints.imageView.leftInset) + make.centerY.equalTo(imageView.snp.centerY) + } + } + } + } + + private func stringFrom(duration:UInt)-> String? { + let formatter = DateComponentsFormatter() + formatter.unitsStyle = .abbreviated + formatter.allowedUnits = [.hour, .minute, .second] + formatter.zeroFormattingBehavior = [ .default ] + + let formattedDuration = formatter.string(from: Double(duration)) + + return formattedDuration + } + + private func stringFrom(status:NYNCallLogStatus, isowner:Bool, duration:UInt)-> String { + switch status { + case .ANSWERED: + if !isowner { + return String.localizable.callHistoryLogDirInbound + } else { + return String.localizable.callHistoryLogDirOutbound + } + case .CANCELED: + return String.localizable.callHistoryLogStatusCanceled + case .DECLINED: + return String.localizable.callHistoryLogStatusDeclined + case .MISSED: + return String.localizable.callHistoryLogStatusMissed + case .UNANSWERED: + return String.localizable.callHistoryLogStatusNoanswer + } + } + + private func stringFrom(date:Date) ->String { + if Calendar.current.isDateInToday(date) { + return MessageCallView.todayDateFormatter.string(from:date) + } else if Calendar.current.isDateInYesterday(date) { + return String.localizable.callHistoryYesterdayTitle + } else if CallHistoryCellModel.isDateInLastWeek(date:date) { + return MessageCallView.lastWeekDateFormatter.string(from:date) + } else { + return MessageCallView.dateFormatter.string(from:date) + } + } + + private func imageFrom(status:NYNCallLogStatus, isaudio:Bool, isowner:Bool)-> UIImage { + if isaudio { + switch status { + case .ANSWERED: + return !isowner ? UIImage.nynja.CallBubbles.icIncomingVoiceBubble.image : UIImage.nynja.CallBubbles.icOutgoingVoiceBubble.image + case .CANCELED: + return UIImage.nynja.CallBubbles.icCanceledVoiceBubble.image + case .DECLINED: + return UIImage.nynja.CallBubbles.icCanceledVoiceBubble.image + case .MISSED: + return UIImage.nynja.CallBubbles.icMissedVoiceBubble.image + case .UNANSWERED: + return UIImage.nynja.CallBubbles.icNoAnswerVoiceBubble.image + } + } else { + switch status { + case .ANSWERED: + return !isowner ? UIImage.nynja.CallBubbles.icIncomingVideoBubble.image : UIImage.nynja.CallBubbles.icOutgoingVideoBubble.image + case .CANCELED: + return UIImage.nynja.CallBubbles.icCanceledVideoBubble.image + case .DECLINED: + return UIImage.nynja.CallBubbles.icCanceledVideoBubble.image + case .MISSED: + return UIImage.nynja.CallBubbles.icMissedVideoBubble.image + case .UNANSWERED: + return UIImage.nynja.CallBubbles.icNoAnswerVideoBubble.image + } + } + } // MARK: - BubbleInjectible override func configure(with model: BaseChatCellModel) { super.configure(with: model) - guard model.type == .audioCall else { return } - let image = model.isOwner ? UIImage.nynja.icVoiceCallOutgoingDark.image : UIImage.nynja.icIncomingCallDark.image - imageView.image = image - title.text = model.isOwner ? String.localizable.outgoingCall : String.localizable.incomingCall + guard (model.type == .audioCall || model.type == .videoCall) else { return } + + updateConstraintsAndControlValues(with: model) } override class func size(for model: BaseChatCellModel, maxWidth: CGFloat) -> CGSize { - return CGSize(width: maxWidth, height: Constraints.height) + return calculateSize(for:model, maxWidth:maxWidth) } - - // MARK: - Private Methods - - private func nameLabelConstraints(default isDefault: Bool = true) -> (ConstraintMaker) -> Void { - return { [weak self] make in - guard let this = self else { return } - let horizontalPadding = Constraints.nameLabel.horizontalPadding - - make.width.equalTo(Constraints.nameLabel.width) - if isDefault { - make.top.equalTo(this.imageView).inset(Constraints.nameLabel.topInset) + private static func calculateSize(for model: BaseChatCellModel, maxWidth: CGFloat) -> CGSize { + var viewHeight:CGFloat = 0.0 + var normalFontHeight:CGFloat = 0.0 + if let nf = normalFont { + normalFontHeight = ceil(nf.lineHeight) + } + + var boldFontHeight:CGFloat = 0.0 + if let bf = boldFont { + boldFontHeight = ceil(bf.lineHeight) + } + + if model.isGroup { + if let d = model.callDuration, d > 0 { + viewHeight = 2*normalFontHeight + boldFontHeight + CGFloat(4*Constraints.nameLabel.verticalInset) } else { - make.centerY.equalTo(this.imageView) + viewHeight = normalFontHeight + boldFontHeight + CGFloat(3*Constraints.nameLabel.verticalInset) + } + } else { + if let d = model.callDuration, d > 0 { + viewHeight = 2*normalFontHeight + CGFloat(3*Constraints.nameLabel.verticalInset) + } else { + viewHeight = normalFontHeight + CGFloat(2*Constraints.nameLabel.verticalInset) } - make.left.equalTo(this.imageView.snp.right).offset(horizontalPadding) - make.right.equalTo(-horizontalPadding) } + + + return CGSize(width: maxWidth, height: max(viewHeight, CGFloat(Constraints.minViewHeight)) + CGFloat(Constraints.offset)) } } diff --git a/Nynja/Modules/Message/WireFrame/MessageWireframe.swift b/Nynja/Modules/Message/WireFrame/MessageWireframe.swift index 71987cd8cb5f6cfbdcb08beb1386e8ef126d680d..e31bdb9140411889cd1aaf17ce12eab198be1a76 100644 --- a/Nynja/Modules/Message/WireFrame/MessageWireframe.swift +++ b/Nynja/Modules/Message/WireFrame/MessageWireframe.swift @@ -126,6 +126,14 @@ class MessageWireFrame: MessageWireframeProtocol, DocumentInteractionWireFrame { } } + func callBackFor(contact:Contact, isAudio:Bool) { + if isAudio { + main?.callBack(contact: contact) + } else { + main?.callBackVideo(contact: contact) + } + } + func openSchedule(with inputMessage: InputScheduleMessage) { main?.showScheduleMessage(with: inputMessage) } diff --git a/Nynja/Modules/ParticipantsHistory/Interactor/ParticipantsHistoryInteractor.swift b/Nynja/Modules/ParticipantsHistory/Interactor/ParticipantsHistoryInteractor.swift new file mode 100644 index 0000000000000000000000000000000000000000..4d2cfc82caf1bdbd4f51dc0c5121949f9d5250fd --- /dev/null +++ b/Nynja/Modules/ParticipantsHistory/Interactor/ParticipantsHistoryInteractor.swift @@ -0,0 +1,138 @@ +// +// ParticipantsHistoryInteractor.swift +// Project +// +// Created by Bozhko Terziev on 15/11/2018. +// Copyright © 2018 Softavail. All rights reserved. +// + + +class ParticipantsHistoryInteractor: BaseInteractor, ParticipantsHistoryInteractorInputProtocol, IoHandlerDelegate { + + weak var presenter: ParticipantsHistoryInteractorOutputProtocol! + + var includedMembers: [Member] = [] + var ownerId:String? + + private var participants: [Participant] = [] + + override init() { + super.init() + IoHandler.delegate = self + } + + + //MARK: - BaseInteractor + + override func loadData() { + super.loadData() + self.loadContacts() + } + + // MARK: - StorageSubscriber + + override var subscribes: [SubscribeType]? { + return [.room(nil)] + } + + override func update(with changes: [StorageChange], type: SubscribeType) { + if case .room = type { + guard let dbRoom = changes.first?.entity as? DBRoom else { return } + guard let updatedMembers = Room(room: dbRoom).allMembers else { return } + self.includedMembers = updatedMembers + self.loadContacts() + } + } + + + //MARK: - ParticipantsHistoryInteractorInputProtocol + + func filterParticipants(with text: String) { + let filtered = participants.filter { + guard let name = $0.contact.nick ?? $0.contact.fullName else { + return false + } + return name.starts(with: text, by: { String($0).lowercased() == String($1).lowercased() }) + } + presenter.participantsFiltered(filtered) + } + + func findContact(by contact: Contact) { + guard let myPhoneId = StorageService.sharedInstance.phoneId else { return } + if contact.phoneId == myPhoneId { + presenter.showMyProfile() + } else if let contact = ContactDAO.findContactBy(phoneId: contact.phoneId) { + presenter.showProfile(contact) + } else { + guard let phoneNumber = contact.phoneNumber else { return } + MQTTService.sharedInstance.tryFindContact(number: phoneNumber) + } + } + + func getCallHostName()->String? { + var hostName:String? + for m in includedMembers { + if m.phone_id == ownerId { + hostName = m.alias + break + } + } + return hostName + } + + func getCallHostImage()->UIImage? { + var hostImage:UIImage? + if let owner = ownerId { + if let ctc = ContactDAO.findContactBy(phoneId: owner) { + if let url = ctc.avatarUrl { + hostImage = UIImage.init(fileUrl: url) + } + } + } + return hostImage + } + + //MARK: - IoHandlerDelegate + + func getContactSuccess(contact: Contact) { + self.presenter.showProfile(contact) + } + + func contactNotFound() { + self.presenter.showContactNotFound() + } + + + //MARK: - Private + + private func loadContacts() { + let contacts = ContactDAO.fetchPlainContacts(with: [.friend, .ban, .banned]) + mapToParticipants(contacts) + } + + private func mapToParticipants(_ contacts: [Contact]) { + let mergedContacts = mergeMembersWith(contacts) + participants = mergedContacts.map{ makeParticipantFromContact($0.contact, isAdmin: $0.isAdmin, alias: $0.alias) } + presenter.participantsFetched(participants) + } + + private func makeParticipantFromContact(_ contact: Contact, isAdmin: Bool, alias: String?) -> Participant { + let participant = Participant(contact: contact) + participant.isSelectable = false + participant.showAdmin = isAdmin + participant.alias = alias + return participant + } + + private func mergeContactFrom(contacts: [Contact], with member: Member) -> Contact { + return contacts.first { $0.phone_id == member.phone_id } ?? Contact(member: member) + } + + private func mergeMembersWith(_ contacts: [Contact]) -> [(contact: Contact, isAdmin: Bool, alias: String?)] { + return includedMembers.map { + let contact = mergeContactFrom(contacts: contacts, with: $0) + let isAdmin = $0.memberStatus == .admin + return (contact, isAdmin, $0.alias) + } + } +} diff --git a/Nynja/Modules/ParticipantsHistory/ParticipantsHistoryProtocols.swift b/Nynja/Modules/ParticipantsHistory/ParticipantsHistoryProtocols.swift new file mode 100644 index 0000000000000000000000000000000000000000..a58ea9a5efc865fe274afe4f6c9b08d9a761c1df --- /dev/null +++ b/Nynja/Modules/ParticipantsHistory/ParticipantsHistoryProtocols.swift @@ -0,0 +1,88 @@ +// +// ParticipantsHistoryProtocols.swift +// Project +// +// Created by Bozhko Terziev on 15/11/2018. +// Copyright © 2018 Softavail. All rights reserved. +// + +import UIKit + +protocol ParticipantsHistoryWireFrameProtocol: class { + + func present(navigation: UINavigationController, main: MainWireFrame?, callLog: NYNCallLog) + + /** + * Add here your methods for communication PRESENTER -> WIREFRAME + */ + func hide() + + func showMyProfileScreen() + func showProfileScreen(contact: Contact) + func closeController() +} + +protocol ParticipantsHistoryViewProtocol: class { + + var presenter: ParticipantsHistoryPresenterProtocol! { get set } + + /** + * Add here your methods for communication PRESENTER -> VIEW + */ + func setupParticipants(_ participants: GroupedParticipants) + func updateParticipantsList(_ participants: GroupedParticipants) +} + +protocol ParticipantsHistoryPresenterProtocol: BasePresenterProtocol, NavigationProtocol { + + var view: ParticipantsHistoryViewProtocol! { get set } + var interactor: ParticipantsHistoryInteractorInputProtocol! { get set } + var wireFrame: ParticipantsHistoryWireFrameProtocol! { get set } + + + /** + * Add here your methods for communication VIEW -> PRESENTER + */ + func hide() + + func filter(with text: String) + + func didTapMember(_ member: Participant) + + func closeController() + + func getCallHostName()->String? + + func getCallHostImage()->UIImage? +} + +protocol ParticipantsHistoryInteractorOutputProtocol: class { + + /** + * Add here your methods for communication INTERACTOR -> PRESENTER + */ + func participantsFetched(_ participants: [Participant]) + func participantsFiltered(_ participants: [Participant]) + + func showMyProfile() + func showProfile(_ contact: Contact) + func showContactNotFound() +} + +protocol ParticipantsHistoryInteractorInputProtocol: BaseInteractorProtocol { + + var presenter: ParticipantsHistoryInteractorOutputProtocol! { get set } + + var includedMembers: [Member] { get set } + /** + * Add here your methods for communication PRESENTER -> INTERACTOR + */ + + func filterParticipants(with text: String) + + func findContact(by contact: Contact) + + func getCallHostName()->String? + + func getCallHostImage()->UIImage? +} diff --git a/Nynja/Modules/ParticipantsHistory/Presenter/ParticipantsHistoryPresenter.swift b/Nynja/Modules/ParticipantsHistory/Presenter/ParticipantsHistoryPresenter.swift new file mode 100644 index 0000000000000000000000000000000000000000..ff217fdbd60191b3909615133be886ce8974b38e --- /dev/null +++ b/Nynja/Modules/ParticipantsHistory/Presenter/ParticipantsHistoryPresenter.swift @@ -0,0 +1,81 @@ +// +// ParticipantsHistoryPresenter.swift +// Project +// +// Created by Bozhko Terziev on 15/11/2018. +// Copyright © 2018 Softavail. All rights reserved. +// + +class ParticipantsHistoryPresenter: BasePresenter, ParticipantsHistoryPresenterProtocol, ParticipantsHistoryInteractorOutputProtocol { + + //MARK: - NavigationProtocol + + func back() { + closeController() + } + + //MARK: - BasePresenter + + override var itemsFactory: WCItemsFactory? { + return GroupOptionsItemsFactory() + } + + //MARK: - ParticipantsHistoryPresenterProtocol + + weak var view: ParticipantsHistoryViewProtocol! + var interactor: ParticipantsHistoryInteractorInputProtocol! { + didSet { + _interactor = interactor + } + } + var wireFrame: ParticipantsHistoryWireFrameProtocol! + + func hide() { + wireFrame.hide() + } + + func getCallHostName()->String? { + return interactor.getCallHostName() + } + + func getCallHostImage()->UIImage? { + return interactor.getCallHostImage() + } + + //MARK: - Actions + + func filter(with text: String) { + interactor.filterParticipants(with: text) + } + + func didTapMember(_ member: Participant) { + interactor.findContact(by: member.contact) + } + + + //MARK: - ParticipantsHistoryInteractorOutputProtocol + + func participantsFetched(_ participants: [Participant]) { + view.setupParticipants(participants.groupedByAlias()) + } + + func participantsFiltered(_ participants: [Participant]) { + view.setupParticipants(participants.groupedByAlias()) + } + + func showMyProfile() { + wireFrame.showMyProfileScreen() + } + + func showProfile(_ contact: Contact) { + wireFrame.showProfileScreen(contact: contact) + } + + func showContactNotFound() { + AlertManager.sharedInstance.showAlertOk(message: String.localizable.profileNotFound) + } + + func closeController() { + wireFrame.closeController() + } +} diff --git a/Nynja/Modules/ParticipantsHistory/View/ParticipantHistoryHeaderView.swift b/Nynja/Modules/ParticipantsHistory/View/ParticipantHistoryHeaderView.swift new file mode 100644 index 0000000000000000000000000000000000000000..f4f807ba0901d0c8d69429f6be7ca10121300016 --- /dev/null +++ b/Nynja/Modules/ParticipantsHistory/View/ParticipantHistoryHeaderView.swift @@ -0,0 +1,106 @@ +// +// ParticipantHistoryHeaderView.swift +// Nynja +// +// Created by Bozhko Terziev on 16.11.18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import UIKit + +class ParticipantHistoryHeaderView: UIView { + + lazy var avatarImageView: UIImageView = { + let av = UIImageView() + av.contentMode = .scaleToFill + av.layer.cornerRadius = CGFloat(Constraints.avatar.height/2) + av.backgroundColor = UIColor.nynja.lightGray + av.clipsToBounds = true + av.layer.borderWidth = 1.0 + av.layer.borderColor = UIColor.nynja.callGreen.cgColor + + self.addSubview(av) + av.snp.makeConstraints { (make) in + make.left.equalToSuperview().offset(Constraints.avatar.leftOffset) + make.centerY.equalToSuperview() + make.height.width.equalTo(Constraints.avatar.height) + } + + return av + }() + + lazy var nameLabel: UILabel = { + let label = UILabel() + label.textAlignment = .left + label.font = UIFont.makeFont(with: FontFamily.NotoSans.regular.name, height: 22.0) + label.textColor = UIColor.nynja.white + label.backgroundColor = UIColor.nynja.clear + label.text = "" + + self.addSubview(label) + label.snp.makeConstraints { (make) in + make.left.equalTo(avatarImageView.snp.right).offset(Constraints.labelName.leftOffset) + make.centerY.equalTo(avatarImageView.snp.centerY) + } + + return label + }() + + private lazy var infoLabel: UILabel = { + let label = UILabel() + label.textAlignment = .right + label.font = UIFont.makeFont(with: FontFamily.NotoSans.regular.name, height: 16.0) + label.textColor = UIColor.nynja.lightGray + label.backgroundColor = UIColor.nynja.clear + label.text = String.localizable.callHistoryParticipantStartedCall + + self.addSubview(label) + label.snp.makeConstraints { (make) in + make.right.equalToSuperview().offset(-Constraints.labelTime.rightOffset) + make.centerY.equalTo(nameLabel.snp.centerY) + make.left.equalTo(nameLabel.snp.right).offset(Constraints.labelTime.horInset) + } + + return label + }() + + // MARK: - Init + + override init(frame: CGRect) { + super.init(frame: frame) + baseSetup() + } + + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + baseSetup() + } + + private func baseSetup() { + self.backgroundColor = UIColor.nynja.clear + self.avatarImageView.isHidden = false + self.infoLabel.isHidden = false + self.nameLabel.isHidden = false + } +} + +extension ParticipantHistoryHeaderView { + + struct Constraints { + + struct avatar { + static let leftOffset = 16.0 + static let height = 44.0 + } + + struct labelName { + static let leftOffset = 16.0 + static let topOffset = 11.0 + } + + struct labelTime { + static let rightOffset = 16.0 + static let horInset = 5.0 + } + } +} diff --git a/Nynja/Modules/ParticipantsHistory/View/ParticipantsHistoryViewController.swift b/Nynja/Modules/ParticipantsHistory/View/ParticipantsHistoryViewController.swift new file mode 100644 index 0000000000000000000000000000000000000000..8d07dcdab8c5de11b39652edff3e00059e75716b --- /dev/null +++ b/Nynja/Modules/ParticipantsHistory/View/ParticipantsHistoryViewController.swift @@ -0,0 +1,158 @@ +// +// ParticipantsHistoryProtocols.swift +// Project +// +// Created by Bozhko Terziev on 15/11/2018. +// Copyright © 2018 Softavail. All rights reserved. +// + +import UIKit + +class ParticipantsHistoryViewController: BaseVC, ParticipantsHistoryViewProtocol, KeyboardInteractive { + + var presenter: ParticipantsHistoryPresenterProtocol! { + didSet { + _presenter = presenter + } + } + + private let bottomInset = Constraints.controlContainerView.bottomInset.adjustedByWidth + + var participantsDataSource: ParticipantsDataSource! + var participantsDelegate: ParticipantsDelegate! + + + override func viewDidLoad() { + super.viewDidLoad() + } + + // MARK: - Views + + + private lazy var tableView: UITableView = { + let tblView = UITableView() + + tblView.backgroundColor = UIColor.nynja.clear + tblView.separatorStyle = .none + tblView.tableFooterView = UIView() + tblView.showsHorizontalScrollIndicator = false + tblView.rowHeight = ParticipantsContactCell.Constraints.height + tblView.estimatedRowHeight = tblView.rowHeight + tblView.sectionHeaderHeight = ParticipantsHeaderView.height + tblView.estimatedSectionHeaderHeight = tblView.sectionHeaderHeight + + self.view.addSubview(tblView) + tblView.snp.makeConstraints { make in + make.top.equalTo(tableHeaderView.snp.bottom) + make.left.right.bottom.equalToSuperview() + } + + return tblView + }() + + private lazy var tableHeaderView:ParticipantHistoryHeaderView = { + let header = ParticipantHistoryHeaderView() + + self.view.addSubview(header) + header.snp.updateConstraints { make in + make.top.equalTo(navigationView.snp.bottom) + make.left.right.equalToSuperview() + make.height.equalTo(Constraints.headerTableView.height) + } + + return header + } () + + var scrollBar: ScrollBar! + + + // MARK: - BaseVC + + override func initialize() { + super.initialize() + self.navigationView.isSeparatorVisible = false + + let title:String = String.localizable.callHistoryTitle.uppercased() + let backBtnImage:UIImage? = UIImage.nynja.icBackNavigation.image + let navHandler:NavigationProtocol? = presenter + + navigationView.configure(config: NavigationView.Config( + isVisibleSeparator: navigationView.isSeparatorVisible ?? false, + isVisibleBackButton: (nil != backBtnImage), + title: title, + navigationHandler: navHandler, + backButtonImage: backBtnImage) + ) + + screenTitle = title + + configure() + setupCallHost() + } + + // MARK: Configure + private func setupCallHost() { + + if let name = presenter.getCallHostName() { + tableHeaderView.nameLabel.text = name + } + + if let img = presenter.getCallHostImage() { + tableHeaderView.avatarImageView.image = img + } else { + tableHeaderView.avatarImageView.image = UIImage.nynja.Contacts.avaPlaceholder.image + } + } + + private func configure() { + configureTableView() + } + + private func configureTableView() { + tableView.register(ParticipantsContactCell.self, forCellReuseIdentifier: ParticipantsContactCell.cellId) + tableView.register(headerFooter: ParticipantsHeaderView.self) + + participantsDataSource = ParticipantsDataSource() + tableView.dataSource = participantsDataSource + + participantsDelegate = ParticipantsDelegate() + participantsDelegate.actionsDelegate = self + + tableView.delegate = participantsDelegate + + scrollBar = ScrollBar(scrollView: tableView) + participantsDelegate.scrollBar = scrollBar + } + + // MARK: - Actions + + @objc private func doneTapped(_ button: UIButton) { + view.endEditing(true) + presenter.hide() + } + + // MARK: - Keyboard + + func keyboardNotified(endFrame: CGRect) { + } + + // MARK: - AddParticipantsViewProtocol + + func updateParticipantsList(_ participants: GroupedParticipants) { + participantsDataSource.groupedParticipants = participants + tableView.reloadData() + } + + func setupParticipants(_ participants: GroupedParticipants) { + participantsDataSource.groupedParticipants = participants + tableView.reloadData() + } +} + + +extension ParticipantsHistoryViewController: ParticipantsActionsDelegate { + + func participantTapped(_ participant: Participant) { + presenter.didTapMember(participant) + } +} diff --git a/Nynja/Modules/ParticipantsHistory/View/ParticipantsHistoryViewControllerLayout.swift b/Nynja/Modules/ParticipantsHistory/View/ParticipantsHistoryViewControllerLayout.swift new file mode 100644 index 0000000000000000000000000000000000000000..f62c6ebd49e93d7ef4a49bfb86adf4c8349d0ce2 --- /dev/null +++ b/Nynja/Modules/ParticipantsHistory/View/ParticipantsHistoryViewControllerLayout.swift @@ -0,0 +1,40 @@ +// +// ParticipantsHistoryViewControllerLayout.swift +// Project +// +// Created by Bozhko Terziev on 15/11/2018. +// Copyright © 2018 Softavail. All rights reserved. +// + +extension ParticipantsHistoryViewController { + + struct Constraints { + + struct avatarsView { + static let height: CGFloat = 44.0 + + static let topInset = 8.0 + static let horizontalInset: CGFloat = 16.0 + } + + struct tableView { + static let topInset = 16.0 + } + + struct headerTableView { + static let height = 84.0 + } + + struct controlContainerView { + static let bottomInset: CGFloat = 28.0 + } + + struct doneButton { + static let width: CGFloat = 82.0 + static let height: CGFloat = 44.0 + + static let rightInset: CGFloat = 48.0 + static let contentRightInset: CGFloat = 16.0 + } + } +} diff --git a/Nynja/Modules/ParticipantsHistory/WireFrame/ParticipantsHistoryWireframe.swift b/Nynja/Modules/ParticipantsHistory/WireFrame/ParticipantsHistoryWireframe.swift new file mode 100644 index 0000000000000000000000000000000000000000..971bc829fce21ade22ab21f623c5354438799b88 --- /dev/null +++ b/Nynja/Modules/ParticipantsHistory/WireFrame/ParticipantsHistoryWireframe.swift @@ -0,0 +1,67 @@ +// +// ParticipantsHistoryWireframe.swift +// Project +// +// Created by Bozhko Terziev on 15/11/2018. +// Copyright © 2018 Softavail. All rights reserved. +// + +import UIKit + +class ParticipantsHistoryWireFrame: ParticipantsHistoryWireFrameProtocol { + + weak var navigation: UINavigationController? + weak var main: MainWireFrame? + + func present(navigation: UINavigationController, + main: MainWireFrame?, + callLog: NYNCallLog) { + let view = ParticipantsHistoryViewController() + let presenter = ParticipantsHistoryPresenter() + let interactor = ParticipantsHistoryInteractor() + + self.navigation = navigation + self.main = main + + var members = [Member]() + + for i in 0.. reader ? $0 as AnyObject : nil } + let unreadMentions = mentions.filter { $0 > reader } let room = Room() room.id = roomId diff --git a/Nynja/ServerModel/Model/Message.swift b/Nynja/ServerModel/Model/Message.swift index ff382502caebe3aca0350df114273d119ac50a16..56f3433ddbac82d83119207ffbe4d88456589282 100644 --- a/Nynja/ServerModel/Model/Message.swift +++ b/Nynja/ServerModel/Model/Message.swift @@ -12,9 +12,9 @@ class Message { var files: [Desc]? var type: [AnyObject]? var link: AnyObject? - var seenby: [AnyObject]? - var repliedby: [AnyObject]? - var mentioned: [AnyObject]? + var seenby: [Int64]? + var repliedby: [Int64]? + var mentioned: [Int64]? var status: AnyObject? diff --git a/Nynja/ServerModel/Model/Room.swift b/Nynja/ServerModel/Model/Room.swift index 99a85499b450a85cda61f8cb848cdcdde49f8e9b..dbc28d4ceb1456e73d37e1278453721a825d34cb 100755 --- a/Nynja/ServerModel/Model/Room.swift +++ b/Nynja/ServerModel/Model/Room.swift @@ -12,7 +12,7 @@ class Room { var tos: String? var tos_update: Int64? var unread: Int64? - var mentions: [AnyObject]? + var mentions: [Int64]? var readers: [AnyObject]? var last_msg: Message? var update: Int64? diff --git a/Nynja/ServerModel/Source/Decoder.swift b/Nynja/ServerModel/Source/Decoder.swift index 0af94781c52a8b09bfadef3a11e479ca94bbbb6f..c2107c16754743f6fd7262f7d8bdd54cee49fc6d 100644 --- a/Nynja/ServerModel/Source/Decoder.swift +++ b/Nynja/ServerModel/Source/Decoder.swift @@ -239,9 +239,9 @@ func parseObject(name: String, body:[Model], tuple: BertTuple) -> AnyObject? a_Message.files = body[9].parse(bert: tuple.elements[10]) as? [Desc] a_Message.type = body[10].parse(bert: tuple.elements[11]) as? [AnyObject] a_Message.link = body[11].parse(bert: tuple.elements[12]) as? AnyObject - a_Message.seenby = body[12].parse(bert: tuple.elements[13]) as? [AnyObject] - a_Message.repliedby = body[13].parse(bert: tuple.elements[14]) as? [AnyObject] - a_Message.mentioned = body[14].parse(bert: tuple.elements[15]) as? [AnyObject] + a_Message.seenby = body[12].parse(bert: tuple.elements[13]) as? [Int64] + a_Message.repliedby = body[13].parse(bert: tuple.elements[14]) as? [Int64] + a_Message.mentioned = body[14].parse(bert: tuple.elements[15]) as? [Int64] a_Message.status = body[15].parse(bert: tuple.elements[16]) as? AnyObject return a_Message case "Link": @@ -268,7 +268,7 @@ func parseObject(name: String, body:[Model], tuple: BertTuple) -> AnyObject? a_Room.tos = body[9].parse(bert: tuple.elements[10]) as? String a_Room.tos_update = body[10].parse(bert: tuple.elements[11]) as? Int64 a_Room.unread = body[11].parse(bert: tuple.elements[12]) as? Int64 - a_Room.mentions = body[12].parse(bert: tuple.elements[13]) as? [AnyObject] + a_Room.mentions = body[12].parse(bert: tuple.elements[13]) as? [Int64] a_Room.readers = body[13].parse(bert: tuple.elements[14]) as? [AnyObject] a_Room.last_msg = body[14].parse(bert: tuple.elements[15]) as? Message a_Room.update = body[15].parse(bert: tuple.elements[16]) as? Int64 diff --git a/Nynja/ServerModel/Spec/Message_Spec.swift b/Nynja/ServerModel/Spec/Message_Spec.swift index ba04a94738cd4692698b17bd307ac28ca3ec76d0..c4690991ee733614df05f1c85705bd9697686a26 100644 --- a/Nynja/ServerModel/Spec/Message_Spec.swift +++ b/Nynja/ServerModel/Spec/Message_Spec.swift @@ -51,9 +51,7 @@ private func get_Message(recursive: Bool) -> Model { Model(value:Atom(constant:"cursor"))]))))])), Model(value:Chain(types: linkTypes)), - Model(value:List(constant:nil, model:Model(value:Chain(types:[ - Model(value:Binary()), - Model(value:Number())])))), + Model(value:List(constant:nil, model:Model(value:Number()))), Model(value:List(constant:nil, model:Model(value:Number()))), Model(value:List(constant:nil, model:Model(value:Number()))), Model(value:Chain(types:[ diff --git a/Nynja/ServerModel/Spec/Room_Spec.swift b/Nynja/ServerModel/Spec/Room_Spec.swift index aaa00bf254abfcbb7c4f0392add78c123659a1fc..49e799450332420558ec08d826b5fdfe287b007d 100755 --- a/Nynja/ServerModel/Spec/Room_Spec.swift +++ b/Nynja/ServerModel/Spec/Room_Spec.swift @@ -32,9 +32,7 @@ func get_Room() -> Model { Model(value:Chain(types:[ Model(value:List(constant:"")), Model(value:Number())])), - Model(value:Chain(types:[ - Model(value:List(constant:"")), - Model(value:List(constant:nil, model:Model(value:Number())))])), + Model(value:List(constant:nil, model:Model(value:Number()))), Model(value:List(constant:nil, model:Model(value:Number()))), Model(value:Chain(types:[ Model(value:List(constant:"")), diff --git a/Nynja/Services/Aps.swift b/Nynja/Services/Aps.swift index dc39a64b1f61552d0a54f829448fb31f819a22dd..b6a0bc33625dcd88613241fc20c5f9b50841d6fc 100644 --- a/Nynja/Services/Aps.swift +++ b/Nynja/Services/Aps.swift @@ -273,7 +273,7 @@ extension Aps { return false } if let mime = message.files?.first?.mime, - mime == SendMessageType.audioCall.rawValue { + mime == SendMessageType.audioCall.rawValue || mime == SendMessageType.videoCall.rawValue { return false } return true diff --git a/Nynja/Services/Debug/LogService/LogService/LogService.swift b/Nynja/Services/Debug/LogService/LogService/LogService.swift index 3ca220fce6cdf0ef2bed131060c9d43005b8a97c..f7468bc3536564c0e81f2733b225a59764fbf22b 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 -final class LogService { +final class LogService: LogServiceProtocol { private static let logWriter: LogWriterProtocol? = ServiceFactory().makeLogWriter() @@ -18,22 +18,17 @@ final class LogService { .system, .network, .galery, .videoConverter, .QRCode, .passphrase, .connectionState] - private static let backgroundQueue = DispatchQueue(label:"logQueue",qos:.background) - - static func log(topic: LogServiceTopic, - file: String = #file, - function: String = #function, - line: Int = #line, - block: (() -> String)?) { + private static let backgroundQueue = DispatchQueue(label: "logQueue", qos: .background) + + static func log(topic: LogServiceTopic, block: (() -> String)?) { #if !RELEASE backgroundQueue.async { - if !enabledTopics.contains(topic) { return } - - let header = "File: \(file), function: \(function), line: \(line)" - let message = block?() ?? "" - let text = "\(header)\n\(message)" + guard enabledTopics.contains(topic) else { + return + } + let text = block?() ?? "" LogService.executeLogs(topic: topic, text: text, thread: Thread.current.debugDescription) } #endif @@ -57,5 +52,4 @@ final class LogService { i += 1 } } - } diff --git a/Nynja/Services/Debug/LogService/LogService/LogServiceProtocol.swift b/Nynja/Services/Debug/LogService/LogServiceProtocol.swift similarity index 61% rename from Nynja/Services/Debug/LogService/LogService/LogServiceProtocol.swift rename to Nynja/Services/Debug/LogService/LogServiceProtocol.swift index 94822e1a55ea2000a896182971d64746dcb3db98..1a2dc2e376cbaf6c3a5b2503765092a68032743e 100644 --- a/Nynja/Services/Debug/LogService/LogService/LogServiceProtocol.swift +++ b/Nynja/Services/Debug/LogService/LogServiceProtocol.swift @@ -7,7 +7,6 @@ // protocol LogServiceProtocol { - static var enabledTopics: [LogServiceTopic] { get } - static func log(topic: LogServiceTopic, block: () -> String) + static func log(topic: LogServiceTopic, block: (() -> String)?) } diff --git a/Nynja/Services/Debug/LogService/LogService/LogServiceTopic.swift b/Nynja/Services/Debug/LogService/LogServiceTopic.swift similarity index 100% rename from Nynja/Services/Debug/LogService/LogService/LogServiceTopic.swift rename to Nynja/Services/Debug/LogService/LogServiceTopic.swift diff --git a/Nynja/Services/Debug/LogService/LogWriter.swift b/Nynja/Services/Debug/LogService/LogWriter/LogWriter.swift similarity index 100% rename from Nynja/Services/Debug/LogService/LogWriter.swift rename to Nynja/Services/Debug/LogService/LogWriter/LogWriter.swift diff --git a/Nynja/Services/Debug/LogService/LogService/LogWriterProtocol.swift b/Nynja/Services/Debug/LogService/LogWriter/LogWriterProtocol.swift similarity index 100% rename from Nynja/Services/Debug/LogService/LogService/LogWriterProtocol.swift rename to Nynja/Services/Debug/LogService/LogWriter/LogWriterProtocol.swift diff --git a/Nynja/Services/HandleServices/MessageHandler.swift b/Nynja/Services/HandleServices/MessageHandler.swift index 7caa63230d30736c32c046ba62ea82ddc23007ae..fa1e314302b990754417e3625a548ff8bf8c028a 100644 --- a/Nynja/Services/HandleServices/MessageHandler.swift +++ b/Nynja/Services/HandleServices/MessageHandler.swift @@ -9,28 +9,28 @@ import Foundation final class MessageHandler: BaseHandler { - + // MARK: - Dependencies - + private static var storageService: StorageService { return StorageService.sharedInstance } - + private static var notificationManager: NotificationManager { return NotificationManager.shared } - + private static var systemSoundManager: SystemSoundManager { return SystemSoundManager.sharedInstance } - - + + // MARK: - Subscribers - + private static let subscribersQueue = DispatchQueue(label: String.label(withSuffix: "message-handler.subscribers-queue")) - + static var subscribers: [MessageHandlerSubscriberReference] = [] - + static func addSubscriber(_ subscriber: MessageHandlerSubscriber) { subscribersQueue.async { [weak subscriber] in guard let subscriber = subscriber else { return } @@ -41,37 +41,37 @@ final class MessageHandler: BaseHandler { subscribers.append(ref) } } - + static func removeSubscriber(_ subscriber: MessageHandlerSubscriber) { subscribersQueue.async { [weak subscriber] in guard let subscriber = subscriber else { return } subscribers = subscribers.filter { $0.subscriber != nil && $0.subscriber !== subscriber } } } - + private static func notify(block: @escaping (MessageHandlerSubscriber) -> Void) { subscribersQueue.sync { subscribers.forEach { ref in guard let subscriber = ref.subscriber else { return } - + dispatchAsyncMain { block(subscriber) } } } } - - + + // MARK: - Execute - + static func executeHandle(data: BertTuple) { guard let message = get_Message().parse(bert: data) as? Message else { return } - + let types = message.types let status = message.messageStatus - + switch (types, status) { case let (types, status) where types.contains("sys") && status == .clear: clearHistory(message) @@ -87,28 +87,28 @@ final class MessageHandler: BaseHandler { saveMessage(message, data: data) } } - + private static func clearHistory(_ message: Message) { ChatService.clearHistory(message) } - + private static func updateReader(from message: Message) { let shouldUpdateOwnReader = self.shouldUpdateOwnReader(from: message) let shouldUpdateOtherReader = !shouldUpdateOwnReader || message.isInOwnChat - + if shouldUpdateOwnReader { ChatService.updateReader(from: message, kind: .own) } - + if shouldUpdateOtherReader { ChatService.updateReader(from: message, kind: .other) } } - + private static func shouldUpdateOwnReader(from message: Message) -> Bool { return message.isOwn } - + private static func deleteMessage(_ message: Message) { do { try save(message) @@ -117,7 +117,7 @@ final class MessageHandler: BaseHandler { LogService.log(topic: .db) { "\(#function), line: \(#line) - \(error.localizedDescription)" } } } - + private static func editMessage(_ message: Message) { do { try save(message) @@ -126,7 +126,7 @@ final class MessageHandler: BaseHandler { LogService.log(topic: .db) { "\(#function), line: \(#line) - \(error.localizedDescription)" } } } - + private static func updateMessage(_ message: Message) { do { try save(message) @@ -136,62 +136,62 @@ final class MessageHandler: BaseHandler { LogService.log(topic: .db) { "\(#function), line: \(#line) - \(error.localizedDescription)" } } } - + private static func saveMessage(_ message: Message, data: BertTuple) { guard !shouldSkipMessage(message) else { + LogService.log(topic: .db) { "\(#function), line: \(#line) - id: \(message.id ?? 0)" } return } - + // Notify subscribers about saving process notify { $0.willSave(message) } defer { notify { $0.didSave(message) } } - + let isMessageExistsInLocalDatabase = message.id .flatMap { try? MessageDAO.messageExists(serverId: $0) } ?? false - - if message.isInOwnChat, !message.isCursor { - ChatService.updateReader(from: message, kind: .other) - ChatService.updateReader(from: message, kind: .own) - } else if !message.isInOwnChat, shouldUpdateOwnReader(from: message) { - ChatService.updateReader(from: message, kind: .own) - ChatService.resetUnreadCount(from: message) - } else if !shouldUpdateOwnReader(from: message) { - ChatService.updateReader(from: message, kind: .other) - } + + updateReaderWhenSaving(message: message) try? save(message) - + if message.isForward { JobDAO.deleteJobs(for: message) } - + let isLastMessageUpdated = ChatService.updateLastMessage(message) { _ in return !isMessageExistsInLocalDatabase } - + if isLastMessageUpdated, UIApplication.shared.applicationState == .active { notificationManager.handle(bert: data, type: .message) } - + // Don't play incoming chat sounds for message, that already exists in database. if !isMessageExistsInLocalDatabase { playSoundIfNeeded(for: message) } } - + + private static func updateReaderWhenSaving(message: Message) { + if message.isInOwnChat, !message.isCursor { + ChatService.updateReader(from: message, kind: .other) + ChatService.updateReader(from: message, kind: .own) + } else if !message.isInOwnChat, shouldUpdateOwnReader(from: message) { + ChatService.updateReader(from: message, kind: .own) + ChatService.resetUnreadCount(from: message) + } else if !shouldUpdateOwnReader(from: message) { + ChatService.updateReader(from: message, kind: .other) + } + } + private static func shouldSkipMessage(_ message: Message) -> Bool { - if let desc = message.files?.first, - desc.mime == SendMessageType.audioCall.rawValue, - desc.data?.first?.key == FeatureKeys.File.Call.users.rawValue, - let ids = desc.data?.first?.value?.splitByComma(), - !ids.contains { $0 == storageService.phoneId } { - - return true + guard let rosterId = storageService.rosterId else { + return false } - - return false + + return !message.isSeenBy(rosterId: rosterId) } private static func save(_ message: Message) throws { @@ -204,7 +204,7 @@ final class MessageHandler: BaseHandler { try MessageDAO.trustIfNextMessageExists(before: message) try storageService.perform(action: .save, with: message) } - + /// Play sound for incoming messages if chat isn't muted. /// Play outcoming message only if chat screen is open. private static func playSoundIfNeeded(for message: Message) { @@ -221,16 +221,16 @@ final class MessageHandler: BaseHandler { member.shouldNotify(for: message) else { break } - + systemSoundManager.playIncomingMessageSound() - + case is p2p: guard let phoneId = MessageDAO.getDestination(for: message, currentPhoneId: currentPhoneId), let contact = ContactDAO.findContactBy(phoneId: phoneId), contact.notifications else { break } - + systemSoundManager.playIncomingMessageSound() default: diff --git a/Nynja/Services/MQTT/Entities/MQTTMessage.swift b/Nynja/Services/MQTT/Entities/MQTTMessage.swift new file mode 100644 index 0000000000000000000000000000000000000000..e55c8d821ae8bbec86bf5e85a2184aa0eb63d089 --- /dev/null +++ b/Nynja/Services/MQTT/Entities/MQTTMessage.swift @@ -0,0 +1,19 @@ +// +// MQTTMessage.swift +// Nynja +// +// Created by Volodymyr Hryhoriev on 12/6/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + +struct MQTTMessage { + let payload: Data? + let topic: String? + + init(payload: Data?, topic: String?) { + self.payload = payload + self.topic = topic + } +} diff --git a/Nynja/Services/MQTT/MQTTService.swift b/Nynja/Services/MQTT/MQTTService.swift index b77c4d60a02c4ca7889a05d7381a125599a68d99..e26350e87d6c076c6fc3d0cc6f6aa9e58dd3473d 100644 --- a/Nynja/Services/MQTT/MQTTService.swift +++ b/Nynja/Services/MQTT/MQTTService.swift @@ -162,7 +162,7 @@ final class MQTTService: NSObject, MQTTServiceProtocol, ConnectionServiceDelegat queuePool.processingQueue.async { [weak mqtt] in guard let mqtt = mqtt, mqtt.status == .connected else { return } let finalModel: MQTTMessage = model.getMessage() - mqtt.publishData(finalModel.payload, onTopic: finalModel.topic, retain: false, qos: finalModel.qos) + mqtt.publishData(finalModel.payload, onTopic: finalModel.topic, retain: false, qos: .exactlyOnce) } } @@ -300,17 +300,3 @@ final class MQTTService: NSObject, MQTTServiceProtocol, ConnectionServiceDelegat } } } - - -struct MQTTMessage { - let payload: Data? - let topic: String? - var qos: MQTTQosLevel! - - init(payload: Data?, topic: String?, qos: MQTTQosLevel? = nil) { - self.payload = payload - self.topic = topic - self.qos = qos - if qos == nil { self.qos = .exactlyOnce } - } -} diff --git a/Nynja/Services/Models/BaseMQTTModel.swift b/Nynja/Services/Models/BaseMQTTModel.swift index ee269c20ee4ed53d5f9b35d7f89b762e4f4d6d23..12606920977c9a9b99438c4233664b3543bd2c32 100644 --- a/Nynja/Services/Models/BaseMQTTModel.swift +++ b/Nynja/Services/Models/BaseMQTTModel.swift @@ -17,7 +17,7 @@ class BaseMQTTModel { func getMessage() -> MQTTMessage { let topic = "events/1//api/anon//" let payload = self.getBert() - let msg = MQTTMessage(payload: payload, topic: topic, qos: .exactlyOnce) + let msg = MQTTMessage(payload: payload, topic: topic) return msg } } diff --git a/Nynja/Services/Models/SendModel.swift b/Nynja/Services/Models/SendModel.swift index ea1a85b68e67d97fc2a1750f3907d631cd7af82b..c8dc4a2280b01263c14b1335101c3d56e3f08a43 100644 --- a/Nynja/Services/Models/SendModel.swift +++ b/Nynja/Services/Models/SendModel.swift @@ -40,6 +40,8 @@ enum SendMessageType: String { return SendMessageType.location.rawValue.capitalized case .audioCall: return String.localizable.audioCall + case .videoCall: + return String.localizable.video default: return rawValue.capitalized } diff --git a/Nynja/Services/NynjaCalls/NynjaCommunicatorService.swift b/Nynja/Services/NynjaCalls/NynjaCommunicatorService.swift index 526cd7e54b131159ba603208a08be1f776c47146..c587b2e2b6c521a27bdca553d5f3f567c4b9a247 100644 --- a/Nynja/Services/NynjaCalls/NynjaCommunicatorService.swift +++ b/Nynja/Services/NynjaCalls/NynjaCommunicatorService.swift @@ -36,7 +36,6 @@ protocol NynjaCallDelegate: class { func callEnded(call: NYNCall, isMissed: Bool) func stateDidChange(call: NYNCall, state: NYNCallState) func participantsUpdated(call: NYNCall) - func conferenceCreated(call: NYNCall) func didAddVideoStreamForCall(call: NYNCall) func didRemoveVideoStreamForCall(call: NYNCall) func didStartLocalCapturerForCall(call: NYNCall) @@ -49,7 +48,6 @@ extension NynjaCallDelegate { func callEnded(call: NYNCall, isMissed: Bool) {} func stateDidChange(call: NYNCall, state: NYNCallState) {} func participantsUpdated(call: NYNCall){} - func conferenceCreated(call: NYNCall) {} func didAddVideoStreamForCall(call: NYNCall) {} func didRemoveVideoStreamForCall(call: NYNCall) {} func didStartLocalCapturerForCall(call: NYNCall) {} @@ -59,7 +57,7 @@ extension NynjaCallDelegate { } class NynjaCommunicatorService: NSObject, NynjaCommunicatorDelegate, NYNCallDelegate, NYNCallManagerDelegate, ConnectionServiceDelegate { - + private var initialized: Bool = false private let storageService = StorageService.sharedInstance let nynComm: NynjaCommunicator @@ -463,7 +461,43 @@ class NynjaCommunicatorService: NSObject, NynjaCommunicatorDelegate, NYNCallDele call.switchCamera() } } + + func setCallHistory(delegate:NynjaCallHistoryManagerDelegate?) { + self.nynComm.getCallHistoryManager().delegate = delegate + } + + func synchronizeHistory() -> NYNCallHistoryCode { + return self.nynComm.getCallHistoryManager().synchronize() + } + + func callHistoryFetchAll() -> NYNCallLogFetchedResults? { + return self.nynComm.getCallHistoryManager().fetchAll() + } + + func callHistoryFetchMissed() -> NYNCallLogFetchedResults? { + return self.nynComm.getCallHistoryManager().fetchMissed() + } + func callHistoryFetchOutgoing() -> NYNCallLogFetchedResults? { + return self.nynComm.getCallHistoryManager().fetchOutgoing() + } + + func callHistoryFetchIncoming() -> NYNCallLogFetchedResults? { + return self.nynComm.getCallHistoryManager().fetchIncoming() + } + + func removeCallLogBy(index:UInt) -> NYNCallHistoryCode { + return self.nynComm.getCallHistoryManager().deleteCallLog(by: index) + } + + func clearCallHistory() -> NYNCallHistoryCode { + return self.nynComm.getCallHistoryManager().deleteAllHistory() + } + + func getCallHistoryRecordMembers(id:String) -> NYNCallHistoryCode{ + return self.nynComm.getCallHistoryManager().fetchCallRecordParticipants(id); + } + //MARK: Helpers func getMySelf() -> Contact? { @@ -742,8 +776,6 @@ class NynjaCommunicatorService: NSObject, NynjaCommunicatorDelegate, NYNCallDele self.nynComm.getCallManager().startConference(withRequestId: cr.startId!, withConferenceId: cr.conferenceId!) - guard let call = self.call else { return } - self.callDelegate?.conferenceCreated(call: call) } } } diff --git a/Nynja/Services/PushService.swift b/Nynja/Services/PushService.swift index 74a93335d6b3552cf3cfdf378dcbcef2fe612141..4d2cd6877830ae9c515c155b8941d02556a2514f 100644 --- a/Nynja/Services/PushService.swift +++ b/Nynja/Services/PushService.swift @@ -284,9 +284,11 @@ final class PushService: NSObject, PKPushRegistryDelegate, UserSettingsRespondab if let repliedMessage = message.repliedMessage { repliedMessage.localStatus = try MessageDAO.localStatusForRepliedMessage(repliedMessage) } + if let status = try MessageDAO.localStatus(message: message) { message.localStatus = status } + if message.messageStatus == .clear { ChatService.clearMessages(before: message) } diff --git a/NynjaUnitTests/Models/MessageTests.swift b/NynjaUnitTests/Models/MessageTests.swift new file mode 100644 index 0000000000000000000000000000000000000000..6f0442d994706be1352fc17eceb0e2406f87e52e --- /dev/null +++ b/NynjaUnitTests/Models/MessageTests.swift @@ -0,0 +1,44 @@ +// +// MessageTests.swift +// NynjaUnitTests +// +// Created by Volodymyr Hryhoriev on 12/6/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import XCTest + +class MessageTests: XCTestCase { + + func testIsSeenByAll() { + assert(seenBy: nil, rosterId: 123, expectedResult: true) + } + + func testIsSeenByNone() { + assert(seenBy: [0], rosterId: 123, expectedResult: false) + } + + func testIsSeenByMe() { + assert(seenBy: [1, 12, 123], rosterId: 123, expectedResult: true) + assert(seenBy: [-1, -12, -13], rosterId: 123, expectedResult: true) + } + + func testIsSeenByNotMe() { + assert(seenBy: [1, 12], rosterId: 123, expectedResult: false) + assert(seenBy: [-1, -123, -13], rosterId: 123, expectedResult: false) + } + + private func assert(seenBy: [Int64]?, rosterId: Int64, expectedResult: Bool) { + let message = makeMessage(seenBy: seenBy) + + let result = message.isSeenBy(rosterId: rosterId) + + XCTAssertEqual(result, expectedResult) + } + + private func makeMessage(seenBy: [Int64]?) -> Message { + let message = Message() + message.seenby = seenBy + return message + } +} diff --git a/Podfile b/Podfile index a70f91f1c071a5b0c20c37b7751facc661b1369a..25a1dca06b6f2283a35d3a8c5971d7763f44c468 100644 --- a/Podfile +++ b/Podfile @@ -39,7 +39,7 @@ def commonPodsForNynja pod 'MaterialComponents/FlexibleHeader', '= 55.3.0' pod 'JTAppleCalendar', '= 7.1.6' - pod 'NynjaSDK', '= 1.8' + #pod 'NynjaSDK', '= 1.7.1' pod 'CryptoSwift', '= 0.13.0' diff --git a/Podfile.lock b/Podfile.lock index 1d2aad740c4507a1f56a5f7a107986717990e964..f52c44a27ac09841a01b4598780ed6d70073a8b5 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -13,25 +13,25 @@ PODS: - Fabric (~> 1.6.3) - CryptoSwift (0.13.0) - Fabric (1.6.13) - - Firebase/Auth (5.8.1): + - Firebase/Auth (5.13.0): - Firebase/CoreOnly - - FirebaseAuth (= 5.0.4) - - Firebase/CoreOnly (5.8.1): - - FirebaseCore (= 5.1.3) - - Firebase/Storage (5.8.1): + - FirebaseAuth (= 5.0.5) + - Firebase/CoreOnly (5.13.0): + - FirebaseCore (= 5.1.8) + - Firebase/Storage (5.13.0): - Firebase/CoreOnly - - FirebaseStorage (= 3.0.2) - - FirebaseAuth (5.0.4): + - FirebaseStorage (= 3.0.3) + - FirebaseAuth (5.0.5): - FirebaseAuthInterop (~> 1.0) - - FirebaseCore (~> 5.0) + - FirebaseCore (~> 5.1) - GoogleUtilities/Environment (~> 5.2) - GTMSessionFetcher/Core (~> 1.1) - FirebaseAuthInterop (1.0.0) - - FirebaseCore (5.1.3): + - FirebaseCore (5.1.8): - GoogleUtilities/Logger (~> 5.2) - - FirebaseStorage (3.0.2): + - FirebaseStorage (3.0.3): - FirebaseAuthInterop (~> 1.0) - - FirebaseCore (~> 5.0) + - FirebaseCore (~> 5.1) - GTMSessionFetcher/Core (~> 1.1) - GoogleMaps (2.7.0): - GoogleMaps/Maps (= 2.7.0) @@ -40,12 +40,12 @@ PODS: - GoogleMaps/Base - GooglePlaces (2.7.0): - GoogleMaps/Base (= 2.7.0) - - GoogleUtilities/Environment (5.3.0) - - GoogleUtilities/Logger (5.3.0): + - GoogleUtilities/Environment (5.3.6) + - GoogleUtilities/Logger (5.3.6): - GoogleUtilities/Environment - GRDBCipher (2.10.0): - SQLCipher (~> 3.4.1) - - GTMSessionFetcher/Core (1.1.15) + - GTMSessionFetcher/Core (1.2.1) - Intercom (5.1.6) - JTAppleCalendar (7.1.6) - libPhoneNumber-iOS (0.9.13) @@ -62,7 +62,6 @@ PODS: - MQTTClient/Min - SocketRocket - MulticastDelegateSwift (2.1.1) - - NynjaSDK (1.8) - QRCode (2.0) - SDWebImage (4.4.2): - SDWebImage/Core (= 4.4.2) @@ -95,7 +94,6 @@ DEPENDENCIES: - MaterialComponents/FlexibleHeader (= 55.3.0) - MQTTClient/Websocket (= 0.15.2) - MulticastDelegateSwift (= 2.1.1) - - NynjaSDK (= 1.8) - QRCode (= 2.0) - SDWebImage (= 4.4.2) - SnapKit (= 4.2.0) @@ -134,8 +132,6 @@ SPEC REPOS: - SQLCipher - SwiftyJSON - TestFairy - https://nynjagroup.jfrog.io/nynjagroup/api/pods/cocoapods-local: - - NynjaSDK EXTERNAL SOURCES: CocoaLumberjack: @@ -155,16 +151,16 @@ SPEC CHECKSUMS: Crashlytics: 95d05f4e4c19a771250c4bd9ce344d996de32bbf CryptoSwift: 16e78bebf567bad1c87b2d58f6547f25b74c31aa Fabric: 2fb5676bc811af011a04513451f463dac6803206 - Firebase: a870ed114d769b424021a0c8ddb0c86c3250a0c5 - FirebaseAuth: 504b198ceb3472dca5c65bb95544ea44cfc9439e + Firebase: 6df9a6114bc9f106a98fe83d5438d4d9833c2019 + FirebaseAuth: 9299ab178271bec7426967b05b2718bb6fc31f17 FirebaseAuthInterop: 0ffa57668be100582bb7643d4fcb7615496c41fc - FirebaseCore: 27bd80e5bfaaf9552a1f5cacb4c7e8bb925bab22 - FirebaseStorage: fd82e5e5c474897e19972b34b22ac0f589dce04e + FirebaseCore: fba2bfaa691c49028309b92e4dd37cc4b5512fbe + FirebaseStorage: 3d22c041370593e639fba013d1eb698a8dae2881 GoogleMaps: f79af95cb24d869457b1f961c93d3ce8b2f3b848 GooglePlaces: 3d06e6c99654545b4738ce49648745779c25f2ef - GoogleUtilities: 760ccb53b7c7f40f9c02d8c241f76f841a7a6162 + GoogleUtilities: 95996bea7c7d9b8fb811b7507669a4a8762f80c7 GRDBCipher: eef21d242c727a21e0f87ad44f8ea2df03edd252 - GTMSessionFetcher: 5fa5b80fd20e439ef5f545fb2cb3ca6c6714caa2 + GTMSessionFetcher: 32aeca0aa144acea523e1c8e053089dec2cb98ca Intercom: 083a05bf222811b0b5e0a0b24c863544123397f0 JTAppleCalendar: abb30678f42a4ef8a340a932b1dcb8c85a33dac2 libPhoneNumber-iOS: e444379ac18bbfbdefad571da735b2cd7e096caa @@ -172,7 +168,6 @@ SPEC CHECKSUMS: MDFTextAccessibility: 94098925e0853551c5a311ce7c1ecefbe297cdb6 MQTTClient: 902c7bcac1501595f3d0b15178c7205b40331fb0 MulticastDelegateSwift: 93eb077c24f50574b3f8a3f23bf71be6de6e3b41 - NynjaSDK: 3c245c3a1b1e5e650012d2f367260ecf4860e748 QRCode: f98a1886c8f37523704a7512a4c0cd45b34c18a4 SDWebImage: 624d6e296c69b244bcede364c72ae0430ac14681 SnapKit: fe8a619752f3f27075cc9a90244d75c6c3f27e2a @@ -181,6 +176,6 @@ SPEC CHECKSUMS: SwiftyJSON: c4bcba26dd9ec7a027fc8eade48e2c911f229e96 TestFairy: 842f8ddc45477b208eb85326b0418047b40f7137 -PODFILE CHECKSUM: 955505f5c9ab80c1ec2042a2c5d6825a0ac12771 +PODFILE CHECKSUM: 263625459e4e6516338c27fb7c538965069b1753 COCOAPODS: 1.5.3 diff --git a/Shared/Library/Extensions/Models/Message/Message+SeenBy.swift b/Shared/Library/Extensions/Models/Message/Message+SeenBy.swift new file mode 100644 index 0000000000000000000000000000000000000000..c43eb5cc38bcb46b9a51c9bd9895831a0959d0f0 --- /dev/null +++ b/Shared/Library/Extensions/Models/Message/Message+SeenBy.swift @@ -0,0 +1,46 @@ +// +// Message+SeenBy.swift +// Nynja +// +// Created by Volodymyr Hryhoriev on 12/6/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +extension Message { + + var isDeletedForAll: Bool { + guard let seenBy = seenby else { + return false + } + return seenBy.contains(0) + } + + func isSeenBy(rosterId: Int64) -> Bool { + guard let seenBy = seenby, let firstRosterId = seenBy.first else { + return true + } + + if firstRosterId > 0, seenBy.contains(rosterId) { + return true + } else if firstRosterId < 0, !seenBy.contains(-rosterId) { + return true + } else { + return false + } + } + + func addDeletedLocalStatusIfNeeded(rosterId: Int64) { + guard !isSeenBy(rosterId: rosterId) else { + return + } + + let localStatus: LocalStatus + if let status = self.localStatus { + localStatus = status.union(.deleted) + } else { + localStatus = .deleted + } + + self.localStatus = localStatus + } +}