diff --git a/Nynja.xcodeproj/project.pbxproj b/Nynja.xcodeproj/project.pbxproj index e2638347e007dc96c09b62736f7038ec772b6b98..aa264a14df4fa7319f299a7d019e774458a674a6 100644 --- a/Nynja.xcodeproj/project.pbxproj +++ b/Nynja.xcodeproj/project.pbxproj @@ -230,7 +230,7 @@ 2651094020ADBB0200F1B38B /* NotificationSettingProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2651093E20ADB81100F1B38B /* NotificationSettingProtocol.swift */; }; 2652D6161FA82EFE005E62C7 /* EditProfileVCLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2652D6151FA82EFE005E62C7 /* EditProfileVCLayout.swift */; }; 2652D6181FA85B28005E62C7 /* ImageSelector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2652D6171FA85B28005E62C7 /* ImageSelector.swift */; }; - 26534B25210B4BE70003B9BC /* DBMessage+TypeExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26534B24210B4BE70003B9BC /* DBMessage+TypeExtension.swift */; }; + 26534B25210B4BE70003B9BC /* DBMessage+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26534B24210B4BE70003B9BC /* DBMessage+Extension.swift */; }; 26541F722007B93400AAEACF /* DBMessageAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26541F712007B93400AAEACF /* DBMessageAction.swift */; }; 26541F742007B9A200AAEACF /* MessageActionTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26541F732007B9A200AAEACF /* MessageActionTable.swift */; }; 2657BE51201233E300F21935 /* ImageFilledItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2657BE50201233E300F21935 /* ImageFilledItemModel.swift */; }; @@ -969,6 +969,8 @@ 85B0013421272694000C89FE /* MessageInteractor+History.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85B0013321272694000C89FE /* MessageInteractor+History.swift */; }; 85B750A120334A2B00AD6013 /* ForwardTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85B750A020334A2B00AD6013 /* ForwardTableViewCell.swift */; }; 85BA176120BEA7BD001EF8AC /* StickerPreviewContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85BA176020BEA7BD001EF8AC /* StickerPreviewContainerView.swift */; }; + 85BDD2B821465EFA00695DE5 /* ScrollDirection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85BDD2B721465EFA00695DE5 /* ScrollDirection.swift */; }; + 85BDD2BA21467A9500695DE5 /* MessageFactoryProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85BDD2B921467A9500695DE5 /* MessageFactoryProtocol.swift */; }; 85BEC0E12063F91C0098C99C /* TimeZoneCellModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85BEC0E02063F91C0098C99C /* TimeZoneCellModel.swift */; }; 85C16C3520D2520E00EDB77E /* StickersDownloadingService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85C16C3420D2520E00EDB77E /* StickersDownloadingService.swift */; }; 85C16C3C20D261C000EDB77E /* MessageStickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85C16C3B20D261C000EDB77E /* MessageStickerView.swift */; }; @@ -2389,7 +2391,7 @@ 2651093E20ADB81100F1B38B /* NotificationSettingProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingProtocol.swift; sourceTree = ""; }; 2652D6151FA82EFE005E62C7 /* EditProfileVCLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditProfileVCLayout.swift; sourceTree = SOURCE_ROOT; }; 2652D6171FA85B28005E62C7 /* ImageSelector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageSelector.swift; sourceTree = ""; }; - 26534B24210B4BE70003B9BC /* DBMessage+TypeExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DBMessage+TypeExtension.swift"; sourceTree = ""; }; + 26534B24210B4BE70003B9BC /* DBMessage+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DBMessage+Extension.swift"; sourceTree = ""; }; 26541F712007B93400AAEACF /* DBMessageAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DBMessageAction.swift; sourceTree = ""; }; 26541F732007B9A200AAEACF /* MessageActionTable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageActionTable.swift; sourceTree = ""; }; 2657BE50201233E300F21935 /* ImageFilledItemModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageFilledItemModel.swift; sourceTree = ""; }; @@ -3032,6 +3034,8 @@ 85B0013321272694000C89FE /* MessageInteractor+History.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MessageInteractor+History.swift"; sourceTree = ""; }; 85B750A020334A2B00AD6013 /* ForwardTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ForwardTableViewCell.swift; sourceTree = ""; }; 85BA176020BEA7BD001EF8AC /* StickerPreviewContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StickerPreviewContainerView.swift; sourceTree = ""; }; + 85BDD2B721465EFA00695DE5 /* ScrollDirection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScrollDirection.swift; sourceTree = ""; }; + 85BDD2B921467A9500695DE5 /* MessageFactoryProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageFactoryProtocol.swift; sourceTree = ""; }; 85BEC0E02063F91C0098C99C /* TimeZoneCellModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeZoneCellModel.swift; sourceTree = ""; }; 85C16C3420D2520E00EDB77E /* StickersDownloadingService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = StickersDownloadingService.swift; path = Services/StickersDownloadingService/StickersDownloadingService.swift; sourceTree = ""; }; 85C16C3B20D261C000EDB77E /* MessageStickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageStickerView.swift; sourceTree = ""; }; @@ -4967,7 +4971,7 @@ 26534B23210B4BCA0003B9BC /* Extension */ = { isa = PBXGroup; children = ( - 26534B24210B4BE70003B9BC /* DBMessage+TypeExtension.swift */, + 26534B24210B4BE70003B9BC /* DBMessage+Extension.swift */, ); path = Extension; sourceTree = ""; @@ -7743,6 +7747,7 @@ 8540A330211B34B4007F65AF /* MessageCollectionViewDataSource.swift */, 8540A332211B35A4007F65AF /* MessageCollectionViewDelegate.swift */, 85D77806211D9B980044E72F /* ScrollPosition.swift */, + 85BDD2B721465EFA00695DE5 /* ScrollDirection.swift */, 2625F29E212463E8007C42B5 /* ProgressIdentifier.swift */, ); path = CollectionView; @@ -12036,6 +12041,7 @@ F11786CF20A9867C007A9A1B /* MessageFactory */ = { isa = PBXGroup; children = ( + 85BDD2B921467A9500695DE5 /* MessageFactoryProtocol.swift */, F11786D020A98685007A9A1B /* MessageFactory.swift */, 264C808520DBF397003532FA /* DBFeatureFactory.swift */, ); @@ -14031,6 +14037,7 @@ 32868DD51F31CADF0028B260 /* ChatsListProtocols.swift in Sources */, A49CC1D220E4A9C000879D41 /* InputBar+DisplayMode.swift in Sources */, A42D51B3206A361400EEB952 /* Room.swift in Sources */, + 85BDD2BA21467A9500695DE5 /* MessageFactoryProtocol.swift in Sources */, 4B06D31E2028A6D6003B275B /* MySelfItemsFactory.swift in Sources */, 26588E6720A20E49000D3E1A /* Customizable.swift in Sources */, 8541BD6B206CE3A40093EF1E /* ChatPlaceholderWheelItemModel.swift in Sources */, @@ -14167,6 +14174,7 @@ 854751492093BDD300F8D5F8 /* CollectionViewScrollProxy.swift in Sources */, 850C301B204DA87A00DB26C2 /* PrivacyListPresenter.swift in Sources */, 267BE28E1FDE9FCC00C47E18 /* SettingsGroupWireFrame.swift in Sources */, + 85BDD2B821465EFA00695DE5 /* ScrollDirection.swift in Sources */, A4679BA620B2DD0F0021FE9C /* SubscribersSelectorWireFrame.swift in Sources */, 2648C41A2069B52100863614 /* ChangeNumberViewLayout.swift in Sources */, 8514D52420EE48A30002378A /* NynjaContextMenuItemsFactory+Messages.swift in Sources */, @@ -15181,7 +15189,7 @@ 8520040720D4F436007C0036 /* StickerPreviewConfig.swift in Sources */, F1607B1D20B20F7800BDF60A /* GridView.swift in Sources */, F11786F320AC7A6E007A9A1B /* VideoPreviewView.swift in Sources */, - 26534B25210B4BE70003B9BC /* DBMessage+TypeExtension.swift in Sources */, + 26534B25210B4BE70003B9BC /* DBMessage+Extension.swift in Sources */, 2F7C7F7837BDE6F5767A3A8C /* GroupStorageViewController.swift in Sources */, 4B1D7E012029C4BE00703228 /* OptionsItemsFactory.swift in Sources */, C921738220BADAFC00519A2D /* TextInputValidationService.swift in Sources */, diff --git a/Nynja/ChatService/ChatService.swift b/Nynja/ChatService/ChatService.swift index 4e94426261ece518ddd86d343eea75037a6f6c87..479980e022c14d852321f3e09e33b62d01481a1d 100644 --- a/Nynja/ChatService/ChatService.swift +++ b/Nynja/ChatService/ChatService.swift @@ -150,9 +150,8 @@ final class ChatService { } static func removeMessage(_ message: Message, shouldUpdateChat: Bool = true) { - guard MessageDAO.removeMessage(using: message), - let id = message.link else { - return + guard MessageDAO.removeMessage(using: message), let id = message.linkedId else { + return } if let action = MessageActionDAO.fetchMessageAction(by: id) { @@ -185,7 +184,7 @@ final class ChatService { } ChatService.updateLastMessage(lastMessage, chat: chat, shouldChangeUnread: false) { (lastMessageId) -> Bool in - return message.link == lastMessageId + return message.linkedId == lastMessageId } } diff --git a/Nynja/ConversationsProvider.swift b/Nynja/ConversationsProvider.swift index b5acc08a0eccdad8f479bc4dbdb7ba26d9c49bc1..c7b497524e1a497f29ca4a9fb27e7d2265199551 100644 --- a/Nynja/ConversationsProvider.swift +++ b/Nynja/ConversationsProvider.swift @@ -64,8 +64,8 @@ class ConversationsProvider: ConversationsProviding { // MARK: - Comparator func comparator(lhs: ChatModel, rhs: ChatModel) -> Bool { - let created1 = lhs.last_msg?.created as? Int64 ?? 0 - let created2 = rhs.last_msg?.created as? Int64 ?? 0 + let created1 = lhs.last_msg?.created ?? 0 + let created2 = rhs.last_msg?.created ?? 0 return created1 > created2 } diff --git a/Nynja/DB/Models/DBJobMessage.swift b/Nynja/DB/Models/DBJobMessage.swift index 558373a7c18fbbf1feb1fef209059846537bb77c..6787e6ceb7b1196775de0ff5bfd258abf33e0102 100644 --- a/Nynja/DB/Models/DBJobMessage.swift +++ b/Nynja/DB/Models/DBJobMessage.swift @@ -9,7 +9,7 @@ import Foundation import GRDBCipher -class DBJobMessage: Record, DBModelProtocol { +final class DBJobMessage: Record, DBModelProtocol { var id: Int64? var container: String? @@ -23,7 +23,7 @@ class DBJobMessage: Record, DBModelProtocol { var to: String? var created: Int64? var type: String? - var editMessage: Int64? + var link: Int64? var repliedBy: String? var mentioned: String? var status: String? @@ -43,9 +43,9 @@ class DBJobMessage: Record, DBModelProtocol { self.localId = message.msg_id self.from = message.from self.to = message.to - self.created = message.created as? Int64 + self.created = message.created self.type = message.types.joinedByCommaIfNotEmpty() - self.editMessage = message.link + self.link = message.linkedId self.status = message.statusString self.files = (message.files ?? []).compactMap { DBDesc(desc: $0, targetId: nil, targetType: .schedule) } @@ -79,7 +79,7 @@ class DBJobMessage: Record, DBModelProtocol { self.to = row[JobMessageTable.Column.to.title] self.created = row[JobMessageTable.Column.created.title] self.type = row[JobMessageTable.Column.type.title] - self.editMessage = row[JobMessageTable.Column.editMessage.title] + self.link = row[JobMessageTable.Column.editMessage.title] self.repliedBy = row[JobMessageTable.Column.repliedBy.title] self.mentioned = row[JobMessageTable.Column.mentioned.title] self.status = row[JobMessageTable.Column.status.title] @@ -100,7 +100,7 @@ class DBJobMessage: Record, DBModelProtocol { container[JobMessageTable.Column.to.title] = self.to container[JobMessageTable.Column.created.title] = self.created container[JobMessageTable.Column.type.title] = self.type - container[JobMessageTable.Column.editMessage.title] = self.editMessage + container[JobMessageTable.Column.editMessage.title] = self.link container[JobMessageTable.Column.repliedBy.title] = self.repliedBy container[JobMessageTable.Column.mentioned.title] = self.mentioned container[JobMessageTable.Column.status.title] = self.status diff --git a/Nynja/DB/Models/DBMessage.swift b/Nynja/DB/Models/DBMessage.swift index 7301113c6eb4171564c1c8acae4012936f19d99d..70f9b33b71198b3c312d5bc92259ffb299a17532 100644 --- a/Nynja/DB/Models/DBMessage.swift +++ b/Nynja/DB/Models/DBMessage.swift @@ -28,7 +28,9 @@ final class DBMessage: Record, DBModelProtocol { var repliedBy: String? var mentioned: String? var status: String? + var localStatus: String? var isTrusted: Bool? + var isVisible: Bool? var files: [DBDesc] = [] @@ -43,11 +45,13 @@ final class DBMessage: Record, DBModelProtocol { self.localId = message.msg_id self.from = message.from self.to = message.to - self.created = message.created as? Int64 + self.created = message.created self.type = message.types.joinedByCommaIfNotEmpty() - self.link = message.link + self.link = message.linkedId self.status = message.statusString + self.localStatus = message.localStatus self.isTrusted = message.isTrusted + self.isVisible = message.isVisible self.files = (message.files ?? []).compactMap { DBDesc(desc: $0, targetId: nil, targetType: .message) } @@ -87,7 +91,9 @@ final class DBMessage: Record, DBModelProtocol { self.repliedBy = row[MessageTable.Column.repliedBy.title] self.mentioned = row[MessageTable.Column.mentioned.title] self.status = row[MessageTable.Column.status.title] + self.localStatus = row[MessageTable.Column.localStatus.title] self.isTrusted = row[MessageTable.Column.trusted.title] + self.isVisible = row[MessageTable.Column.visible.title] super.init() } @@ -109,9 +115,13 @@ final class DBMessage: Record, DBModelProtocol { container[MessageTable.Column.repliedBy.title] = self.repliedBy container[MessageTable.Column.mentioned.title] = self.mentioned container[MessageTable.Column.status.title] = self.status + container[MessageTable.Column.localStatus.title] = self.localStatus if let isTrusted = isTrusted { container[MessageTable.Column.trusted.title] = isTrusted } + if let isVisible = isVisible { + container[MessageTable.Column.visible.title] = isVisible + } } override func didInsert(with rowID: Int64, for column: String?) { diff --git a/Nynja/DB/Models/DBStarMessage.swift b/Nynja/DB/Models/DBStarMessage.swift index c3143042d83327742d786a93e80a044f0359bbbb..2d29114add77238da55e5c2d6428c83f4382fa28 100644 --- a/Nynja/DB/Models/DBStarMessage.swift +++ b/Nynja/DB/Models/DBStarMessage.swift @@ -23,7 +23,7 @@ final class DBStarMessage: Record, DBModelProtocol { var to: String? var created: Int64? var type: String? - var editMessage: Int64? + var link: Int64? var repliedBy: String? var mentioned: String? var status: String? @@ -63,9 +63,9 @@ final class DBStarMessage: Record, DBModelProtocol { self.localId = message.msg_id self.from = message.from self.to = message.to - self.created = message.created as? Int64 + self.created = message.created self.type = message.types.joinedByCommaIfNotEmpty() - self.editMessage = message.link + self.link = message.linkedId self.status = message.statusString self.files = (message.files ?? []).compactMap { DBDesc(desc: $0, targetId: nil, targetType: .star) } @@ -101,7 +101,7 @@ final class DBStarMessage: Record, DBModelProtocol { self.to = row[StarMessageTable.Column.to.title] self.created = row[StarMessageTable.Column.created.title] self.type = row[StarMessageTable.Column.type.title] - self.editMessage = row[StarMessageTable.Column.editMessage.title] + self.link = row[StarMessageTable.Column.editMessage.title] self.repliedBy = row[StarMessageTable.Column.repliedBy.title] self.mentioned = row[StarMessageTable.Column.mentioned.title] self.status = row[StarMessageTable.Column.status.title] @@ -122,7 +122,7 @@ final class DBStarMessage: Record, DBModelProtocol { container[StarMessageTable.Column.to.title] = self.to container[StarMessageTable.Column.created.title] = self.created container[StarMessageTable.Column.type.title] = self.type - container[StarMessageTable.Column.editMessage.title] = self.editMessage + container[StarMessageTable.Column.editMessage.title] = self.link container[StarMessageTable.Column.repliedBy.title] = self.repliedBy container[StarMessageTable.Column.mentioned.title] = self.mentioned container[StarMessageTable.Column.status.title] = self.status diff --git a/Nynja/DB/Models/Extension/DBMessage+TypeExtension.swift b/Nynja/DB/Models/Extension/DBMessage+Extension.swift similarity index 69% rename from Nynja/DB/Models/Extension/DBMessage+TypeExtension.swift rename to Nynja/DB/Models/Extension/DBMessage+Extension.swift index d003ddb0d837853fa5607c6786ba16b2b8c74cc8..25d1b02266bd3f31bb53112203920225a8ce9b0e 100644 --- a/Nynja/DB/Models/Extension/DBMessage+TypeExtension.swift +++ b/Nynja/DB/Models/Extension/DBMessage+Extension.swift @@ -1,21 +1,13 @@ // -// DBMessage+TypeExtension.swift +// DBMessage+Extension.swift // Nynja // // Created by Andrey Reznik on 27.07.2018. // Copyright © 2018 TecSynt Solutions. All rights reserved. // - extension DBMessage { - var isInOwnChat: Bool { - let ownerId = StorageService.sharedInstance.phoneId - return from == ownerId && to == ownerId - } - - var isOwn: Bool { - return self.from == StorageService.sharedInstance.phoneId - } + // MARK: - Types var isForward: Bool { return type?.contains("forward") ?? false @@ -40,5 +32,24 @@ extension DBMessage { var isCursor: Bool { return type?.contains("cursor") ?? false } + + + // MARK: - Delivery Status + + var isDelivered: Bool { + return serverId != nil + } + + + // MARK: - Storage + // FIXME: must be removed from extension, because it shouldn't use implicit singleton dependency. + + var isInOwnChat: Bool { + let ownerId = StorageService.sharedInstance.phoneId + return from == ownerId && to == ownerId + } + + var isOwn: Bool { + return from == StorageService.sharedInstance.phoneId + } } - diff --git a/Nynja/DB/Tables/MessageTable.swift b/Nynja/DB/Tables/MessageTable.swift index c65e1fd6246c65e8758550f56620c465b80e0935..32ba1ad43c43ddb00f8cc575a44a52a595bd598f 100644 --- a/Nynja/DB/Tables/MessageTable.swift +++ b/Nynja/DB/Tables/MessageTable.swift @@ -32,7 +32,9 @@ final class MessageTable: Table { t.column(Column.repliedBy, .text) t.column(Column.mentioned, .text) t.column(Column.status, .text) + t.column(Column.localStatus, .text) t.column(Column.trusted, .boolean) + t.column(Column.visible, .boolean) } } } @@ -57,6 +59,8 @@ extension MessageTable { case repliedBy case mentioned case status + case localStatus case trusted + case visible } } diff --git a/Nynja/Extensions/Bundle+Keys.swift b/Nynja/Extensions/Bundle+Keys.swift index dcc1a320ab94514fc69e44f30efa8e3a875c9ba0..293ec8e6620552c5be1c4ff35d56fe86a775013a 100644 --- a/Nynja/Extensions/Bundle+Keys.swift +++ b/Nynja/Extensions/Bundle+Keys.swift @@ -33,7 +33,6 @@ extension Bundle { return object(forInfoDictionaryKey: "ServerURL") as! String } - var appGroupName: String { return object(forInfoDictionaryKey: "AppGroup") as! String } @@ -41,7 +40,6 @@ extension Bundle { var serverPort: UInt16 { let serverPort = object(forInfoDictionaryKey: "ServerPort") as! String return UInt16(serverPort)! - } var modelsVersion: Int { diff --git a/Nynja/Extensions/Models/Message/Message+DB.swift b/Nynja/Extensions/Models/Message/Message+DB.swift index 891a54a704eb55d3cacf0fbb2cbc919a77b1d951..183f73fc0ab6ce356bbd3d024ebd7b9406b1414c 100644 --- a/Nynja/Extensions/Models/Message/Message+DB.swift +++ b/Nynja/Extensions/Models/Message/Message+DB.swift @@ -20,14 +20,15 @@ extension Message { self.msg_id = message.localId self.from = message.from self.to = message.to - self.created = message.created as AnyObject? + self.created = message.created self.types = Set(message.type?.components(separatedBy: Constants.commaSeparator) ?? []) - self.link = message.link + self.linkedId = message.link 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) } @@ -39,6 +40,7 @@ extension Message { } self.isTrusted = message.isTrusted + self.isVisible = message.isVisible } convenience init(message: DBStarMessage) { @@ -51,9 +53,9 @@ extension Message { self.msg_id = message.localId self.from = message.from self.to = message.to - self.created = message.created as AnyObject? + self.created = message.created self.types = Set(message.type?.components(separatedBy: Constants.commaSeparator) ?? []) - self.link = message.editMessage + self.linkedId = message.link self.repliedby = message.repliedBy?.splitIntegerIdentifiers() self.mentioned = message.mentioned?.splitIntegerIdentifiers() @@ -86,9 +88,9 @@ extension Message { self.msg_id = message.localId self.from = message.from self.to = message.to - self.created = message.created as AnyObject? + self.created = message.created self.types = Set(message.type?.components(separatedBy: Constants.commaSeparator) ?? []) - self.link = message.editMessage + self.linkedId = message.link self.repliedby = message.repliedBy?.splitIntegerIdentifiers() self.mentioned = message.mentioned?.splitIntegerIdentifiers() diff --git a/Nynja/Extensions/Models/StarExtension.swift b/Nynja/Extensions/Models/StarExtension.swift index a0f63c181c95444668bfc8f79ddde694bbe6f248..95cbaf53a88b93451596b8e51355680792934dbf 100644 --- a/Nynja/Extensions/Models/StarExtension.swift +++ b/Nynja/Extensions/Models/StarExtension.swift @@ -101,7 +101,7 @@ extension Star { } var timestamp: Int64 { - guard let timestamp = message?.created as? Int64 else { + guard let timestamp = message?.created else { return 0 } return timestamp diff --git a/Nynja/Library/MessageFactory/MessageFactory.swift b/Nynja/Library/MessageFactory/MessageFactory.swift index ef4b71c75363dfe95fc7389b5482366250394256..bbf75a9762a2c18003d89ffe17ba8d18730478ce 100644 --- a/Nynja/Library/MessageFactory/MessageFactory.swift +++ b/Nynja/Library/MessageFactory/MessageFactory.swift @@ -9,57 +9,10 @@ import AVFoundation import CoreLocation -protocol MessageFactoryProtocol: class { - func makeTextMessage(inputText: InputTextMessage, contact: Contact?, room: Room?) -> Message - - func makeTextMessageEdited(message: Message, newInputText: InputTextMessage) -> Message - func makeTextMessageEdited(message: Message, action: DBMessageEditAction) -> Message - - func makeMediaMessage(media: Media, contact: Contact?, room: Room?) -> Message - func makeImageMessage(imageURL: URL, contact: Contact?, room: Room?) -> Message - func makeVideoMessage(videoURL: URL, contact: Contact?, room: Room?) -> Message - - func makeLocationMessage(coordinate: CLLocationCoordinate2D, contact: Contact?, room: Room?) -> Message - func makeLocationMessage(coordinate: String, contact: Contact?, room: Room?) -> Message - - func makePlaceMessage(place: Place, contact: Contact?, room: Room?) -> Message - func makeAudioMessage(withUrl url: URL, language: String?, contact: Contact?, room: Room?) -> Message - func makeContactMessage(sendingContact: Contact, contact: Contact?, room: Room?) -> Message - - func makeFileMessage(withUrl url: URL, contact: Contact?, room: Room?) -> Message - func makeStickerMessage(sticker: Sticker, contact: Contact?, room: Room?) -> Message - - func makeTranslatedMessage(message: Message, text: String, translatedText: String, lang: String) -> Message - func makeUntranslatedMessage(message: Message, translationId: String) -> Message - func autotranslationDesc(text: String, translatedText: String, lang: String) -> Desc - - func makeTranscribedMessage(message: Message, text: String, lang: String) -> Message - func makeUntranscribedMessage(message: Message, transcriptionId: String, translationId: String?) -> Message - - func makePaymentMessage(inputText: String, contact: Contact) -> Message - - func makeCallMessage(members: [String], room: Room?) -> Message - - func makeMessageForDelete(message: Message, seenBy: [AnyObject]) -> Message -} - -extension MessageFactoryProtocol { - func makeAudioMessage(withUrl url: URL, language: String? = nil, contact: Contact?, room: Room?) -> Message { - return makeAudioMessage(withUrl: url, - language: language, - contact: contact, - room: room) - } - - func makeUntranscribedMessage(message: Message, transcriptionId: String, translationId: String? = nil) -> Message { - return makeUntranscribedMessage(message: message, - transcriptionId: transcriptionId, - translationId: translationId) - } -} - final class MessageFactory: MessageFactoryProtocol, InitializeInjectable { + // MARK: - Dependencies + private let storageService: StorageService private let payloadBuilder: MessagePayloadBuilderInput @@ -68,6 +21,9 @@ final class MessageFactory: MessageFactoryProtocol, InitializeInjectable { let payloadBuilder: MessagePayloadBuilderInput } + + // MARK: - Init + init(dependencies: Dependencies) { storageService = dependencies.storageService payloadBuilder = dependencies.payloadBuilder @@ -322,7 +278,7 @@ final class MessageFactory: MessageFactoryProtocol, InitializeInjectable { private extension MessageFactory { func makeMessage(descs: [Desc], mentioned: [Int64]? = nil, phoneId: String?, contact: Contact?, room: Room?) -> Message { let message = Message(phoneId: phoneId, contact: contact, room: room) - message.created = Date.currentTimestamp as AnyObject + message.created = Date.currentTimestamp message.files = descs message.status = nil message.mentioned = mentioned as [AnyObject]? diff --git a/Nynja/Library/MessageFactory/MessageFactoryProtocol.swift b/Nynja/Library/MessageFactory/MessageFactoryProtocol.swift new file mode 100644 index 0000000000000000000000000000000000000000..5fde105e4cbc16d472d53835e7e7db9a9f0a5c41 --- /dev/null +++ b/Nynja/Library/MessageFactory/MessageFactoryProtocol.swift @@ -0,0 +1,59 @@ +// +// MessageFactoryProtocol.swift +// Nynja +// +// Created by Anton Poltoratskyi on 10.09.2018. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation +import CoreLocation + +protocol MessageFactoryProtocol: class { + func makeTextMessage(inputText: InputTextMessage, contact: Contact?, room: Room?) -> Message + + func makeTextMessageEdited(message: Message, newInputText: InputTextMessage) -> Message + func makeTextMessageEdited(message: Message, action: DBMessageEditAction) -> Message + + func makeMediaMessage(media: Media, contact: Contact?, room: Room?) -> Message + func makeImageMessage(imageURL: URL, contact: Contact?, room: Room?) -> Message + func makeVideoMessage(videoURL: URL, contact: Contact?, room: Room?) -> Message + + func makeLocationMessage(coordinate: CLLocationCoordinate2D, contact: Contact?, room: Room?) -> Message + func makeLocationMessage(coordinate: String, contact: Contact?, room: Room?) -> Message + + func makePlaceMessage(place: Place, contact: Contact?, room: Room?) -> Message + func makeAudioMessage(withUrl url: URL, language: String?, contact: Contact?, room: Room?) -> Message + func makeContactMessage(sendingContact: Contact, contact: Contact?, room: Room?) -> Message + + func makeFileMessage(withUrl url: URL, contact: Contact?, room: Room?) -> Message + func makeStickerMessage(sticker: Sticker, contact: Contact?, room: Room?) -> Message + + func makeTranslatedMessage(message: Message, text: String, translatedText: String, lang: String) -> Message + func makeUntranslatedMessage(message: Message, translationId: String) -> Message + func autotranslationDesc(text: String, translatedText: String, lang: String) -> Desc + + func makeTranscribedMessage(message: Message, text: String, lang: String) -> Message + func makeUntranscribedMessage(message: Message, transcriptionId: String, translationId: String?) -> Message + + func makePaymentMessage(inputText: String, contact: Contact) -> Message + + func makeCallMessage(members: [String], room: Room?) -> Message + + func makeMessageForDelete(message: Message, seenBy: [AnyObject]) -> Message +} + +extension MessageFactoryProtocol { + func makeAudioMessage(withUrl url: URL, language: String? = nil, contact: Contact?, room: Room?) -> Message { + return makeAudioMessage(withUrl: url, + language: language, + contact: contact, + room: room) + } + + func makeUntranscribedMessage(message: Message, transcriptionId: String, translationId: String? = nil) -> Message { + return makeUntranscribedMessage(message: message, + transcriptionId: transcriptionId, + translationId: translationId) + } +} diff --git a/Nynja/MQTTModels/MessageExtension+BERT.swift b/Nynja/MQTTModels/MessageExtension+BERT.swift index 970f0ec6467201463f0c3d1b762fd3deb52f9d00..b8fe8b296fdc2962252e30631ea2722c97e5e1c2 100644 --- a/Nynja/MQTTModels/MessageExtension+BERT.swift +++ b/Nynja/MQTTModels/MessageExtension+BERT.swift @@ -41,18 +41,23 @@ extension Message { _seenBy = BertList(fromElements: obj) } - var _feed: BertObject = BertNil() + let _feed: BertObject if let p2p = self.feed_id as? p2p { _feed = p2p.getBert() - } - if let muc = self.feed_id as? muc { + } else if let muc = self.feed_id as? muc { _feed = muc.getBert() + } else { + _feed = BertNil() } - var _created: BertObject = BertNil() - if let c = self.created as? Int64 { + + let _created: BertObject + if let c = self.created { _created = BertNumber(fromInt64: c) + } else { + _created = BertNil() } - var attachments: BertObject = BertNil() + + let attachments: BertObject if let descs = self.files, descs.count > 0 { var att = [BertObject]() for i in descs { @@ -60,25 +65,36 @@ extension Message { att.append(desc) } attachments = BertList(fromElements: att) + } else { + attachments = BertNil() } - var types: BertObject = BertNil() + let types: BertObject if !self.types.isEmpty { let arr = self.types.map { BertAtom(fromString: $0) } types = BertList(fromElements: arr) + } else { + types = BertNil() } - let link = Bert.getBin(self.link) + let link: BertObject + if let linkedId = self.linkedId { + link = Bert.getBin(linkedId) + } else { + link = BertNil() + } - var status: BertObject = BertNil() + let status: BertObject if let string = self.status as? String { status = BertAtom(fromString: string) } else if let atom = self.status as? StringAtom, let string = atom.string { status = BertAtom(fromString: string) + } else { + status = BertNil() } - var mentioned: BertObject - if let objects = (self.mentioned as? [Int64])?.compactMap({ Bert.getBin($0) as? BertNumber }) { + let mentioned: BertObject + if let objects = (self.mentioned as? [Int64])?.compactMap({ Bert.getBin($0) as? BertNumber }), !objects.isEmpty { mentioned = BertList(fromElements: objects) } else { mentioned = BertNil() diff --git a/Nynja/MessageDAO.swift b/Nynja/MessageDAO.swift index e96afa926d3a009d71cbea18218ba5cc2d0c2748..10990bf5df80214f6cf9de5da7c6797e906d39c6 100644 --- a/Nynja/MessageDAO.swift +++ b/Nynja/MessageDAO.swift @@ -8,7 +8,7 @@ import GRDBCipher -class MessageDAO: MessageDAOProtocol { +final class MessageDAO: MessageDAOProtocol { // MARK: - Save static func saveMessages(_ messages: [Message]) throws { @@ -130,9 +130,13 @@ class MessageDAO: MessageDAOProtocol { // MARK: - Cursor - static func dropFirst(for fetchType: FetchType, closure: (DBMessage) -> Void) throws { + static func dropFirst(for fetchType: FetchType, + orderedBy order: TableOrder, + orderColumn: MessageTable.Column, + closure: (DBMessage) -> Void) throws { + dbManager.fetch { db in - let cursor = try DBMessage.fetchCursor(db, fetchType: fetchType, orderedBy: .desc, orderColumn: .serverId).dropFirst() + let cursor = try DBMessage.fetchCursor(db, fetchType: fetchType, orderedBy: order, orderColumn: orderColumn).dropFirst() while let message = try cursor.next() { closure(message) @@ -194,9 +198,8 @@ class MessageDAO: MessageDAOProtocol { // MARK: - Remove static func removeMessage(using message: Message) -> Bool { - guard let id = message.link, - let deletedMessage = fetchMessage(serverId: id) else { - return false + guard let id = message.linkedId, let deletedMessage = fetchMessage(serverId: id) else { + return false } let shouldMarkMessageAsDelete = self.shouldMarkMessageAsDelete(message) diff --git a/Nynja/MessageDAOProtocol.swift b/Nynja/MessageDAOProtocol.swift index b703ed2ad3541e70dc040119c67ddc4e094e05de..784903d400b4c09a2532b01ff68b0ce64b4addb1 100644 --- a/Nynja/MessageDAOProtocol.swift +++ b/Nynja/MessageDAOProtocol.swift @@ -43,7 +43,10 @@ protocol MessageDAOProtocol: DAOProtocol { static func fetchMessages(_ type: FetchType, serverIds: Set) -> [Message] // MARK: - Cursor - static func dropFirst(for fetchType: FetchType, closure: (DBMessage) -> Void) throws + static func dropFirst(for fetchType: FetchType, + orderedBy order: TableOrder, + orderColumn: MessageTable.Column, + closure: (DBMessage) -> Void) throws // MARK: - Trusted diff --git a/Nynja/MigrationManager.swift b/Nynja/MigrationManager.swift index 643ebe9392408255158c0998100ba94062756fec..c26a35d7475b9cdbcaa897d5600ce97f514af656 100644 --- a/Nynja/MigrationManager.swift +++ b/Nynja/MigrationManager.swift @@ -24,6 +24,8 @@ enum Migration: Int, Describable { case primaryKeyMessageAction case addTrustedColumnForMessage case createStarActionTable + case addLocalStatusColumnForMessage + case addVisibleColumnForMessage static var allTitles: [String] = { var i = 0 @@ -242,18 +244,37 @@ final class MigrationManager { try MessageTable.alter(in: db) { t in t.add(column: column, .boolean) } - - let sql = """ - UPDATE \(MessageTable.name) - SET \(column) = ? - """ - + let sql = "UPDATE \(MessageTable.name) SET \(column) = ?" try db.execute(sql, arguments: [true]) } migrator.registerMigration(.createStarActionTable) { db in try StarActionTable.createIfNotExists(in: db) } + + migrator.registerMigration(.addLocalStatusColumnForMessage) { db in + let column = MessageTable.Column.localStatus.title + guard try !MessageTable.hasColumns([column], in: db) else { + return + } + try MessageTable.alter(in: db) { t in + t.add(column: column, .text) + } + } + + migrator.registerMigration(.addVisibleColumnForMessage) { db in + let column = MessageTable.Column.visible.title + guard try !MessageTable.hasColumns([column], in: db) else { + return + } + try MessageTable.alter(in: db) { t in + t.add(column: column, .boolean) + } + + // Make all replied messages 'visible', that was currently loaded in database. + let sql = "UPDATE \(MessageTable.name) SET \(column) = ?" + try db.execute(sql, arguments: [true]) + } } private func replaceFeatureKey(_ key: String, newKey: Room.FeatureKey, db: Database) throws { @@ -271,6 +292,7 @@ final class MigrationManager { // MARK: - DatabaseMigrator private extension DatabaseMigrator { + mutating func registerMigration(_ migration: Migration, migrate: @escaping (Database) throws -> Void) { registerMigration(migration.title, migrate: migrate) } diff --git a/Nynja/Modules/Message/Interactor/MessageInteractor+Fetch.swift b/Nynja/Modules/Message/Interactor/MessageInteractor+Fetch.swift index 8b0bcbfda920ccead526f3c40fedebeca4ad3633..d0369dd81d5e07841972a08bf79c23f8fa4995fc 100644 --- a/Nynja/Modules/Message/Interactor/MessageInteractor+Fetch.swift +++ b/Nynja/Modules/Message/Interactor/MessageInteractor+Fetch.swift @@ -159,14 +159,17 @@ extension MessageInteractor { } private func fetchRepliedModels(_ type: FetchType) -> ChatConfiguration.RepliedModels { - let serverIds = configuration.messages.compactMap { $0.isReply ? $0.link : nil } + let serverIds = configuration.messages.compactMap { $0.isReply ? $0.linkedId : nil } let repliedMessages = MessageDAO.fetchMessages(type, serverIds: Set(serverIds)) var repliedModels = ChatConfiguration.RepliedModels() repliedMessages.forEach { message in - if let serverId = message.id, let sender = sender(for: message) { + if let serverId = message.id { + let sender = self.sender(for: message) let mentions = payloadParser.parse(message) - repliedModels[serverId] = RepliedMessageModel(message: message, author: sender.nick ?? sender.fullname, mentions: mentions) + repliedModels[serverId] = RepliedMessageModel(message: message, + author: sender.nick ?? sender.fullname, + mentions: mentions) } } diff --git a/Nynja/Modules/Message/Interactor/MessageInteractor+History.swift b/Nynja/Modules/Message/Interactor/MessageInteractor+History.swift index 7ce3c9129151220aceef2ad50dcb81511854a5ed..432c30863dd75cb80d00e4796a2e14b65ebbb546 100644 --- a/Nynja/Modules/Message/Interactor/MessageInteractor+History.swift +++ b/Nynja/Modules/Message/Interactor/MessageInteractor+History.swift @@ -10,12 +10,18 @@ import Foundation extension MessageInteractor { + enum HistoryCheckResult { + case valid + case updateRequired + case broken(MessageHistoryGaps) + } + struct MessageHistoryGaps { - typealias Range = CountableClosedRange + fileprivate typealias Range = CountableClosedRange private let ranges: [Range]? - init(ranges: [Range]) { + fileprivate init(ranges: [Range]) { self.ranges = ranges } @@ -32,57 +38,48 @@ extension MessageInteractor { } } - enum HistoryCheckResult { - case validSequence - case updateRequired - case gaps(MessageHistoryGaps) - } - func checkLocalHistory() -> HistoryCheckResult { guard chat.last_msg?.statusString != "update" else { return .updateRequired } guard let gaps = fetchAllHistoryGaps(), !gaps.isEmpty else { - return .validSequence + return .valid } - return .gaps(gaps) + return .broken(gaps) } private func fetchAllHistoryGaps() -> MessageHistoryGaps? { - guard let fetchType = fetchType else { return nil } - + guard let fetchType = fetchType, let lastMessage = chat.last_msg else { + return nil + } + return fetchAllHistoryGaps(in: fetchType, lastMessage: lastMessage) + } + + private func fetchAllHistoryGaps(in fetchType: FetchType, lastMessage: Message) -> MessageHistoryGaps { var gaps = [MessageHistoryGaps.Range]() + var previousMessage = DBMessage(message: lastMessage) - var endMessageId: MessageServerId? // Latest message id - var repliedMessageId: MessageServerId? // Oldest replied message id + let isValidChain: (DBMessage) -> Bool = { + previousMessage?.next == $0.serverId || previousMessage?.isTrusted == true + } + + // Latest message id + var endMessageId: MessageServerId? - let appendGapIfExists = { (message: DBMessage) in + let appendGapIfExists: (DBMessage) -> Void = { message in if let end = endMessageId, let start = message.serverId, start <= end { gaps.append(start...end) } endMessageId = nil } - var previousMessage = chat.last_msg.flatMap { DBMessage(message: $0) } - - let isValidChain = { (message: DBMessage) -> Bool in - return previousMessage?.next == message.serverId || previousMessage?.isTrusted == true - } - // Iterate over all messages that isn't filtered by status. // Ignore first message, because it's equal to chat's last message. - try? MessageDAO.dropFirst(for: fetchType) { message in - guard let id = message.serverId else { return } + try? MessageDAO.dropFirst(for: fetchType, orderedBy: .desc, orderColumn: .serverId) { message in + guard message.isDelivered else { return } defer { previousMessage = message } - - if id == repliedMessageId { - repliedMessageId = nil - - } else if message.isReply, let link = message.link { - repliedMessageId = repliedMessageId.flatMap { min($0, link) } ?? link - } - + if isValidChain(message) { appendGapIfExists(message) @@ -93,15 +90,7 @@ extension MessageInteractor { } previousMessage.map { appendGapIfExists($0) } - /* - if let repliedMessageId = repliedMessageId, - let start = gaps.last?.lowerBound, - let end = gaps.first?.upperBound, - !(start...end).contains(repliedMessageId) { - - gaps.append(repliedMessageId...start) - }*/ - + return MessageHistoryGaps(ranges: gaps) } } diff --git a/Nynja/Modules/Message/Interactor/MessageInteractor+StorageSubscriber.swift b/Nynja/Modules/Message/Interactor/MessageInteractor+StorageSubscriber.swift index c2790c3cf2da9ce0efa374f7e3570ebb92d75d78..c69dede2e796b1ced729498c3a2e8a5df90ffb0c 100644 --- a/Nynja/Modules/Message/Interactor/MessageInteractor+StorageSubscriber.swift +++ b/Nynja/Modules/Message/Interactor/MessageInteractor+StorageSubscriber.swift @@ -258,10 +258,15 @@ extension MessageInteractor { func createMessageConfiguration(_ message: Message) -> MessageConfiguration { var repliedModel: RepliedMessageModel? - if message.isReply { - if let serverId = message.link, let repliedMessage = messageBy(serverId: serverId), let sender = sender(for: repliedMessage) { + if message.isReply, let serverId = message.linkedId { + let repliedMessage = messageBy(serverId: serverId) ?? MessageDAO.fetchMessage(serverId: serverId) + + if let repliedMessage = repliedMessage { + let sender = self.sender(for: repliedMessage) let mentions = payloadParser.parse(repliedMessage) - repliedModel = RepliedMessageModel(message: repliedMessage, author: sender.nick ?? sender.fullname, mentions: mentions) + repliedModel = RepliedMessageModel(message: repliedMessage, + author: sender.nick ?? sender.fullname, + mentions: mentions) configuration.repliedModels[serverId] = repliedModel } } diff --git a/Nynja/Modules/Message/Interactor/MessageInteractor.swift b/Nynja/Modules/Message/Interactor/MessageInteractor.swift index 14c417fdc8290229e942c8578993facc8ee9708c..a80750827b1a1fe61b5c6ca85d9b2f97b9a4ce17 100644 --- a/Nynja/Modules/Message/Interactor/MessageInteractor.swift +++ b/Nynja/Modules/Message/Interactor/MessageInteractor.swift @@ -342,10 +342,10 @@ final class MessageInteractor: BaseInteractor, MessageInteractorInputProtocol, H private func fetchData() { fetchChatModel() switch checkLocalHistory() { - case .updateRequired, .validSequence: + case .updateRequired, .valid: fetchMessages() - case let .gaps(gaps): + case let .broken(gaps): processingQueue.async { [weak self] in self?.fetchFromStorage() } @@ -377,9 +377,10 @@ final class MessageInteractor: BaseInteractor, MessageInteractorInputProtocol, H // MARK: - Sender - func sender(for message: Message) -> MessageSender? { + func sender(for message: Message) -> MessageSender { let isOwner = message.from == myContact?.phone_id - if let _ = (message.feed_id as? muc), let from = message.from, let member = member(for: from) { + + if message.feed_id is muc, let from = message.from, let member = member(for: from) { return messageSender(from: member) } else if let contact = self.contact { if isOwner, let myContact = myContact { @@ -530,7 +531,7 @@ final class MessageInteractor: BaseInteractor, MessageInteractorInputProtocol, H } private func compare(_ left: Message?, with right: Message?) -> Bool { - return Date(timestamp: (left?.created as? Int64) ?? 0) > Date(timestamp: (right?.created as? Int64) ?? 0) + return Date(timestamp: left?.created ?? 0) > Date(timestamp: right?.created ?? 0) } private func flatMap(desc: Desc?) -> LocationType? { @@ -558,7 +559,7 @@ final class MessageInteractor: BaseInteractor, MessageInteractorInputProtocol, H urls[mainURL] = message } return urls.values - .sorted { Date(timestamp: ($0.created as? Int64) ?? 0) < Date(timestamp: ($1.created as? Int64) ?? 0) } + .sorted { Date(timestamp: $0.created ?? 0) < Date(timestamp: $1.created ?? 0) } .compactMap { guard let mainFile = $0.mainFile, let thumbnailFile = $0.thumb, let mainURL = mainFile.url, let thumbnailURL = thumbnailFile.url else { @@ -626,9 +627,6 @@ final class MessageInteractor: BaseInteractor, MessageInteractorInputProtocol, H if let url = message.mainUrl { configuration.progressModels.removeValue(forKey: url) } - if let serverId = message.link { - configuration.repliedModels.removeValue(forKey: serverId) - } presenter?.removeMessage(id) } } @@ -710,7 +708,7 @@ final class MessageInteractor: BaseInteractor, MessageInteractorInputProtocol, H } let message = Message(message: msg) - message.created = Date.currentTimestamp as AnyObject + message.created = Date.currentTimestamp let starLocalId = IdBuilder(format: .starClientId) .addValueForComponent(messageServerId, .key) @@ -858,7 +856,7 @@ final class MessageInteractor: BaseInteractor, MessageInteractorInputProtocol, H } func processForwardMessageTap(serverId: MessageServerId) { - guard let link = MessageDAO.fetchMessage(serverId: serverId)?.link, + guard let link = MessageDAO.fetchMessage(serverId: serverId)?.linkedId, let linkedMessage = MessageDAO.fetchMessage(serverId: link), let localId = linkedMessage.msg_id else { return @@ -902,30 +900,29 @@ final class MessageInteractor: BaseInteractor, MessageInteractorInputProtocol, H } // MARK: - HistoryHandlerDelegate - var isNew = false + private var isNew = false private var isHistoryUpdating: Bool = false func getHistorySuccess() { guard !isHistoryUpdating else { isHistoryUpdating = false - isNew = false + processingQueue.async { + self.isNew = false + } fetchData() return } - if isNew { - processingQueue.async { [weak self] in - guard let `self` = self else { return } + processingQueue.async { [weak self] in + guard let `self` = self else { return } + + if self.isNew { self.fetchNewFromStorage() - self.autoTranslateReceiptMessagesIfNeeded() - } - } else { - isNew = true - processingQueue.async { [weak self] in - guard let `self` = self else { return } + } else { + self.isNew = true self.fetchFromStorage() - self.autoTranslateReceiptMessagesIfNeeded() } + self.autoTranslateReceiptMessagesIfNeeded() } } diff --git a/Nynja/Modules/Message/Presenter/MessagePresenter.swift b/Nynja/Modules/Message/Presenter/MessagePresenter.swift index cbebb3d8c75e31ca5f343a7580a48b250a1d2f54..2e80d93bcf05f6110d7721e9d896d435aac4f667 100644 --- a/Nynja/Modules/Message/Presenter/MessagePresenter.swift +++ b/Nynja/Modules/Message/Presenter/MessagePresenter.swift @@ -572,7 +572,7 @@ class MessagePresenter: BasePresenter, MessagePresenterProtocol, MessageInteract } var repliedModel: RepliedMessageModel? - if message.isReply, let serverId = message.link { + if message.isReply, let serverId = message.linkedId { repliedModel = configuration.repliedModels[serverId] } @@ -641,16 +641,18 @@ class MessagePresenter: BasePresenter, MessagePresenterProtocol, MessageInteract } var repliedModel: RepliedMessageModel? - if message.isReply, let serverId = message.link { + if message.isReply, let serverId = message.linkedId { repliedModel = configuration.repliedModels[serverId] } if let systemMessage = message.systemMessage(for: interactor.chat) { cells.append(systemModel(message: systemMessage, mod: message)) + } else if let localId = message.msg_id, var model = self.getCellModel(message: message, repliedModel: repliedModel, readerID: configuration.reader, + links: configuration.links[localId], mentions: configuration.mentions[localId], translation: configuration.translations[localId], transcription: configuration.transcriptions[localId], diff --git a/Nynja/Modules/Message/Protocols/MessageProtocols.swift b/Nynja/Modules/Message/Protocols/MessageProtocols.swift index 104ea6625a291afc1a97d91e0df9da0ef1d4eb22..5bf2900b98df43d074dbfc882a8885884e579025 100644 --- a/Nynja/Modules/Message/Protocols/MessageProtocols.swift +++ b/Nynja/Modules/Message/Protocols/MessageProtocols.swift @@ -238,7 +238,7 @@ protocol MessageInteractorInputProtocol: BaseInteractorProtocol, MentionFetchInp func member(for phoneId: String) -> Member? - func sender(for message: Message) -> MessageSender? + func sender(for message: Message) -> MessageSender func askForInternetStatus() diff --git a/Nynja/Modules/Message/View/MessageVC+CellDelegate.swift b/Nynja/Modules/Message/View/MessageVC+CellDelegate.swift index bbad76598c73d8e6302c7913c741b201a4ba9a95..71cf1415e5139b8cf7a59862d941066efc8ec739 100644 --- a/Nynja/Modules/Message/View/MessageVC+CellDelegate.swift +++ b/Nynja/Modules/Message/View/MessageVC+CellDelegate.swift @@ -8,9 +8,12 @@ import Foundation -extension MessageVC: VoiceAudioInteractive, BaseChatCellDelegate, AudioManagerDelegate, ProximitySensorManagerDelegate { +extension MessageVC: VoiceAudioInteractive, AudioManagerDelegate, ProximitySensorManagerDelegate { } + +// MARK: - BaseChatCellDelegate + +extension MessageVC: BaseChatCellDelegate { - // MARK: - BaseChatCellDelegate func didCellTapped(_ cell: BaseChatCell) { guard let model = cell.model, let type = model.type else { return diff --git a/Nynja/Modules/Message/View/Views/CollectionView/MessageCollectionViewDataSource.swift b/Nynja/Modules/Message/View/Views/CollectionView/MessageCollectionViewDataSource.swift index e07023d4769af25d652b87fcf2b11e0a3c2e7472..2d6b1e91ee9865c00995fcf7fc70f14bb035ae35 100644 --- a/Nynja/Modules/Message/View/Views/CollectionView/MessageCollectionViewDataSource.swift +++ b/Nynja/Modules/Message/View/Views/CollectionView/MessageCollectionViewDataSource.swift @@ -191,7 +191,7 @@ extension MessageCollectionViewDataSource: UICollectionViewDataSource { } } - if model.messageType == .system { + if case .system = model.messageType { if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "system", for: indexPath) as? SystemCell { cell.setup(message: model.text!) cell.accessibilityIdentifier = "system_cell_unread_\(indexPath.section)" diff --git a/Nynja/Modules/Message/View/Views/CollectionView/MessageCollectionViewDelegate.swift b/Nynja/Modules/Message/View/Views/CollectionView/MessageCollectionViewDelegate.swift index 422da5f56e61a485e8aad516fe8ca072b8c354ee..1c2075eaa6963b572840dd1301a2248bf2da7b3e 100644 --- a/Nynja/Modules/Message/View/Views/CollectionView/MessageCollectionViewDelegate.swift +++ b/Nynja/Modules/Message/View/Views/CollectionView/MessageCollectionViewDelegate.swift @@ -8,13 +8,6 @@ import UIKit -enum ScrollDirection { - case top - case bottom -} - - - final class MessageCollectionViewDelegate: NSObject, UICollectionViewDelegateFlowLayout { unowned var view: MessageVC @@ -46,11 +39,11 @@ final class MessageCollectionViewDelegate: NSObject, UICollectionViewDelegateFlo private func heightForItem(at indexPath: IndexPath) -> CGFloat { let model = view.messageDS.cellModel(at: indexPath) - if model.messageType == .system { + if case .system = model.messageType { return 40 } - if let _ = model.type { + if model.type != nil { return height(for: model) } diff --git a/Nynja/Modules/Message/View/Views/CollectionView/ScrollDirection.swift b/Nynja/Modules/Message/View/Views/CollectionView/ScrollDirection.swift new file mode 100644 index 0000000000000000000000000000000000000000..f077dc5c2c18d9f4bf58711e8cd87365a0d84b42 --- /dev/null +++ b/Nynja/Modules/Message/View/Views/CollectionView/ScrollDirection.swift @@ -0,0 +1,12 @@ +// +// ScrollDirection.swift +// Nynja +// +// Created by Anton Poltoratskyi on 10.09.2018. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +enum ScrollDirection { + case top + case bottom +} diff --git a/Nynja/Modules/Message/View/Views/TableView/Cells/ChatCells/BaseChatCell/BaseChatCell.swift b/Nynja/Modules/Message/View/Views/TableView/Cells/ChatCells/BaseChatCell/BaseChatCell.swift index 54b6ed8be81e469554eaba0d87109f0c41eef7f6..fbbe8e6c7944522c0c4e3c8df8e153d79c9a35d8 100644 --- a/Nynja/Modules/Message/View/Views/TableView/Cells/ChatCells/BaseChatCell/BaseChatCell.swift +++ b/Nynja/Modules/Message/View/Views/TableView/Cells/ChatCells/BaseChatCell/BaseChatCell.swift @@ -502,7 +502,9 @@ class BaseChatCell: UICollectionViewCell, MessageBaseImageViewDataSource, Messag } static func shouldShowSender(_ model: BaseChatCellModel) -> Bool { - guard model.messageType != .system else { return false } + if case .system = model.messageType { + return false + } switch model.infoType { case .date: diff --git a/Nynja/Modules/Message/View/Views/TableView/Cells/Models/BaseChatCellModel.swift b/Nynja/Modules/Message/View/Views/TableView/Cells/Models/BaseChatCellModel.swift index 54200782162943f57f4c36d9fed1e3ab8233f357..31742b76b139a9457177d1797edfdc21db5815e7 100644 --- a/Nynja/Modules/Message/View/Views/TableView/Cells/Models/BaseChatCellModel.swift +++ b/Nynja/Modules/Message/View/Views/TableView/Cells/Models/BaseChatCellModel.swift @@ -28,38 +28,13 @@ enum PlayStatus { case stop } -enum MessageType: Hashable { +enum MessageType { case regular case forward indirect case reply(RepliedMessageModel, MessageType) indirect case convertion(ConvertionMessageModel, MessageType) case system - var hashValue: Int { - get { - return self.getHash() - } - } - - static func ==(lhs: MessageType, rhs: MessageType) -> Bool { - return lhs.hashValue == rhs.hashValue - } - - private func getHash() -> Int { - switch self { - case .regular: - return "regular".hashValue - case .reply(let model, _): - return ("reply" + model.id).hashValue - case .system: - return "system".hashValue - case .forward: - return "forward".hashValue - case .convertion: - return "convertion".hashValue - } - } - var ejectReply: MessageType? { switch self { case .reply: @@ -145,7 +120,7 @@ final class BaseChatCellModel { var serverID: Int64? - var id: String? + var id: MessageLocalId? var type: SendMessageType! var isOwner: Bool = true var timeStamp: Date? diff --git a/Nynja/Modules/Message/View/Views/TableView/Cells/Models/RepliedMessageModel.swift b/Nynja/Modules/Message/View/Views/TableView/Cells/Models/RepliedMessageModel.swift index 081e65bf8b834733d4974aae0961043964355e7c..77bfcee3b9d41c4adffb20ca374bb23a49988dfd 100644 --- a/Nynja/Modules/Message/View/Views/TableView/Cells/Models/RepliedMessageModel.swift +++ b/Nynja/Modules/Message/View/Views/TableView/Cells/Models/RepliedMessageModel.swift @@ -11,7 +11,7 @@ import Foundation final class RepliedMessageModel { /// local id of replied message - var id: String + var id: MessageLocalId var type: SendMessageType var author: String var text: String @@ -42,7 +42,7 @@ final class RepliedMessageModel { self.text = fileName } - self.date = Date(timestamp: (message.created as? Int64) ?? 0) + self.date = Date(timestamp: message.created ?? 0) self.mentions = mentions } diff --git a/Nynja/Modules/Replies/Presenter/RepliesPresenter.swift b/Nynja/Modules/Replies/Presenter/RepliesPresenter.swift index 5d04e312b9d1cbe8ce204d7891a2df19d1a0a0d2..a15b38b4b0b27e52ade1f22f0f0f536938a356d0 100644 --- a/Nynja/Modules/Replies/Presenter/RepliesPresenter.swift +++ b/Nynja/Modules/Replies/Presenter/RepliesPresenter.swift @@ -88,7 +88,7 @@ class RepliesPresenter: BasePresenter, RepliesPresenterProtocol, RepliesInteract model.isOwner = message.from == StorageService.sharedInstance.phoneId model.replied = message.repliedby?.count ?? 0 - model.timeStamp = Date(timestamp: (message.created as? Int64) ?? 0) + model.timeStamp = Date(timestamp: message.created ?? 0) let size = attach.size ?? 0 model.currentSize = size diff --git a/Nynja/Modules/ScheduleMessage/Interactor/ScheduleMessageInteractor.swift b/Nynja/Modules/ScheduleMessage/Interactor/ScheduleMessageInteractor.swift index 4b682e480d798f2ea3294b38654405ea63769a7d..969c88e3dc210438953aa5c63063f7c0a9720efb 100644 --- a/Nynja/Modules/ScheduleMessage/Interactor/ScheduleMessageInteractor.swift +++ b/Nynja/Modules/ScheduleMessage/Interactor/ScheduleMessageInteractor.swift @@ -58,7 +58,7 @@ class ScheduleMessageInteractor: BaseInteractor, ScheduleMessageInteractorInputP guard let phoneId = StorageService.sharedInstance.phoneId, let info = self.info, let (features, timestamp) = prepareDateTimeInfo(with: timeZone, date: date) else { return } let message = info.message - message.created = timestamp as AnyObject + message.created = timestamp let messages = info.targets.messages(from: message, phoneId: phoneId) sentMessagesLocalIds = messages.compactMap { $0.msg_id } diff --git a/Nynja/Resources/DevConfig.xcconfig b/Nynja/Resources/DevConfig.xcconfig index be91b0379fba8f4bd6ea0d98541d4551acb992a2..fccdcaf03f8bfe8377819004149d2be1ab25674d 100644 --- a/Nynja/Resources/DevConfig.xcconfig +++ b/Nynja/Resources/DevConfig.xcconfig @@ -14,6 +14,6 @@ AppName = NYNJADev ServerPort = 1883 Config = dev AppGroup = group.com.nynja.mobile.communicator.dev -ModelsVersion = 8 +ModelsVersion = 9 ConfServerAddress = 35.198.118.190 ConfServerPort = 80 diff --git a/Nynja/ServerModel/Model/Message.swift b/Nynja/ServerModel/Model/Message.swift index c6d66da659696466536a9d00933b94a80c1d6707..156fa575f4935977694688635f558f027b3cf243 100644 --- a/Nynja/ServerModel/Model/Message.swift +++ b/Nynja/ServerModel/Model/Message.swift @@ -8,22 +8,31 @@ class Message { var msg_id: String? var from: String? var to: String? - var created: AnyObject? + var created: Int64? var files: [Desc]? var type: [AnyObject]? - var link: Int64? + var link: AnyObject? var seenby: [AnyObject]? var repliedby: [AnyObject]? var mentioned: [AnyObject]? var status: AnyObject? + + // MARK: - Local Fields + var feedName: String? var senderName: String? var senderAvatar: String? + var localStatus: String? + /// Property is needed in order to handle gaps in message history. /// Property 'isTrusted = true' when sequence of [message BEFORE (serverId = 1), self message (serverId = 2)] /// form a valid history chain inside a p2p of muc, /// so we could ignore some of history gaps based on 'prev' and 'next' fields. var isTrusted: Bool? + + /// Property is needed in order to hide replied message from visible history that will be displayed, + /// until replied message will be received with other chat history. + var isVisible: Bool? } diff --git a/Nynja/ServerModel/Source/Decoder.swift b/Nynja/ServerModel/Source/Decoder.swift index 58d3811347ae04864c4bfe751c1255e19d3f552d..bf56d8420ce0a3f68d19824de005e4e5cbe23e6e 100644 --- a/Nynja/ServerModel/Source/Decoder.swift +++ b/Nynja/ServerModel/Source/Decoder.swift @@ -233,10 +233,10 @@ func parseObject(name: String, body:[Model], tuple: BertTuple) -> AnyObject? a_Message.msg_id = body[5].parse(bert: tuple.elements[6]) as? String a_Message.from = body[6].parse(bert: tuple.elements[7]) as? String a_Message.to = body[7].parse(bert: tuple.elements[8]) as? String - a_Message.created = body[8].parse(bert: tuple.elements[9]) as? AnyObject + a_Message.created = body[8].parse(bert: tuple.elements[9]) as? Int64 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? Int64 + 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] diff --git a/Nynja/ServerModel/Spec/Message_Spec.swift b/Nynja/ServerModel/Spec/Message_Spec.swift index 59a1eea2461aadc6c14675f466fa4ce7350925a4..3d1ac4148faf92b3f0f3fe23b0eca78c3cabeca2 100644 --- a/Nynja/ServerModel/Spec/Message_Spec.swift +++ b/Nynja/ServerModel/Spec/Message_Spec.swift @@ -4,7 +4,6 @@ func get_Message() -> Model { Model(value:List(constant:"")), Model(value:Number())])), Model(value:Chain(types:[ - Model(value:Atom()), Model(value:Atom(constant:"chain")), Model(value:Atom(constant:"cur"))])), Model(value:Chain(types:[ @@ -27,40 +26,88 @@ func get_Message() -> Model { Model(value:Binary())])), Model(value:Chain(types:[ Model(value:List(constant:"")), - Model(value:Number()), - Model(value:Binary())])), - Model(value:Chain(types:[ - Model(value:List(constant:"")), - Model(value:List(constant:nil,model:get_Desc()))])), + Model(value:Number())])), + Model(value:List(constant:nil,model:get_Desc())), Model(value:Chain(types:[ Model(value:List(constant:"")), Model(value:List(constant:nil, model:Model(value:Chain(types:[ - Model(value:Atom()), Model(value:Atom(constant:"sys")), Model(value:Atom(constant:"reply")), Model(value:Atom(constant:"forward")), - Model(value:Atom(constant:"sched")), Model(value:Atom(constant:"read")), Model(value:Atom(constant:"edited")), Model(value:Atom(constant:"cursor"))]))))])), + Model(value:Chain(types:[ + Model(value:List(constant:"")), + Model(value:Number()), + getNotRecursiveMessage()])), + 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:Chain(types:[ + Model(value:List(constant:"")), + Model(value:Atom(constant:"async")), + Model(value:Atom(constant:"delete")), + Model(value:Atom(constant:"clear")), + Model(value:Atom(constant:"update")), + Model(value:Atom(constant:"edit"))]))])) +} + +// FIXME: need to think about recursive relations. +private func getNotRecursiveMessage() -> Model { + return Model(value:Tuple(name:"Message",body:[ Model(value:Chain(types:[ Model(value:List(constant:"")), Model(value:Number())])), + Model(value:Chain(types:[ + Model(value:Atom(constant:"chain")), + Model(value:Atom(constant:"cur"))])), + Model(value:Chain(types:[ + get_muc(), + get_p2p()])), Model(value:Chain(types:[ Model(value:List(constant:"")), - Model(value:List(constant:nil, model:Model(value:Chain(types:[ - Model(value:Binary()), - Model(value:Number())]))))])), + Model(value:Number())])), Model(value:Chain(types:[ Model(value:List(constant:"")), - Model(value:List(constant:nil, model:Model(value:Number())))])), + Model(value:Number())])), + Model(value:Chain(types:[ + Model(value:List(constant:"")), + Model(value:Binary())])), + Model(value:Chain(types:[ + Model(value:List(constant:"")), + Model(value:Binary())])), + Model(value:Chain(types:[ + Model(value:List(constant:"")), + Model(value:Binary())])), Model(value:Chain(types:[ Model(value:List(constant:"")), - Model(value:List(constant:nil, model:Model(value:Number())))])), + Model(value:Number())])), + Model(value:List(constant:nil,model:get_Desc())), + Model(value:Chain(types:[ + Model(value:List(constant:"")), + Model(value:List(constant:nil, model:Model(value:Chain(types:[ + Model(value:Atom(constant:"sys")), + Model(value:Atom(constant:"reply")), + Model(value:Atom(constant:"forward")), + Model(value:Atom(constant:"read")), + Model(value:Atom(constant:"edited")), + Model(value:Atom(constant:"cursor"))]))))])), + Model(value:Chain(types:[ + Model(value:List(constant:"")), + Model(value:Number())])), + 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:Chain(types:[ Model(value:List(constant:"")), Model(value:Atom(constant:"async")), Model(value:Atom(constant:"delete")), Model(value:Atom(constant:"clear")), Model(value:Atom(constant:"update")), - Model(value:Atom(constant:"edit"))]))]))} + Model(value:Atom(constant:"edit"))]))])) +} diff --git a/Nynja/Services/HandleServices/HistoryHandler.swift b/Nynja/Services/HandleServices/HistoryHandler.swift index 03ac100d1cb5dedfa61ca1b735cdbd026f561541..07850011dc8565f8ea3aa58aa4adbcb381dbbc08 100644 --- a/Nynja/Services/HandleServices/HistoryHandler.swift +++ b/Nynja/Services/HandleServices/HistoryHandler.swift @@ -74,7 +74,7 @@ final class HistoryHandler: BaseHandler { /// The first message is new, the last is old. private static func updateMessageHistory(_ messages: [Message]) { - var stackForSave = [Message]() + var stackForSave = [Message](reserveCapacity: messages.count) var stackForDelete = [Message]() var deletedActions = [DBMessageAction]() var systemClearMessage: Message? @@ -85,7 +85,7 @@ final class HistoryHandler: BaseHandler { message.isTrusted = true if message.isStatusDelete { - if let id = message.link, let action = MessageActionDAO.fetchMessageAction(by: id) { + if let id = message.linkedId, let action = MessageActionDAO.fetchMessageAction(by: id) { deletedActions.append(action) } if !isHistoryCleared { @@ -104,6 +104,9 @@ final class HistoryHandler: BaseHandler { } else if !isHistoryCleared { stackForSave.append(message) + if let repliedMessage = message.repliedMessage { + stackForSave.append(repliedMessage) + } } case .skip: // Ignore received message if we have local 'edit' action for it. @@ -119,6 +122,9 @@ final class HistoryHandler: BaseHandler { ChatService.clearMessages(before: systemClearMessage) } + // Save new messages after old messages + stackForSave.reverse() + try? MessageDAO.saveMessages(stackForSave) ChatService.removeMessages(stackForDelete) try? MessageActionDAO.delete(deletedActions) diff --git a/Nynja/Services/HandleServices/MessageHandler.swift b/Nynja/Services/HandleServices/MessageHandler.swift index 2b20d6385a7d1500ac311352859c7fc56ec323dd..a4d4415a4950db2fd4b3b0edafc7d6418bf52d84 100644 --- a/Nynja/Services/HandleServices/MessageHandler.swift +++ b/Nynja/Services/HandleServices/MessageHandler.swift @@ -70,7 +70,7 @@ final class MessageHandler: BaseHandler { private static func editMessage(_ message: Message) { try? saveIntoDatabase(message: message) - guard let link = message.link else { + guard let link = message.linkedId else { return } diff --git a/Nynja/Services/MessageSendingService/MessageSendingService.swift b/Nynja/Services/MessageSendingService/MessageSendingService.swift index 3856ab1294b12bf0e5d9b06e472492cf6c093ab0..cc4e311f2852f953c56aa2f9d7541d0ec442932b 100644 --- a/Nynja/Services/MessageSendingService/MessageSendingService.swift +++ b/Nynja/Services/MessageSendingService/MessageSendingService.swift @@ -50,7 +50,7 @@ final class MessageSendingService: MessageSendingServiceProtocol, InitializeInje } func sendReplayedMessage(_ repliedMessage: Message, message: Message) { - message.link = repliedMessage.id + message.linkedId = repliedMessage.id message.types = ["reply"] sendMessage(message) diff --git a/Shared/Library/Extensions/Models/Message/Message+Factory.swift b/Shared/Library/Extensions/Models/Message/Message+Factory.swift index 0513e042a524ebf2858e1325699964748c2128d2..197a44394fd058eabef3375e9ed4837ddcc5ce1e 100644 --- a/Shared/Library/Extensions/Models/Message/Message+Factory.swift +++ b/Shared/Library/Extensions/Models/Message/Message+Factory.swift @@ -39,7 +39,7 @@ extension Message { message.types = ["forward"] - message.link = link + message.linkedId = link message.status = nil message.files = message.files?.filter({ $0.mime != "translate" @@ -67,7 +67,7 @@ extension Message { func cloned() -> Message { let msg = Message(message: self) - msg.created = Date.currentTimestamp as AnyObject + msg.created = Date.currentTimestamp let builder = IdBuilder(format: .defaultId) diff --git a/Shared/Library/Extensions/Models/Message/MessageExtension.swift b/Shared/Library/Extensions/Models/Message/MessageExtension.swift index 10dfdcae5562518fead3b89f74ba0eba4bfca5ce..0342830c1e7a73135ef65358c3570ad9724e148c 100644 --- a/Shared/Library/Extensions/Models/Message/MessageExtension.swift +++ b/Shared/Library/Extensions/Models/Message/MessageExtension.swift @@ -49,12 +49,14 @@ extension Message { self.repliedby = message.repliedby self.mentioned = message.mentioned self.status = StringAtom(any: message.status) + self.localStatus = message.localStatus self.feedName = message.feedName self.senderName = message.senderName self.senderAvatar = message.senderAvatar self.isTrusted = message.isTrusted + self.isVisible = message.isVisible } var isDelivered: Bool { @@ -70,15 +72,14 @@ extension Message { } var createdInt: Int64 { - return (created as? Int64) ?? 0 + return created ?? 0 } var createdDate: Date? { - if let timestamp = created as? Int64 { + if let timestamp = created { let formattedTimestamp = Double(timestamp) / 1000 return Date(timeIntervalSince1970: formattedTimestamp) } - return nil } @@ -99,6 +100,23 @@ extension Message { type = newValue.compactMap { StringAtom(string: $0) } } } + + var linkedId: MessageServerId? { + get { + if let link = link as? MessageServerId { + return link + } + return (link as? Message)?.id + } + set { link = newValue as AnyObject? } + } + + var repliedMessage: Message? { + guard isReply, let repliedMessage = link as? Message else { + return nil + } + return repliedMessage + } } // MARK: - Feed @@ -166,6 +184,10 @@ extension Message { extension Message { + enum LocalStatus: String { + case replied = "replied" + } + var isStatusClear: Bool { return statusString == "clear" }