no message

This commit is contained in:
Peter
2016-11-03 23:02:21 +03:00
parent 2b07ed38ed
commit abfd19bda8
38 changed files with 1604 additions and 609 deletions

View File

@@ -25,8 +25,17 @@
D03ADB4F1D70546B005A521C /* AccessoryPanelNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03ADB4E1D70546B005A521C /* AccessoryPanelNode.swift */; };
D06879551DB8F1FC00424BBD /* CachedResourceRepresentations.swift in Sources */ = {isa = PBXBuildFile; fileRef = D06879541DB8F1FC00424BBD /* CachedResourceRepresentations.swift */; };
D06879571DB8F22200424BBD /* FetchCachedRepresentations.swift in Sources */ = {isa = PBXBuildFile; fileRef = D06879561DB8F22200424BBD /* FetchCachedRepresentations.swift */; };
D073CE631DCBBE5D007511FD /* MessageSent.caf in Resources */ = {isa = PBXBuildFile; fileRef = D073CE621DCBBE5D007511FD /* MessageSent.caf */; };
D073CE651DCBC26B007511FD /* ServiceSoundManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D073CE641DCBC26B007511FD /* ServiceSoundManager.swift */; };
D07A7DA31D957671005BCD27 /* ListMessageSnippetItemNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D07A7DA21D957671005BCD27 /* ListMessageSnippetItemNode.swift */; };
D07A7DA51D95783C005BCD27 /* ListMessageNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D07A7DA41D95783C005BCD27 /* ListMessageNode.swift */; };
D07CFF741DCA207200761F81 /* PeerSelectionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D07CFF731DCA207200761F81 /* PeerSelectionController.swift */; };
D07CFF761DCA224100761F81 /* PeerSelectionControllerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D07CFF751DCA224100761F81 /* PeerSelectionControllerNode.swift */; };
D07CFF791DCA226F00761F81 /* ChatListNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D07CFF781DCA226F00761F81 /* ChatListNode.swift */; };
D07CFF7B1DCA24BF00761F81 /* ChatListNodeEntries.swift in Sources */ = {isa = PBXBuildFile; fileRef = D07CFF7A1DCA24BF00761F81 /* ChatListNodeEntries.swift */; };
D07CFF7D1DCA273400761F81 /* ChatListViewTransition.swift in Sources */ = {isa = PBXBuildFile; fileRef = D07CFF7C1DCA273400761F81 /* ChatListViewTransition.swift */; };
D07CFF7F1DCA308500761F81 /* ChatListNodeLocation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D07CFF7E1DCA308500761F81 /* ChatListNodeLocation.swift */; };
D07CFF871DCAAE5E00761F81 /* ForwardAccessoryPanelNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D07CFF861DCAAE5E00761F81 /* ForwardAccessoryPanelNode.swift */; };
D08C367F1DB66A820064C744 /* ChatMediaInputPanelEntries.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08C367E1DB66A820064C744 /* ChatMediaInputPanelEntries.swift */; };
D08C36811DB66AAC0064C744 /* ChatMediaInputGridEntries.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08C36801DB66AAC0064C744 /* ChatMediaInputGridEntries.swift */; };
D08C36831DB66AD40064C744 /* ChatMediaInputStickerGridItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08C36821DB66AD40064C744 /* ChatMediaInputStickerGridItem.swift */; };
@@ -245,8 +254,17 @@
D03ADB4E1D70546B005A521C /* AccessoryPanelNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccessoryPanelNode.swift; sourceTree = "<group>"; };
D06879541DB8F1FC00424BBD /* CachedResourceRepresentations.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CachedResourceRepresentations.swift; sourceTree = "<group>"; };
D06879561DB8F22200424BBD /* FetchCachedRepresentations.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FetchCachedRepresentations.swift; sourceTree = "<group>"; };
D073CE621DCBBE5D007511FD /* MessageSent.caf */ = {isa = PBXFileReference; lastKnownFileType = file; name = MessageSent.caf; path = TelegramUI/Sounds/MessageSent.caf; sourceTree = "<group>"; };
D073CE641DCBC26B007511FD /* ServiceSoundManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ServiceSoundManager.swift; sourceTree = "<group>"; };
D07A7DA21D957671005BCD27 /* ListMessageSnippetItemNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListMessageSnippetItemNode.swift; sourceTree = "<group>"; };
D07A7DA41D95783C005BCD27 /* ListMessageNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListMessageNode.swift; sourceTree = "<group>"; };
D07CFF731DCA207200761F81 /* PeerSelectionController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerSelectionController.swift; sourceTree = "<group>"; };
D07CFF751DCA224100761F81 /* PeerSelectionControllerNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerSelectionControllerNode.swift; sourceTree = "<group>"; };
D07CFF781DCA226F00761F81 /* ChatListNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatListNode.swift; sourceTree = "<group>"; };
D07CFF7A1DCA24BF00761F81 /* ChatListNodeEntries.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatListNodeEntries.swift; sourceTree = "<group>"; };
D07CFF7C1DCA273400761F81 /* ChatListViewTransition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatListViewTransition.swift; sourceTree = "<group>"; };
D07CFF7E1DCA308500761F81 /* ChatListNodeLocation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatListNodeLocation.swift; sourceTree = "<group>"; };
D07CFF861DCAAE5E00761F81 /* ForwardAccessoryPanelNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ForwardAccessoryPanelNode.swift; sourceTree = "<group>"; };
D08C367E1DB66A820064C744 /* ChatMediaInputPanelEntries.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatMediaInputPanelEntries.swift; sourceTree = "<group>"; };
D08C36801DB66AAC0064C744 /* ChatMediaInputGridEntries.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatMediaInputGridEntries.swift; sourceTree = "<group>"; };
D08C36821DB66AD40064C744 /* ChatMediaInputStickerGridItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatMediaInputStickerGridItem.swift; sourceTree = "<group>"; };
@@ -541,10 +559,34 @@
children = (
D03ADB4E1D70546B005A521C /* AccessoryPanelNode.swift */,
D03ADB4A1D70443F005A521C /* ReplyAccessoryPanelNode.swift */,
D07CFF861DCAAE5E00761F81 /* ForwardAccessoryPanelNode.swift */,
);
name = "Accessory Panels";
sourceTree = "<group>";
};
D073CE611DCBBE09007511FD /* Sounds */ = {
isa = PBXGroup;
children = (
D073CE621DCBBE5D007511FD /* MessageSent.caf */,
);
name = Sounds;
sourceTree = "<group>";
};
D07CFF771DCA226200761F81 /* Chat List Node */ = {
isa = PBXGroup;
children = (
D07CFF781DCA226F00761F81 /* ChatListNode.swift */,
D0F69DFA1D6B8A880046BCD6 /* ChatListEmptyItem.swift */,
D0F69DFB1D6B8A880046BCD6 /* ChatListHoleItem.swift */,
D0F69DFC1D6B8A880046BCD6 /* ChatListItem.swift */,
D0F69DFD1D6B8A880046BCD6 /* ChatListSearchItem.swift */,
D07CFF7A1DCA24BF00761F81 /* ChatListNodeEntries.swift */,
D07CFF7C1DCA273400761F81 /* ChatListViewTransition.swift */,
D07CFF7E1DCA308500761F81 /* ChatListNodeLocation.swift */,
);
name = "Chat List Node";
sourceTree = "<group>";
};
D08D45281D5E340200A7428A /* Frameworks */ = {
isa = PBXGroup;
children = (
@@ -615,12 +657,14 @@
name = "Navigation Accessory Panels";
sourceTree = "<group>";
};
D0D2689B1D79D31500C422DA /* Share Recipients */ = {
D0D2689B1D79D31500C422DA /* Peer Selection */ = {
isa = PBXGroup;
children = (
D07CFF731DCA207200761F81 /* PeerSelectionController.swift */,
D07CFF751DCA224100761F81 /* PeerSelectionControllerNode.swift */,
D0D2689C1D79D33E00C422DA /* ShareRecipientsActionSheetController.swift */,
);
name = "Share Recipients";
name = "Peer Selection";
sourceTree = "<group>";
};
D0DE772C1D934DCB002B8809 /* List Items */ = {
@@ -761,6 +805,7 @@
D0F69DBE1D6B89880046BCD6 /* Gestures */,
D0F69DBF1D6B89AE0046BCD6 /* Nodes */,
D0F69DD31D6B8A160046BCD6 /* Controllers */,
D07CFF771DCA226200761F81 /* Chat List Node */,
D0E7A1BB1D8C17EB00C37A6F /* Chat History Node */,
);
name = Components;
@@ -840,7 +885,7 @@
D0F69E4E1D6B8BB90046BCD6 /* Media */,
D0F69E6C1D6B8C220046BCD6 /* Contacts */,
D0EE97131D88BB1A006C18E1 /* Peer Info */,
D0D2689B1D79D31500C422DA /* Share Recipients */,
D0D2689B1D79D31500C422DA /* Peer Selection */,
D0F69E791D6B8C3B0046BCD6 /* Settings */,
);
name = Controllers;
@@ -865,10 +910,6 @@
children = (
D0F69DF81D6B8A880046BCD6 /* ChatListController.swift */,
D0F69DF91D6B8A880046BCD6 /* ChatListControllerNode.swift */,
D0F69DFA1D6B8A880046BCD6 /* ChatListEmptyItem.swift */,
D0F69DFB1D6B8A880046BCD6 /* ChatListHoleItem.swift */,
D0F69DFC1D6B8A880046BCD6 /* ChatListItem.swift */,
D0F69DFD1D6B8A880046BCD6 /* ChatListSearchItem.swift */,
D0F69E051D6B8A8B0046BCD6 /* Search */,
);
name = "Chat List";
@@ -1064,6 +1105,7 @@
D0F69E941D6B8C9B0046BCD6 /* WebP.swift */,
D0B844571DAC44E8005F29E1 /* PeerPresenceStatusManager.swift */,
D0ED5D4A1DC806D7007CBB15 /* ApplicationSpecificData.swift */,
D073CE641DCBC26B007511FD /* ServiceSoundManager.swift */,
);
name = Utils;
sourceTree = "<group>";
@@ -1085,6 +1127,7 @@
children = (
D0F69DB91D6B88190046BCD6 /* TelegramUI.xcconfig */,
D0AB0BBA1D6719B5002C78E7 /* Images.xcassets */,
D073CE611DCBBE09007511FD /* Sounds */,
D0FC40811D5B8E7400261D9D /* TelegramUI */,
D0FC408C1D5B8E7500261D9D /* TelegramUITests */,
D0FC40801D5B8E7400261D9D /* Products */,
@@ -1227,6 +1270,7 @@
files = (
D0AB0BBB1D6719B5002C78E7 /* Images.xcassets in Resources */,
D0F69DBA1D6B88190046BCD6 /* TelegramUI.xcconfig in Resources */,
D073CE631DCBBE5D007511FD /* MessageSent.caf in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1253,6 +1297,7 @@
D0F69E171D6B8ACF0046BCD6 /* ChatHistoryLocation.swift in Sources */,
D0F69E741D6B8C340046BCD6 /* ContactsControllerNode.swift in Sources */,
D021E0E51DB55D0A00C6B04F /* ChatMediaInputStickerPackItem.swift in Sources */,
D07CFF7F1DCA308500761F81 /* ChatListNodeLocation.swift in Sources */,
D0F69EA21D6B8E380046BCD6 /* PhotoResources.swift in Sources */,
D08C367F1DB66A820064C744 /* ChatMediaInputPanelEntries.swift in Sources */,
D0BA6F831D784C520034826E /* ChatInputPanelNode.swift in Sources */,
@@ -1269,6 +1314,7 @@
D0B843DB1DAAB138005F29E1 /* PeerInfoPeerActionItem.swift in Sources */,
D0F69EA11D6B8E380046BCD6 /* FileResources.swift in Sources */,
D0F69D271D6B87D30046BCD6 /* FFMpegAudioFrameDecoder.swift in Sources */,
D073CE651DCBC26B007511FD /* ServiceSoundManager.swift in Sources */,
D0F69D521D6B87D30046BCD6 /* MediaPlayer.swift in Sources */,
D0F69E031D6B8A880046BCD6 /* ChatListItem.swift in Sources */,
D0F69E081D6B8A9C0046BCD6 /* ChatListSearchContainerNode.swift in Sources */,
@@ -1292,6 +1338,7 @@
D03ADB4F1D70546B005A521C /* AccessoryPanelNode.swift in Sources */,
D0F69E421D6B8B7E0046BCD6 /* ChatTextInputPanelNode.swift in Sources */,
D0F69E1A1D6B8AE60046BCD6 /* ChatHoleItem.swift in Sources */,
D07CFF871DCAAE5E00761F81 /* ForwardAccessoryPanelNode.swift in Sources */,
D0F69D9C1D6B87EC0046BCD6 /* MediaPlaybackData.swift in Sources */,
D0F69D241D6B87D30046BCD6 /* MediaPlayerAudioRenderer.swift in Sources */,
D0DF0CA41D82BCD0008AEB01 /* MentionChatInputContextPanelNode.swift in Sources */,
@@ -1303,6 +1350,7 @@
D0D2686E1D7898A900C422DA /* ChatMessageSelectionNode.swift in Sources */,
D0F69E8B1D6B8C850046BCD6 /* FFMpegSwResample.m in Sources */,
D0B843921DA7F13E005F29E1 /* PeerInfoDisclosureItem.swift in Sources */,
D07CFF791DCA226F00761F81 /* ChatListNode.swift in Sources */,
D0B7F8E81D8A1F5F0045D939 /* PeerMediaCollectionControllerNode.swift in Sources */,
D0F69DD21D6B8A0D0046BCD6 /* SearchDisplayControllerContentNode.swift in Sources */,
D0105D5A1D80B957008755D8 /* ChatChannelSubscriberInputPanelNode.swift in Sources */,
@@ -1323,6 +1371,7 @@
D0DE77301D934DEF002B8809 /* ListMessageItem.swift in Sources */,
D08C36831DB66AD40064C744 /* ChatMediaInputStickerGridItem.swift in Sources */,
D0F69E641D6B8BF90046BCD6 /* ChatVideoGalleryItem.swift in Sources */,
D07CFF7B1DCA24BF00761F81 /* ChatListNodeEntries.swift in Sources */,
D0DF0CA11D821B28008AEB01 /* HashtagsTableCell.swift in Sources */,
D021E0D01DB413BC00C6B04F /* ChatInputNode.swift in Sources */,
D0F69E351D6B8B030046BCD6 /* ChatMessageInteractiveFileNode.swift in Sources */,
@@ -1373,6 +1422,7 @@
D0B843D91DAAAA0C005F29E1 /* PeerInfoPeerItem.swift in Sources */,
D0F69DD11D6B8A0D0046BCD6 /* SearchDisplayController.swift in Sources */,
D0DE77271D932627002B8809 /* ChatHistoryNode.swift in Sources */,
D07CFF7D1DCA273400761F81 /* ChatListViewTransition.swift in Sources */,
D0DF0C951D81B063008AEB01 /* ChatInterfaceStateContextMenus.swift in Sources */,
D0F69DF21D6B8A6C0046BCD6 /* AuthorizationPasswordController.swift in Sources */,
D0DE772B1D932E16002B8809 /* PeerMediaCollectionModeSelectionNode.swift in Sources */,
@@ -1384,6 +1434,7 @@
D0F69DF11D6B8A6C0046BCD6 /* AuthorizationController.swift in Sources */,
D0E7A1BF1D8C24B900C37A6F /* ChatHistoryViewForLocation.swift in Sources */,
D0F69E891D6B8C850046BCD6 /* FastBlur.m in Sources */,
D07CFF761DCA224100761F81 /* PeerSelectionControllerNode.swift in Sources */,
D0F69E7C1D6B8C470046BCD6 /* SettingsAccountInfoItem.swift in Sources */,
D0B843D31DA922E3005F29E1 /* ChannelInfoEntries.swift in Sources */,
D0B843D11DA922D7005F29E1 /* UserInfoEntries.swift in Sources */,
@@ -1418,6 +1469,7 @@
D0F69D311D6B87D30046BCD6 /* FFMpegMediaFrameSource.swift in Sources */,
D0F69DDF1D6B8A420046BCD6 /* ListController.swift in Sources */,
D0F69E3A1D6B8B030046BCD6 /* ChatMessageReplyInfoNode.swift in Sources */,
D07CFF741DCA207200761F81 /* PeerSelectionController.swift in Sources */,
D0EE971A1D88BCA0006C18E1 /* ChatInfo.swift in Sources */,
D0F69DE31D6B8A420046BCD6 /* ListControllerItem.swift in Sources */,
D07A7DA31D957671005BCD27 /* ListMessageSnippetItemNode.swift in Sources */,

View File

@@ -35,6 +35,9 @@ public class ChatController: ViewController {
private var controllerInteraction: ChatControllerInteraction?
private var interfaceInteraction: ChatPanelInterfaceInteraction?
private let controllerNavigationDisposable = MetaDisposable()
private let sentMessageEventsDisposable = MetaDisposable()
public init(account: Account, peerId: PeerId, messageId: MessageId? = nil) {
self.account = account
self.peerId = peerId
@@ -164,7 +167,7 @@ public class ChatController: ViewController {
}, sendSticker: { [weak self] file in
if let strongSelf = self {
strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({})
enqueueMessage(account: strongSelf.account, peerId: strongSelf.peerId, text: "", replyMessageId: nil, media: file).start()
enqueueMessages(account: strongSelf.account, peerId: strongSelf.peerId, messages: [.message(text: "", media: file, replyToMessageId: nil)]).start()
}
})
@@ -207,6 +210,8 @@ public class ChatController: ViewController {
self.navigationActionDisposable.dispose()
self.galleryHiddenMesageAndMediaDisposable.dispose()
self.peerDisposable.dispose()
self.controllerNavigationDisposable.dispose()
self.sentMessageEventsDisposable.dispose()
}
var chatDisplayNode: ChatControllerNode {
@@ -218,7 +223,19 @@ public class ChatController: ViewController {
override public func loadDisplayNode() {
self.displayNode = ChatControllerNode(account: self.account, peerId: self.peerId, messageId: self.messageId, controllerInteraction: self.controllerInteraction!)
self.ready.set(combineLatest(self.chatDisplayNode.historyNode.historyReady.get(), self._peerReady.get()) |> map { $0 && $1 })
let initialData = self.chatDisplayNode.historyNode.initialData
|> take(1)
|> beforeNext { [weak self] initialData in
if let strongSelf = self, let initialData = initialData {
if let interfaceState = initialData.chatInterfaceState as? ChatInterfaceState {
strongSelf.updateChatPresentationInterfaceState(animated: false, interactive: false, { $0.updatedInterfaceState({ _ in return interfaceState }) })
}
}
}
self.ready.set(combineLatest(self.chatDisplayNode.historyNode.historyReady.get(), self._peerReady.get(), initialData) |> map { historyReady, peerReady, _ in
return historyReady && peerReady
})
self.chatDisplayNode.historyNode.visibleContentOffsetChanged = { [weak self] offset in
if let strongSelf = self {
@@ -282,7 +299,7 @@ public class ChatController: ViewController {
stationaryItemRange = (maxInsertedItem + 1, Int.max)
}
mappedTransition = (ChatHistoryListViewTransition(historyView: transition.historyView, deleteItems: deleteItems, insertItems: insertItems, updateItems: transition.updateItems, options: options, scrollToItem: scrollToItem, stationaryItemRange: stationaryItemRange), updateSizeAndInsets)
mappedTransition = (ChatHistoryListViewTransition(historyView: transition.historyView, deleteItems: deleteItems, insertItems: insertItems, updateItems: transition.updateItems, options: options, scrollToItem: scrollToItem, stationaryItemRange: stationaryItemRange, initialData: transition.initialData), updateSizeAndInsets)
})
if let mappedTransition = mappedTransition {
@@ -309,7 +326,7 @@ public class ChatController: ViewController {
let resource = PhotoLibraryMediaResource(localIdentifier: asset.localIdentifier)
let media = TelegramMediaImage(imageId: MediaId(namespace: Namespaces.Media.LocalImage, id: randomId), representations: [TelegramMediaImageRepresentation(dimensions: scaledSize, resource: resource)])
strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({})
enqueueMessage(account: strongSelf.account, peerId: strongSelf.peerId, text: "", replyMessageId: nil, media: media).start()
enqueueMessages(account: strongSelf.account, peerId: strongSelf.peerId, messages: [.message(text: "", media: media, replyToMessageId: nil)]).start()
}
}
controller.location = { [weak strongSelf] in
@@ -356,9 +373,49 @@ public class ChatController: ViewController {
}
}, forwardSelectedMessages: { [weak self] in
if let strongSelf = self {
let controller = ShareRecipientsActionSheetController()
//let controller = ShareRecipientsActionSheetController()
//strongSelf.present(controller, in: .window)
if let forwardMessageIdsSet = strongSelf.presentationInterfaceState.interfaceState.selectionState?.selectedIds {
let forwardMessageIds = Array(forwardMessageIdsSet).sorted()
let controller = PeerSelectionController(account: strongSelf.account)
controller.peerSelected = { [weak controller] peerId in
if let strongSelf = self, let strongController = controller {
if peerId == strongSelf.peerId {
strongSelf.updateChatPresentationInterfaceState(animated: false, interactive: true, { $0.updatedInterfaceState({ $0.withUpdatedForwardMessageIds(forwardMessageIds).withoutSelectionState() }) })
strongController.dismiss()
} else {
(strongSelf.account.postbox.modify({ modifier -> Void in
modifier.updatePeerChatInterfaceState(peerId, update: { currentState in
if let currentState = currentState as? ChatInterfaceState {
return currentState.withUpdatedForwardMessageIds(forwardMessageIds)
} else {
return ChatInterfaceState().withUpdatedForwardMessageIds(forwardMessageIds)
}
return currentState
})
}) |> deliverOnMainQueue).start(completed: {
if let strongSelf = self {
strongSelf.updateChatPresentationInterfaceState(animated: false, interactive: true, { $0.updatedInterfaceState({ $0.withoutSelectionState() }) })
let ready = ValuePromise<Bool>()
strongSelf.controllerNavigationDisposable.set((ready.get() |> take(1) |> deliverOnMainQueue).start(next: { _ in
if let strongController = controller {
strongController.dismiss()
}
}))
(strongSelf.navigationController as? NavigationController)?.replaceTopController(ChatController(account: strongSelf.account, peerId: peerId), animated: false, ready: ready)
}
})
}
}
}
strongSelf.present(controller, in: .window)
}
}
}, updateTextInputState: { [weak self] textInputState in
if let strongSelf = self {
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { $0.updatedInterfaceState { $0.withUpdatedInputState(textInputState) } })
@@ -373,6 +430,10 @@ public class ChatController: ViewController {
self.chatDisplayNode.interfaceInteraction = interfaceInteraction
self.displayNodeDidLoad()
self.sentMessageEventsDisposable.set(self.account.pendingMessageManager.deliveredMessageEvents(peerId: self.peerId).start(next: { _ in
serviceSoundManager.playMessageDeliveredSound()
}))
}
override public func viewWillAppear(_ animated: Bool) {
@@ -388,6 +449,18 @@ public class ChatController: ViewController {
self.chatDisplayNode.loadInputPanels()
}
override public func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
let peerId = self.peerId
let interfaceState = self.presentationInterfaceState.interfaceState
self.account.postbox.modify({ modifier -> Void in
modifier.updatePeerChatInterfaceState(peerId, update: { _ in
return interfaceState
})
}).start()
}
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
super.containerLayoutUpdated(layout, transition: transition)

View File

@@ -104,16 +104,28 @@ class ChatControllerNode: ASDisplayNode {
}
let text = textInputPanelNode.text
if !text.isEmpty || strongSelf.chatPresentationInterfaceState.interfaceState.forwardMessageIds != nil {
strongSelf.setupSendActionOnViewUpdate({ [weak strongSelf] in
if let strongSelf = strongSelf, let textInputPanelNode = strongSelf.inputPanelNode as? ChatTextInputPanelNode {
strongSelf.ignoreUpdateHeight = true
textInputPanelNode.text = ""
strongSelf.requestUpdateChatInterfaceState(false, { $0.withUpdatedReplyMessageId(nil) })
strongSelf.requestUpdateChatInterfaceState(false, { $0.withUpdatedReplyMessageId(nil).withUpdatedForwardMessageIds(nil) })
strongSelf.ignoreUpdateHeight = false
}
})
let _ = enqueueMessage(account: strongSelf.account, peerId: strongSelf.peerId, text: text, replyMessageId: strongSelf.chatPresentationInterfaceState.interfaceState.replyMessageId).start()
var messages: [EnqueueMessage] = []
if !text.isEmpty {
messages.append(.message(text: text, media: nil, replyToMessageId: strongSelf.chatPresentationInterfaceState.interfaceState.replyMessageId))
}
if let forwardMessageIds = strongSelf.chatPresentationInterfaceState.interfaceState.forwardMessageIds {
for id in forwardMessageIds {
messages.append(.forward(source: id))
}
}
let _ = enqueueMessages(account: strongSelf.account, peerId: strongSelf.peerId, messages: messages).start()
}
}
}
@@ -240,7 +252,11 @@ class ChatControllerNode: ASDisplayNode {
accessoryPanelNode.dismiss = { [weak self, weak accessoryPanelNode] in
if let strongSelf = self, let accessoryPanelNode = accessoryPanelNode, strongSelf.accessoryPanelNode === accessoryPanelNode {
if let _ = accessoryPanelNode as? ReplyAccessoryPanelNode {
strongSelf.requestUpdateChatInterfaceState(true, { $0.withUpdatedReplyMessageId(nil) })
} else if let _ = accessoryPanelNode as? ForwardAccessoryPanelNode {
strongSelf.requestUpdateChatInterfaceState(true, { $0.withUpdatedForwardMessageIds(nil) })
}
}
}
@@ -439,8 +455,11 @@ class ChatControllerNode: ASDisplayNode {
var updateInputTextState = self.chatPresentationInterfaceState.interfaceState.inputState != chatPresentationInterfaceState.interfaceState.inputState
self.chatPresentationInterfaceState = chatPresentationInterfaceState
let keepSendButtonEnabled = chatPresentationInterfaceState.interfaceState.forwardMessageIds != nil
if let textInputPanelNode = self.textInputPanelNode, updateInputTextState {
textInputPanelNode.inputTextState = chatPresentationInterfaceState.interfaceState.inputState
textInputPanelNode.updateInputTextState(chatPresentationInterfaceState.interfaceState.inputState, keepSendButtonEnabled: keepSendButtonEnabled, animated: animated)
} else {
textInputPanelNode?.updateKeepSendButtonEnabled(keepSendButtonEnabled: keepSendButtonEnabled, animated: animated)
}
let layoutTransition: ContainedViewLayoutTransition = animated ? .animated(duration: 0.4, curve: .spring) : .immediate

View File

@@ -149,7 +149,7 @@ public final class ChatHistoryGridNode: GridNode, ChatHistoryNode {
}
}
return .complete()
case let .HistoryView(view, type, scrollPosition):
case let .HistoryView(view, type, scrollPosition, _):
let reason: ChatHistoryViewTransitionReason
var prepareOnMainQueue = false
switch type {
@@ -172,7 +172,7 @@ public final class ChatHistoryGridNode: GridNode, ChatHistoryNode {
let processedView = ChatHistoryView(originalView: view, filteredEntries: chatHistoryEntriesForView(view, includeUnreadEntry: false))
let previous = previousView.swap(processedView)
return preparedChatHistoryViewTransition(from: previous, to: processedView, reason: reason, account: account, peerId: peerId, controllerInteraction: controllerInteraction, scrollPosition: scrollPosition) |> map({ mappedChatHistoryViewListTransition(account: account, peerId: peerId, controllerInteraction: controllerInteraction, transition: $0) }) |> runOn(prepareOnMainQueue ? Queue.mainQueue() : messageViewQueue)
return preparedChatHistoryViewTransition(from: previous, to: processedView, reason: reason, account: account, peerId: peerId, controllerInteraction: controllerInteraction, scrollPosition: scrollPosition, initialData: nil) |> map({ mappedChatHistoryViewListTransition(account: account, peerId: peerId, controllerInteraction: controllerInteraction, transition: $0) }) |> runOn(prepareOnMainQueue ? Queue.mainQueue() : messageViewQueue)
}
}

View File

@@ -21,8 +21,8 @@ enum ChatHistoryViewUpdateType {
}
enum ChatHistoryViewUpdate {
case Loading
case HistoryView(view: MessageHistoryView, type: ChatHistoryViewUpdateType, scrollPosition: ChatHistoryViewScrollPosition?)
case Loading(initialData: InitialMessageHistoryData?)
case HistoryView(view: MessageHistoryView, type: ChatHistoryViewUpdateType, scrollPosition: ChatHistoryViewScrollPosition?, initialData: InitialMessageHistoryData?)
}
struct ChatHistoryView {
@@ -59,6 +59,7 @@ struct ChatHistoryViewTransition {
let options: ListViewDeleteAndInsertOptions
let scrollToItem: ListViewScrollToItem?
let stationaryItemRange: (Int, Int)?
let initialData: InitialMessageHistoryData?
}
struct ChatHistoryListViewTransition {
@@ -69,6 +70,7 @@ struct ChatHistoryListViewTransition {
let options: ListViewDeleteAndInsertOptions
let scrollToItem: ListViewScrollToItem?
let stationaryItemRange: (Int, Int)?
let initialData: InitialMessageHistoryData?
}
private func maxIncomingMessageIdForEntries(_ entries: [ChatHistoryEntry], indexRange: (Int, Int)) -> MessageId? {
@@ -121,7 +123,7 @@ private func mappedUpdateEntries(account: Account, peerId: PeerId, controllerInt
}
private func mappedChatHistoryViewListTransition(account: Account, peerId: PeerId, controllerInteraction: ChatControllerInteraction, mode: ChatHistoryListMode, transition: ChatHistoryViewTransition) -> ChatHistoryListViewTransition {
return ChatHistoryListViewTransition(historyView: transition.historyView, deleteItems: transition.deleteItems, insertItems: mappedInsertEntries(account: account, peerId: peerId, controllerInteraction: controllerInteraction, mode: mode, entries: transition.insertEntries), updateItems: mappedUpdateEntries(account: account, peerId: peerId, controllerInteraction: controllerInteraction, mode: mode, entries: transition.updateEntries), options: transition.options, scrollToItem: transition.scrollToItem, stationaryItemRange: transition.stationaryItemRange)
return ChatHistoryListViewTransition(historyView: transition.historyView, deleteItems: transition.deleteItems, insertItems: mappedInsertEntries(account: account, peerId: peerId, controllerInteraction: controllerInteraction, mode: mode, entries: transition.insertEntries), updateItems: mappedUpdateEntries(account: account, peerId: peerId, controllerInteraction: controllerInteraction, mode: mode, entries: transition.updateEntries), options: transition.options, scrollToItem: transition.scrollToItem, stationaryItemRange: transition.stationaryItemRange, initialData: transition.initialData)
}
private final class ChatHistoryTransactionOpaqueState {
@@ -153,6 +155,12 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
public let historyReady = Promise<Bool>()
private var didSetHistoryReady = false
private let _initialData = Promise<InitialMessageHistoryData?>()
private var didSetInitialData = false
public var initialData: Signal<InitialMessageHistoryData?, NoError> {
return self._initialData.get()
}
private let maxVisibleIncomingMessageId = ValuePromise<MessageId>()
let canReadHistory = ValuePromise<Bool>()
@@ -191,7 +199,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|> mapToSignal { location in
return chatHistoryViewForLocation(location, account: account, peerId: peerId, fixedCombinedReadState: fixedCombinedReadState.with { $0 }, tagMask: tagMask) |> beforeNext { viewUpdate in
switch viewUpdate {
case let .HistoryView(view, _, _):
case let .HistoryView(view, _, _, _):
let _ = fixedCombinedReadState.swap(view.combinedReadState)
default:
break
@@ -202,10 +210,16 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
let previousView = Atomic<ChatHistoryView?>(value: nil)
let historyViewTransition = historyViewUpdate |> mapToQueue { [weak self] update -> Signal<ChatHistoryListViewTransition, NoError> in
let initialData: InitialMessageHistoryData?
switch update {
case .Loading:
case let .Loading(data):
initialData = data
Queue.mainQueue().async { [weak self] in
if let strongSelf = self {
if !strongSelf.didSetInitialData {
strongSelf.didSetInitialData = true
strongSelf._initialData.set(.single(data))
}
if !strongSelf.didSetHistoryReady {
strongSelf.didSetHistoryReady = true
strongSelf.historyReady.set(.single(true))
@@ -213,7 +227,8 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
}
}
return .complete()
case let .HistoryView(view, type, scrollPosition):
case let .HistoryView(view, type, scrollPosition, data):
initialData = data
let reason: ChatHistoryViewTransitionReason
var prepareOnMainQueue = false
switch type {
@@ -236,7 +251,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
let processedView = ChatHistoryView(originalView: view, filteredEntries: chatHistoryEntriesForView(view, includeUnreadEntry: mode == .bubbles))
let previous = previousView.swap(processedView)
return preparedChatHistoryViewTransition(from: previous, to: processedView, reason: reason, account: account, peerId: peerId, controllerInteraction: controllerInteraction, scrollPosition: scrollPosition) |> map({ mappedChatHistoryViewListTransition(account: account, peerId: peerId, controllerInteraction: controllerInteraction, mode: mode, transition: $0) }) |> runOn(prepareOnMainQueue ? Queue.mainQueue() : messageViewQueue)
return preparedChatHistoryViewTransition(from: previous, to: processedView, reason: reason, account: account, peerId: peerId, controllerInteraction: controllerInteraction, scrollPosition: scrollPosition, initialData: initialData) |> map({ mappedChatHistoryViewListTransition(account: account, peerId: peerId, controllerInteraction: controllerInteraction, mode: mode, transition: $0) }) |> runOn(prepareOnMainQueue ? Queue.mainQueue() : messageViewQueue)
}
}
@@ -344,6 +359,10 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
if strongSelf.isNodeLoaded {
strongSelf.dequeueHistoryViewTransition()
} else {
if !strongSelf.didSetInitialData {
strongSelf.didSetInitialData = true
strongSelf._initialData.set(.single(transition.initialData))
}
if !strongSelf.didSetHistoryReady {
strongSelf.didSetHistoryReady = true
strongSelf.historyReady.set(.single(true))
@@ -374,7 +393,10 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
}
}
}
if !strongSelf.didSetInitialData {
strongSelf.didSetInitialData = true
strongSelf._initialData.set(.single(transition.initialData))
}
if !strongSelf.didSetHistoryReady {
strongSelf.didSetHistoryReady = true
strongSelf.historyReady.set(.single(true))

View File

@@ -9,15 +9,15 @@ func chatHistoryViewForLocation(_ location: ChatHistoryLocation, account: Accoun
case let .Initial(count):
var preloaded = false
var fadeIn = false
let signal: Signal<(MessageHistoryView, ViewUpdateType), NoError>
let signal: Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError>
if let tagMask = tagMask {
signal = account.viewTracker.aroundMessageHistoryViewForPeerId(peerId, index: MessageIndex.upperBound(peerId: peerId), count: count, anchorIndex: MessageIndex.upperBound(peerId: peerId), fixedCombinedReadState: nil, tagMask: tagMask)
} else {
signal = account.viewTracker.aroundUnreadMessageHistoryViewForPeerId(peerId, count: count, tagMask: tagMask)
}
return signal |> map { view, updateType -> ChatHistoryViewUpdate in
return signal |> map { view, updateType, initialData -> ChatHistoryViewUpdate in
if preloaded {
return .HistoryView(view: view, type: .Generic(type: updateType), scrollPosition: nil)
return .HistoryView(view: view, type: .Generic(type: updateType), scrollPosition: nil, initialData: initialData)
} else {
if let maxReadIndex = view.maxReadIndex {
var targetIndex = 0
@@ -33,25 +33,25 @@ func chatHistoryViewForLocation(_ location: ChatHistoryLocation, account: Accoun
for i in targetIndex ..< maxIndex {
if case .HoleEntry = view.entries[i] {
fadeIn = true
return .Loading
return .Loading(initialData: initialData)
}
}
}
preloaded = true
return .HistoryView(view: view, type: .Initial(fadeIn: fadeIn), scrollPosition: .Unread(index: maxReadIndex))
return .HistoryView(view: view, type: .Initial(fadeIn: fadeIn), scrollPosition: .Unread(index: maxReadIndex), initialData: initialData)
} else {
preloaded = true
return .HistoryView(view: view, type: .Initial(fadeIn: fadeIn), scrollPosition: nil)
return .HistoryView(view: view, type: .Initial(fadeIn: fadeIn), scrollPosition: nil, initialData: initialData)
}
}
}
case let .InitialSearch(messageId, count):
var preloaded = false
var fadeIn = false
return account.viewTracker.aroundIdMessageHistoryViewForPeerId(peerId, count: count, messageId: messageId, tagMask: tagMask) |> map { view, updateType -> ChatHistoryViewUpdate in
return account.viewTracker.aroundIdMessageHistoryViewForPeerId(peerId, count: count, messageId: messageId, tagMask: tagMask) |> map { view, updateType, initialData -> ChatHistoryViewUpdate in
if preloaded {
return .HistoryView(view: view, type: .Generic(type: updateType), scrollPosition: nil)
return .HistoryView(view: view, type: .Generic(type: updateType), scrollPosition: nil, initialData: initialData)
} else {
let anchorIndex = view.anchorIndex
@@ -68,19 +68,19 @@ func chatHistoryViewForLocation(_ location: ChatHistoryLocation, account: Accoun
for i in targetIndex ..< maxIndex {
if case .HoleEntry = view.entries[i] {
fadeIn = true
return .Loading
return .Loading(initialData: initialData)
}
}
}
preloaded = true
//case Index(index: MessageIndex, position: ListViewScrollPosition, directionHint: ListViewScrollToItemDirectionHint, animated: Bool)
return .HistoryView(view: view, type: .Initial(fadeIn: fadeIn), scrollPosition: .Index(index: anchorIndex, position: .Center(.Bottom), directionHint: .Down, animated: false))
return .HistoryView(view: view, type: .Initial(fadeIn: fadeIn), scrollPosition: .Index(index: anchorIndex, position: .Center(.Bottom), directionHint: .Down, animated: false), initialData: initialData)
}
}
case let .Navigation(index, anchorIndex):
var first = true
return account.viewTracker.aroundMessageHistoryViewForPeerId(peerId, index: index, count: 140, anchorIndex: anchorIndex, fixedCombinedReadState: fixedCombinedReadState, tagMask: tagMask) |> map { view, updateType -> ChatHistoryViewUpdate in
return account.viewTracker.aroundMessageHistoryViewForPeerId(peerId, index: index, count: 140, anchorIndex: anchorIndex, fixedCombinedReadState: fixedCombinedReadState, tagMask: tagMask) |> map { view, updateType, initialData -> ChatHistoryViewUpdate in
let genericType: ViewUpdateType
if first {
first = false
@@ -88,13 +88,13 @@ func chatHistoryViewForLocation(_ location: ChatHistoryLocation, account: Accoun
} else {
genericType = updateType
}
return .HistoryView(view: view, type: .Generic(type: genericType), scrollPosition: nil)
return .HistoryView(view: view, type: .Generic(type: genericType), scrollPosition: nil, initialData: initialData)
}
case let .Scroll(index, anchorIndex, sourceIndex, scrollPosition, animated):
let directionHint: ListViewScrollToItemDirectionHint = sourceIndex > index ? .Down : .Up
let chatScrollPosition = ChatHistoryViewScrollPosition.Index(index: index, position: scrollPosition, directionHint: directionHint, animated: animated)
var first = true
return account.viewTracker.aroundMessageHistoryViewForPeerId(peerId, index: index, count: 140, anchorIndex: anchorIndex, fixedCombinedReadState: fixedCombinedReadState, tagMask: tagMask) |> map { view, updateType -> ChatHistoryViewUpdate in
return account.viewTracker.aroundMessageHistoryViewForPeerId(peerId, index: index, count: 140, anchorIndex: anchorIndex, fixedCombinedReadState: fixedCombinedReadState, tagMask: tagMask) |> map { view, updateType, initialData -> ChatHistoryViewUpdate in
let genericType: ViewUpdateType
let scrollPosition: ChatHistoryViewScrollPosition? = first ? chatScrollPosition : nil
if first {
@@ -103,7 +103,7 @@ func chatHistoryViewForLocation(_ location: ChatHistoryLocation, account: Accoun
} else {
genericType = updateType
}
return .HistoryView(view: view, type: .Generic(type: genericType), scrollPosition: scrollPosition)
return .HistoryView(view: view, type: .Generic(type: genericType), scrollPosition: scrollPosition, initialData: initialData)
}
}
}

View File

@@ -1,15 +1,33 @@
import Foundation
import Postbox
struct ChatInterfaceSelectionState: Equatable {
struct ChatInterfaceSelectionState: Coding, Equatable {
let selectedIds: Set<MessageId>
static func ==(lhs: ChatInterfaceSelectionState, rhs: ChatInterfaceSelectionState) -> Bool {
return lhs.selectedIds == rhs.selectedIds
}
init(selectedIds: Set<MessageId>) {
self.selectedIds = selectedIds
}
struct ChatTextInputState: Equatable {
init(decoder: Decoder) {
if let data = decoder.decodeBytesForKeyNoCopy("i") {
self.selectedIds = Set(MessageId.decodeArrayFromBuffer(data))
} else {
self.selectedIds = Set()
}
}
func encode(_ encoder: Encoder) {
let buffer = WriteBuffer()
MessageId.encodeArrayToBuffer(Array(selectedIds), buffer: buffer)
encoder.encodeBytes(buffer, forKey: "i")
}
}
struct ChatTextInputState: Coding, Equatable {
let inputText: String
let selectionRange: Range<Int>
@@ -26,35 +44,123 @@ struct ChatTextInputState: Equatable {
self.inputText = inputText
self.selectionRange = selectionRange
}
init(decoder: Decoder) {
self.inputText = decoder.decodeStringForKey("t")
self.selectionRange = Int(decoder.decodeInt32ForKey("s0")) ..< Int(decoder.decodeInt32ForKey("s1"))
}
final class ChatInterfaceState: Equatable {
func encode(_ encoder: Encoder) {
encoder.encodeString(self.inputText, forKey: "t")
encoder.encodeInt32(Int32(self.selectionRange.lowerBound), forKey: "s0")
encoder.encodeInt32(Int32(self.selectionRange.upperBound), forKey: "s1")
}
}
final class ChatInterfaceState: PeerChatInterfaceState, Equatable {
let inputState: ChatTextInputState
let replyMessageId: MessageId?
let forwardMessageIds: [MessageId]?
let selectionState: ChatInterfaceSelectionState?
var chatListEmbeddedState: PeerChatListEmbeddedInterfaceState? {
return nil
}
init() {
self.inputState = ChatTextInputState()
self.replyMessageId = nil
self.forwardMessageIds = nil
self.selectionState = nil
}
init(inputState: ChatTextInputState, replyMessageId: MessageId?, selectionState: ChatInterfaceSelectionState?) {
init(inputState: ChatTextInputState, replyMessageId: MessageId?, forwardMessageIds: [MessageId]?, selectionState: ChatInterfaceSelectionState?) {
self.inputState = inputState
self.replyMessageId = replyMessageId
self.forwardMessageIds = forwardMessageIds
self.selectionState = selectionState
}
init(decoder: Decoder) {
if let inputState = decoder.decodeObjectForKey("is", decoder: { return ChatTextInputState(decoder: $0) }) as? ChatTextInputState {
self.inputState = inputState
} else {
self.inputState = ChatTextInputState()
}
let replyMessageIdPeerId: Int64? = decoder.decodeInt64ForKey("r.p")
let replyMessageIdNamespace: Int32? = decoder.decodeInt32ForKey("r.n")
let replyMessageIdId: Int32? = decoder.decodeInt32ForKey("r.i")
if let replyMessageIdPeerId = replyMessageIdPeerId, let replyMessageIdNamespace = replyMessageIdNamespace, let replyMessageIdId = replyMessageIdId {
self.replyMessageId = MessageId(peerId: PeerId(replyMessageIdPeerId), namespace: replyMessageIdNamespace, id: replyMessageIdId)
} else {
self.replyMessageId = nil
}
if let forwardMessageIdsData = decoder.decodeBytesForKeyNoCopy("fm") {
self.forwardMessageIds = MessageId.decodeArrayFromBuffer(forwardMessageIdsData)
} else {
self.forwardMessageIds = nil
}
if let selectionState = decoder.decodeObjectForKey("ss", decoder: { return ChatInterfaceSelectionState(decoder: $0) }) as? ChatInterfaceSelectionState {
self.selectionState = selectionState
} else {
self.selectionState = nil
}
}
func encode(_ encoder: Encoder) {
encoder.encodeObject(self.inputState, forKey: "is")
if let replyMessageId = self.replyMessageId {
encoder.encodeInt64(replyMessageId.peerId.toInt64(), forKey: "r.p")
encoder.encodeInt32(replyMessageId.namespace, forKey: "r.n")
encoder.encodeInt32(replyMessageId.id, forKey: "r.i")
} else {
encoder.encodeNil(forKey: "r.p")
encoder.encodeNil(forKey: "r.n")
encoder.encodeNil(forKey: "r.i")
}
if let forwardMessageIds = self.forwardMessageIds {
let buffer = WriteBuffer()
MessageId.encodeArrayToBuffer(forwardMessageIds, buffer: buffer)
encoder.encodeBytes(buffer, forKey: "fm")
} else {
encoder.encodeNil(forKey: "fm")
}
if let selectionState = self.selectionState {
encoder.encodeObject(selectionState, forKey: "ss")
} else {
encoder.encodeNil(forKey: "ss")
}
}
func isEqual(to: PeerChatInterfaceState) -> Bool {
if let to = to as? ChatInterfaceState, self == to {
return true
} else {
return false
}
}
static func ==(lhs: ChatInterfaceState, rhs: ChatInterfaceState) -> Bool {
if let lhsForwardMessageIds = lhs.forwardMessageIds, let rhsForwardMessageIds = rhs.forwardMessageIds {
if lhsForwardMessageIds != rhsForwardMessageIds {
return false
}
} else if (lhs.forwardMessageIds != nil) != (rhs.forwardMessageIds != nil) {
return false
}
return lhs.inputState == rhs.inputState && lhs.replyMessageId == rhs.replyMessageId && lhs.selectionState == rhs.selectionState
}
func withUpdatedInputState(_ inputState: ChatTextInputState) -> ChatInterfaceState {
return ChatInterfaceState(inputState: inputState, replyMessageId: self.replyMessageId, selectionState: self.selectionState)
return ChatInterfaceState(inputState: inputState, replyMessageId: self.replyMessageId, forwardMessageIds: self.forwardMessageIds, selectionState: self.selectionState)
}
func withUpdatedReplyMessageId(_ replyMessageId: MessageId?) -> ChatInterfaceState {
return ChatInterfaceState(inputState: self.inputState, replyMessageId: replyMessageId, selectionState: self.selectionState)
return ChatInterfaceState(inputState: self.inputState, replyMessageId: replyMessageId, forwardMessageIds: self.forwardMessageIds, selectionState: self.selectionState)
}
func withUpdatedForwardMessageIds(_ forwardMessageIds: [MessageId]?) -> ChatInterfaceState {
return ChatInterfaceState(inputState: self.inputState, replyMessageId: self.replyMessageId, forwardMessageIds: forwardMessageIds, selectionState: self.selectionState)
}
func withUpdatedSelectedMessage(_ messageId: MessageId) -> ChatInterfaceState {
@@ -63,7 +169,7 @@ final class ChatInterfaceState: Equatable {
selectedIds.formUnion(selectionState.selectedIds)
}
selectedIds.insert(messageId)
return ChatInterfaceState(inputState: self.inputState, replyMessageId: self.replyMessageId, selectionState: ChatInterfaceSelectionState(selectedIds: selectedIds))
return ChatInterfaceState(inputState: self.inputState, replyMessageId: self.replyMessageId, forwardMessageIds: self.forwardMessageIds, selectionState: ChatInterfaceSelectionState(selectedIds: selectedIds))
}
func withToggledSelectedMessage(_ messageId: MessageId) -> ChatInterfaceState {
@@ -76,10 +182,10 @@ final class ChatInterfaceState: Equatable {
} else {
selectedIds.insert(messageId)
}
return ChatInterfaceState(inputState: self.inputState, replyMessageId: self.replyMessageId, selectionState: ChatInterfaceSelectionState(selectedIds: selectedIds))
return ChatInterfaceState(inputState: self.inputState, replyMessageId: self.replyMessageId, forwardMessageIds: self.forwardMessageIds, selectionState: ChatInterfaceSelectionState(selectedIds: selectedIds))
}
func withoutSelectionState() -> ChatInterfaceState {
return ChatInterfaceState(inputState: self.inputState, replyMessageId: self.replyMessageId, selectionState: nil)
return ChatInterfaceState(inputState: self.inputState, replyMessageId: self.replyMessageId, forwardMessageIds: self.forwardMessageIds, selectionState: nil)
}
}

View File

@@ -7,7 +7,16 @@ func accessoryPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceS
return nil
}
if let replyMessageId = chatPresentationInterfaceState.interfaceState.replyMessageId {
if let forwardMessageIds = chatPresentationInterfaceState.interfaceState.forwardMessageIds {
if let forwardPanelNode = currentPanel as? ForwardAccessoryPanelNode, forwardPanelNode.messageIds == forwardMessageIds {
forwardPanelNode.interfaceInteraction = interfaceInteraction
return forwardPanelNode
} else {
let panelNode = ForwardAccessoryPanelNode(account: account, messageIds: forwardMessageIds)
panelNode.interfaceInteraction = interfaceInteraction
return panelNode
}
} else if let replyMessageId = chatPresentationInterfaceState.interfaceState.replyMessageId {
if let replyPanelNode = currentPanel as? ReplyAccessoryPanelNode, replyPanelNode.messageId == replyMessageId {
replyPanelNode.interfaceInteraction = interfaceInteraction
return replyPanelNode

View File

@@ -4,202 +4,14 @@ import SwiftSignalKit
import Display
import TelegramCore
enum ChatListMessageViewPosition: Equatable {
case Tail(count: Int)
case Around(index: MessageIndex, anchorIndex: MessageIndex, scrollPosition: ListViewScrollPosition?)
}
func ==(lhs: ChatListMessageViewPosition, rhs: ChatListMessageViewPosition) -> Bool {
switch lhs {
case let .Tail(lhsCount):
switch rhs {
case let .Tail(rhsCount) where lhsCount == rhsCount:
return true
default:
return false
}
case let .Around(lhsId, lhsAnchorIndex, lhsScrollPosition):
switch rhs {
case let .Around(rhsId, rhsAnchorIndex, rhsScrollPosition) where lhsId == rhsId && lhsAnchorIndex == rhsAnchorIndex && lhsScrollPosition == rhsScrollPosition:
return true
default:
return false
}
}
}
private enum ChatListControllerEntryId: Hashable, CustomStringConvertible {
case Search
case Hole(Int64)
case PeerId(Int64)
var hashValue: Int {
switch self {
case .Search:
return 0
case let .Hole(peerId):
return peerId.hashValue
case let .PeerId(peerId):
return peerId.hashValue
}
}
var description: String {
switch self {
case .Search:
return "search"
case let .Hole(value):
return "hole(\(value))"
case let .PeerId(value):
return "peerId(\(value))"
}
}
}
private func <(lhs: ChatListControllerEntryId, rhs: ChatListControllerEntryId) -> Bool {
return lhs.hashValue < rhs.hashValue
}
private func ==(lhs: ChatListControllerEntryId, rhs: ChatListControllerEntryId) -> Bool {
switch lhs {
case .Search:
switch rhs {
case .Search:
return true
default:
return false
}
case let .Hole(lhsId):
switch rhs {
case .Hole(lhsId):
return true
default:
return false
}
case let .PeerId(lhsId):
switch rhs {
case let .PeerId(rhsId):
return lhsId == rhsId
default:
return false
}
}
}
private enum ChatListControllerEntry: Comparable, Identifiable {
case SearchEntry
case MessageEntry(Message, CombinedPeerReadState?, PeerNotificationSettings?)
case HoleEntry(ChatListHole)
case Nothing(MessageIndex)
var index: MessageIndex {
switch self {
case .SearchEntry:
return MessageIndex.absoluteUpperBound()
case let .MessageEntry(message, _, _):
return MessageIndex(message)
case let .HoleEntry(hole):
return hole.index
case let .Nothing(index):
return index
}
}
var stableId: ChatListControllerEntryId {
switch self {
case .SearchEntry:
return .Search
case let .MessageEntry(message, _, _):
return .PeerId(message.id.peerId.toInt64())
case let .HoleEntry(hole):
return .Hole(Int64(hole.index.id.id))
case let .Nothing(index):
return .PeerId(index.id.peerId.toInt64())
}
}
}
private func <(lhs: ChatListControllerEntry, rhs: ChatListControllerEntry) -> Bool {
return lhs.index < rhs.index
}
private func ==(lhs: ChatListControllerEntry, rhs: ChatListControllerEntry) -> Bool {
switch lhs {
case .SearchEntry:
switch rhs {
case .SearchEntry:
return true
default:
return false
}
case let .MessageEntry(lhsMessage, lhsUnreadCount, lhsNotificationSettings):
switch rhs {
case let .MessageEntry(rhsMessage, rhsUnreadCount, rhsNotificationSettings):
if lhsMessage.id != rhsMessage.id || lhsMessage.flags != rhsMessage.flags || lhsUnreadCount != rhsUnreadCount {
return false
}
if let lhsNotificationSettings = lhsNotificationSettings, let rhsNotificationSettings = rhsNotificationSettings {
if !lhsNotificationSettings.isEqual(to: rhsNotificationSettings) {
return false
}
} else if (lhsNotificationSettings != nil) != (rhsNotificationSettings != nil) {
return false
}
return true
default:
break
}
case let .HoleEntry(lhsHole):
switch rhs {
case let .HoleEntry(rhsHole):
return lhsHole == rhsHole
default:
return false
}
case let .Nothing(lhsIndex):
switch rhs {
case let .Nothing(rhsIndex):
return lhsIndex == rhsIndex
default:
return false
}
}
return false
}
extension ChatListEntry: Identifiable {
public var stableId: Int64 {
return self.index.id.peerId.toInt64()
}
}
private final class ChatListOpaqueTransactionState {
let chatListViewAndEntries: (ChatListView, [ChatListControllerEntry])
init(chatListViewAndEntries: (ChatListView, [ChatListControllerEntry])) {
self.chatListViewAndEntries = chatListViewAndEntries
}
}
public class ChatListController: ViewController {
let account: Account
private var chatListViewAndEntries: (ChatListView, [ChatListControllerEntry])?
var chatListPosition: ChatListMessageViewPosition?
let chatListDisposable: MetaDisposable = MetaDisposable()
let messageViewQueue = Queue()
let messageViewTransactionQueue = ListViewTransactionQueue()
var settingView = false
private let account: Account
let openMessageFromSearchDisposable: MetaDisposable = MetaDisposable()
var chatListDisplayNode: ChatListControllerNode {
get {
private var chatListDisplayNode: ChatListControllerNode {
return super.displayNode as! ChatListControllerNode
}
}
public init(account: Account) {
self.account = account
@@ -216,47 +28,39 @@ public class ChatListController: ViewController {
self.scrollToTop = { [weak self] in
if let strongSelf = self {
if let (view, _) = strongSelf.chatListViewAndEntries, view.laterIndex == nil {
strongSelf.chatListDisplayNode.listView.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous], scrollToItem: ListViewScrollToItem(index: 0, position: .Top, animated: true, curve: .Default, directionHint: .Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
} else {
strongSelf.setMessageViewPosition(.Around(index: MessageIndex.absoluteUpperBound(), anchorIndex: MessageIndex.absoluteUpperBound(), scrollPosition: .Top), hint: "later", force: true)
strongSelf.chatListDisplayNode.chatListNode.scrollToLatest()
}
}
}
self.setMessageViewPosition(.Tail(count: 50), hint: "initial", force: false)
}
required public init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
self.chatListDisposable.dispose()
self.openMessageFromSearchDisposable.dispose()
}
override public func loadDisplayNode() {
self.displayNode = ChatListControllerNode(account: self.account)
self.chatListDisplayNode.listView.displayedItemRangeChanged = { [weak self] range, transactionOpaqueState in
if let strongSelf = self, !strongSelf.settingView {
if let range = range.loadedRange, let (view, _) = (transactionOpaqueState as? ChatListOpaqueTransactionState)?.chatListViewAndEntries {
if range.firstIndex < 5 && view.laterIndex != nil {
strongSelf.setMessageViewPosition(.Around(index: view.entries[view.entries.count - 1].index, anchorIndex: MessageIndex.absoluteUpperBound(), scrollPosition: nil), hint: "later", force: false)
} else if range.firstIndex >= 5 && range.lastIndex >= view.entries.count - 5 && view.earlierIndex != nil {
strongSelf.setMessageViewPosition(.Around(index: view.entries[0].index, anchorIndex: MessageIndex.absoluteUpperBound(), scrollPosition: nil), hint: "earlier", force: false)
}
}
}
}
self.chatListDisplayNode.navigationBar = self.navigationBar
self.chatListDisplayNode.requestDeactivateSearch = { [weak self] in
self?.deactivateSearch()
}
self.chatListDisplayNode.chatListNode.activateSearch = { [weak self] in
self?.activateSearch()
}
self.chatListDisplayNode.chatListNode.peerSelected = { [weak self] peerId in
if let strongSelf = self {
(strongSelf.navigationController as? NavigationController)?.pushViewController(ChatController(account: strongSelf.account, peerId: peerId))
strongSelf.chatListDisplayNode.chatListNode.clearHighlightAnimated(true)
}
}
self.chatListDisplayNode.requestOpenMessageFromSearch = { [weak self] peer, messageId in
if let strongSelf = self {
let storedPeer = strongSelf.account.postbox.modify { modifier -> Void in
@@ -283,56 +87,6 @@ public class ChatListController: ViewController {
self.displayNodeDidLoad()
}
private func setMessageViewPosition(_ position: ChatListMessageViewPosition, hint: String, force: Bool) {
if self.chatListPosition == nil || self.chatListPosition! != position || force {
let signal: Signal<(ChatListView, ViewUpdateType), NoError>
self.chatListPosition = position
var scrollPosition: (MessageIndex, ListViewScrollPosition, ListViewScrollToItemDirectionHint)?
switch position {
case let .Tail(count):
signal = self.account.postbox.tailChatListView(count)
case let .Around(index, _, position):
trace("request around \(index.id.id) \(hint)")
signal = self.account.postbox.aroundChatListView(index, count: 80)
if let position = position {
var directionHint: ListViewScrollToItemDirectionHint = .Up
if let visibleItemRange = self.chatListDisplayNode.listView.displayedItemRange.loadedRange, let (_, entries) = self.chatListViewAndEntries {
if visibleItemRange.firstIndex >= 0 && visibleItemRange.firstIndex < entries.count {
if entries[visibleItemRange.firstIndex].index < index {
directionHint = .Up
} else {
directionHint = .Down
}
}
}
scrollPosition = (index, position, directionHint)
}
}
var firstTime = true
chatListDisposable.set((
signal |> deliverOnMainQueue
).start(next: {[weak self] (view, updateType) in
if let strongSelf = self {
let animated: Bool
switch updateType {
case .Generic:
animated = !firstTime
case .FillHole:
animated = false
case .InitialUnread:
animated = false
case .UpdateVisible:
animated = false
}
strongSelf.setPeerView(view, firstTime: strongSelf.chatListViewAndEntries == nil, scrollPosition: firstTime ? scrollPosition : nil, animated: animated)
firstTime = false
}
}))
}
}
override public func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
}
@@ -341,179 +95,6 @@ public class ChatListController: ViewController {
super.viewDidDisappear(animated)
}
private func chatListControllerEntries(_ view: ChatListView) -> [ChatListControllerEntry] {
var result: [ChatListControllerEntry] = []
for entry in view.entries {
switch entry {
case let .MessageEntry(message, combinedReadState, notificationSettings):
result.append(.MessageEntry(message, combinedReadState, notificationSettings))
case let .HoleEntry(hole):
result.append(.HoleEntry(hole))
case let .Nothing(index):
result.append(.Nothing(index))
}
}
if view.laterIndex == nil {
result.append(.SearchEntry)
}
return result
}
private func setPeerView(_ view: ChatListView, firstTime: Bool, scrollPosition: (MessageIndex, ListViewScrollPosition, ListViewScrollToItemDirectionHint)?, animated: Bool) {
self.messageViewTransactionQueue.addTransaction { [weak self] completed in
if let strongSelf = self {
strongSelf.settingView = true
let currentEntries = strongSelf.chatListViewAndEntries?.1 ?? []
let viewEntries = strongSelf.chatListControllerEntries(view)
strongSelf.messageViewQueue.async {
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: currentEntries, rightList: viewEntries)
//let (deleteIndices, indicesAndItems) = mergeListsStable(leftList: currentEntries, rightList: viewEntries)
//let updateIndices: [(Int, ChatListControllerEntry)] = []
Queue.mainQueue().async {
var adjustedDeleteIndices: [ListViewDeleteItem] = []
let previousCount = currentEntries.count
if deleteIndices.count != 0 {
for index in deleteIndices {
adjustedDeleteIndices.append(ListViewDeleteItem(index: previousCount - 1 - index, directionHint: nil))
}
}
let updatedCount = viewEntries.count
var maxAnimatedInsertionIndex = -1
if animated {
for (index, _, _) in indicesAndItems.sorted(by: { $0.0 > $1.0 }) {
let adjustedIndex = updatedCount - 1 - index
if adjustedIndex == maxAnimatedInsertionIndex + 1 {
maxAnimatedInsertionIndex += 1
}
}
}
var adjustedIndicesAndItems: [ListViewInsertItem] = []
for (index, entry, previousIndex) in indicesAndItems {
let adjustedIndex = updatedCount - 1 - index
var adjustedPreviousIndex: Int?
if let previousIndex = previousIndex {
adjustedPreviousIndex = previousCount - 1 - previousIndex
}
var directionHint: ListViewItemOperationDirectionHint?
if maxAnimatedInsertionIndex >= 0 && adjustedIndex <= maxAnimatedInsertionIndex {
directionHint = .Down
}
switch entry {
case .SearchEntry:
adjustedIndicesAndItems.append(ListViewInsertItem(index: updatedCount - 1 - index, previousIndex: adjustedPreviousIndex, item: ChatListSearchItem(placeholder: "Search for messages or users", activate: { [weak self] in
self?.activateSearch()
}), directionHint: directionHint))
case let .MessageEntry(message, combinedReadState, notificationSettings):
adjustedIndicesAndItems.append(ListViewInsertItem(index: adjustedIndex, previousIndex: adjustedPreviousIndex, item: ChatListItem(account: strongSelf.account, message: message, combinedReadState: combinedReadState, notificationSettings: notificationSettings, action: { [weak self] message in
if let strongSelf = self {
strongSelf.entrySelected(entry)
strongSelf.chatListDisplayNode.listView.clearHighlightAnimated(true)
}
}), directionHint: directionHint))
case .HoleEntry:
adjustedIndicesAndItems.append(ListViewInsertItem(index: updatedCount - 1 - index, previousIndex: adjustedPreviousIndex, item: ChatListHoleItem(), directionHint: directionHint))
case .Nothing:
adjustedIndicesAndItems.append(ListViewInsertItem(index: updatedCount - 1 - index, previousIndex: adjustedPreviousIndex, item: ChatListEmptyItem(), directionHint: directionHint))
}
}
var adjustedUpdateItems: [ListViewUpdateItem] = []
for (index, entry, previousIndex) in updateIndices {
let adjustedIndex = updatedCount - 1 - index
let adjustedPreviousIndex = previousCount - 1 - previousIndex
let directionHint: ListViewItemOperationDirectionHint? = nil
switch entry {
case .SearchEntry:
adjustedUpdateItems.append(ListViewUpdateItem(index: adjustedIndex, previousIndex: adjustedPreviousIndex, item: ChatListSearchItem(placeholder: "Search for messages or users", activate: { [weak self] in
self?.activateSearch()
}), directionHint: directionHint))
case let .MessageEntry(message, combinedReadState, notificationSettings):
adjustedUpdateItems.append(ListViewUpdateItem(index: adjustedIndex, previousIndex: adjustedPreviousIndex, item: ChatListItem(account: strongSelf.account, message: message, combinedReadState: combinedReadState, notificationSettings: notificationSettings, action: { [weak self] message in
if let strongSelf = self {
strongSelf.entrySelected(entry)
strongSelf.chatListDisplayNode.listView.clearHighlightAnimated(true)
}
}), directionHint: directionHint))
case .HoleEntry:
adjustedUpdateItems.append(ListViewUpdateItem(index: adjustedIndex, previousIndex: adjustedPreviousIndex, item: ChatListHoleItem(), directionHint: directionHint))
case .Nothing:
adjustedUpdateItems.append(ListViewUpdateItem(index: adjustedIndex, previousIndex: adjustedPreviousIndex, item: ChatListEmptyItem(), directionHint: directionHint))
}
}
if !adjustedDeleteIndices.isEmpty || !adjustedIndicesAndItems.isEmpty || !adjustedUpdateItems.isEmpty || scrollPosition != nil {
var options: ListViewDeleteAndInsertOptions = []
if firstTime {
} else {
let _ = options.insert(.AnimateAlpha)
if animated {
let _ = options.insert(.AnimateInsertion)
}
}
var scrollToItem: ListViewScrollToItem?
if let (itemIndex, itemPosition, directionHint) = scrollPosition {
var index = viewEntries.count - 1
for entry in viewEntries {
if entry.index >= itemIndex {
scrollToItem = ListViewScrollToItem(index: index, position: itemPosition, animated: true, curve: .Default, directionHint: directionHint)
break
}
index -= 1
}
if scrollToItem == nil {
var index = 0
for entry in viewEntries.reversed() {
if entry.index < itemIndex {
scrollToItem = ListViewScrollToItem(index: index, position: itemPosition, animated: true, curve: .Default, directionHint: directionHint)
break
}
index += 1
}
}
}
strongSelf.chatListDisplayNode.listView.transaction(deleteIndices: adjustedDeleteIndices, insertIndicesAndItems: adjustedIndicesAndItems, updateIndicesAndItems: adjustedUpdateItems, options: options, scrollToItem: scrollToItem, updateOpaqueState: ChatListOpaqueTransactionState(chatListViewAndEntries: (view, viewEntries)), completion: { [weak self] _ in
if let strongSelf = self {
strongSelf.ready.set(single(true, NoError.self))
strongSelf.settingView = false
completed()
}
})
} else {
strongSelf.ready.set(single(true, NoError.self))
strongSelf.settingView = false
completed()
}
strongSelf.chatListViewAndEntries = (view, viewEntries)
}
}
} else {
completed()
}
}
}
private func entrySelected(_ entry: ChatListControllerEntry) {
if case let .MessageEntry(message, _, _) = entry {
//(self.navigationController as? NavigationController)?.pushViewController(PeerMediaCollectionController(account: self.account, peerId: message.id.peerId))
(self.navigationController as? NavigationController)?.pushViewController(ChatController(account: self.account, peerId: message.id.peerId))
}
}
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
super.containerLayoutUpdated(layout, transition: transition)

View File

@@ -7,7 +7,7 @@ import TelegramCore
class ChatListControllerNode: ASDisplayNode {
private let account: Account
let listView: ListView
let chatListNode: ChatListNode
var navigationBar: NavigationBar?
private var searchDisplayController: SearchDisplayController?
@@ -20,13 +20,13 @@ class ChatListControllerNode: ASDisplayNode {
init(account: Account) {
self.account = account
self.listView = ListView()
self.chatListNode = ChatListNode(account: account, mode: .chatList)
super.init(viewBlock: {
return UITracingLayerView()
}, didLoad: nil)
self.addSubnode(self.listView)
self.addSubnode(self.chatListNode)
}
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
@@ -35,8 +35,8 @@ class ChatListControllerNode: ASDisplayNode {
var insets = layout.insets(options: [.input])
insets.top += max(navigationBarHeight, layout.insets(options: [.statusBar]).top)
self.listView.bounds = CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: layout.size.height)
self.listView.position = CGPoint(x: layout.size.width / 2.0, y: layout.size.height / 2.0)
self.chatListNode.bounds = CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: layout.size.height)
self.chatListNode.position = CGPoint(x: layout.size.width / 2.0, y: layout.size.height / 2.0)
var duration: Double = 0.0
var curve: UInt = 0
@@ -63,7 +63,7 @@ class ChatListControllerNode: ASDisplayNode {
let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: layout.size, insets: insets, duration: duration, curve: listViewCurve)
self.listView.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: nil, updateSizeAndInsets: updateSizeAndInsets, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
self.chatListNode.updateLayout(transition: transition, updateSizeAndInsets: updateSizeAndInsets)
if let searchDisplayController = self.searchDisplayController {
searchDisplayController.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: transition)
@@ -76,7 +76,7 @@ class ChatListControllerNode: ASDisplayNode {
}
var maybePlaceholderNode: SearchBarPlaceholderNode?
self.listView.forEachItemNode { node in
self.chatListNode.forEachItemNode { node in
if let node = node as? ChatListSearchItemNode {
maybePlaceholderNode = node.searchBarNode
}
@@ -111,7 +111,7 @@ class ChatListControllerNode: ASDisplayNode {
func deactivateSearch() {
if let searchDisplayController = self.searchDisplayController {
var maybePlaceholderNode: SearchBarPlaceholderNode?
self.listView.forEachItemNode { node in
self.chatListNode.forEachItemNode { node in
if let node = node as? ChatListSearchItemNode {
maybePlaceholderNode = node.searchBarNode
}

View File

@@ -476,7 +476,7 @@ class ChatListItemNode: ListViewItemNode {
}
}
override func animateInsertion(_ currentTimestamp: Double, duration: Double) {
override func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) {
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration * 0.5)
}

View File

@@ -0,0 +1,292 @@
import Foundation
import Display
import AsyncDisplayKit
import SwiftSignalKit
import TelegramCore
import Postbox
enum ChatListNodeMode {
case chatList
case peers
}
struct ChatListNodeListViewTransition {
let chatListView: ChatListNodeView
let deleteItems: [ListViewDeleteItem]
let insertItems: [ListViewInsertItem]
let updateItems: [ListViewUpdateItem]
let options: ListViewDeleteAndInsertOptions
let scrollToItem: ListViewScrollToItem?
let stationaryItemRange: (Int, Int)?
}
final class ChatListNodeInteraction {
let activateSearch: () -> Void
let peerSelected: (PeerId) -> Void
init(activateSearch: @escaping () -> Void, peerSelected: @escaping (PeerId) -> Void) {
self.activateSearch = activateSearch
self.peerSelected = peerSelected
}
}
private func mappedInsertEntries(account: Account, nodeInteraction: ChatListNodeInteraction, mode: ChatListNodeMode, entries: [ChatListNodeViewTransitionInsertEntry]) -> [ListViewInsertItem] {
return entries.map { entry -> ListViewInsertItem in
switch entry.entry {
case .SearchEntry:
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListSearchItem(placeholder: "Search for messages or users", activate: {
nodeInteraction.activateSearch()
}), directionHint: entry.directionHint)
case let .MessageEntry(message, combinedReadState, notificationSettings):
switch mode {
case .chatList:
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListItem(account: account, message: message, combinedReadState: combinedReadState, notificationSettings: notificationSettings, action: { _ in
nodeInteraction.peerSelected(message.id.peerId)
}), directionHint: entry.directionHint)
case .peers:
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ContactsPeerItem(account: account, peer: message.peers[message.id.peerId], presence: nil, index: nil, action: { _ in
nodeInteraction.peerSelected(message.id.peerId)
}), directionHint: entry.directionHint)
}
case .HoleEntry:
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListHoleItem(), directionHint: entry.directionHint)
case .Nothing:
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListEmptyItem(), directionHint: entry.directionHint)
}
}
}
private func mappedUpdateEntries(account: Account, nodeInteraction: ChatListNodeInteraction, mode: ChatListNodeMode, entries: [ChatListNodeViewTransitionUpdateEntry]) -> [ListViewUpdateItem] {
return entries.map { entry -> ListViewUpdateItem in
switch entry.entry {
case .SearchEntry:
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListSearchItem(placeholder: "Search for messages or users", activate: {
nodeInteraction.activateSearch()
}), directionHint: entry.directionHint)
case let .MessageEntry(message, combinedReadState, notificationSettings):
switch mode {
case .chatList:
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListItem(account: account, message: message, combinedReadState: combinedReadState, notificationSettings: notificationSettings, action: { _ in
nodeInteraction.peerSelected(message.id.peerId)
}), directionHint: entry.directionHint)
case .peers:
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ContactsPeerItem(account: account, peer: message.peers[message.id.peerId], presence: nil, index: nil, action: { _ in
nodeInteraction.peerSelected(message.id.peerId)
}), directionHint: entry.directionHint)
}
case .HoleEntry:
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListHoleItem(), directionHint: entry.directionHint)
case .Nothing:
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListEmptyItem(), directionHint: entry.directionHint)
}
}
}
private func mappedChatListNodeViewListTransition(account: Account, nodeInteraction: ChatListNodeInteraction, mode: ChatListNodeMode, transition: ChatListNodeViewTransition) -> ChatListNodeListViewTransition {
return ChatListNodeListViewTransition(chatListView: transition.chatListView, deleteItems: transition.deleteItems, insertItems: mappedInsertEntries(account: account, nodeInteraction: nodeInteraction, mode: mode, entries: transition.insertEntries), updateItems: mappedUpdateEntries(account: account, nodeInteraction: nodeInteraction, mode: mode, entries: transition.updateEntries), options: transition.options, scrollToItem: transition.scrollToItem, stationaryItemRange: transition.stationaryItemRange)
}
private final class ChatListOpaqueTransactionState {
let chatListView: ChatListNodeView
init(chatListView: ChatListNodeView) {
self.chatListView = chatListView
}
}
final class ChatListNode: ListView {
private let _ready = ValuePromise<Bool>()
private var didSetReady = false
var ready: Signal<Bool, NoError> {
return _ready.get()
}
var peerSelected: ((PeerId) -> Void)?
var activateSearch: (() -> Void)?
private let viewProcessingQueue = Queue()
private var chatListView: ChatListNodeView?
private var dequeuedInitialTransitionOnLayout = false
private var enqueuedTransition: (ChatListNodeListViewTransition, () -> Void)?
private var currentLocation: ChatListNodeLocation?
private let chatListLocation = ValuePromise<ChatListNodeLocation>()
private let chatListDisposable = MetaDisposable()
init(account: Account, mode: ChatListNodeMode) {
super.init()
let nodeInteraction = ChatListNodeInteraction(activateSearch: { [weak self] in
if let strongSelf = self, let activateSearch = strongSelf.activateSearch {
activateSearch()
}
}, peerSelected: { [weak self] peerId in
if let strongSelf = self, let peerSelected = strongSelf.peerSelected {
peerSelected(peerId)
}
})
let viewProcessingQueue = self.viewProcessingQueue
let chastListViewUpdate = self.chatListLocation.get()
|> distinctUntilChanged
|> mapToSignal { location in
return chatListViewForLocation(location, account: account)
}
let previousView = Atomic<ChatListNodeView?>(value: nil)
let chatListNodeViewTransition = chastListViewUpdate |> mapToQueue { [weak self] update -> Signal<ChatListNodeListViewTransition, NoError> in
let processedView = ChatListNodeView(originalView: update.view, filteredEntries: chatListNodeEntriesForView(update.view))
let previous = previousView.swap(processedView)
let reason: ChatListNodeViewTransitionReason
var prepareOnMainQueue = false
var previousWasEmptyOrSingleHole = false
if let previous = previous {
if previous.filteredEntries.count == 1 {
if case .HoleEntry = previous.filteredEntries[0] {
previousWasEmptyOrSingleHole = true
}
}
} else {
previousWasEmptyOrSingleHole = true
}
if previousWasEmptyOrSingleHole {
reason = .initial
if previous == nil {
prepareOnMainQueue = true
}
} else {
switch update.type {
case .InitialUnread:
reason = .initial
prepareOnMainQueue = true
case .Generic:
reason = .interactiveChanges
case .UpdateVisible:
reason = .reload
case .FillHole:
reason = .reload
}
}
return preparedChatListNodeViewTransition(from: previous, to: processedView, reason: reason, account: account, scrollPosition: update.scrollPosition)
|> map({ mappedChatListNodeViewListTransition(account: account, nodeInteraction: nodeInteraction, mode: mode, transition: $0) })
|> runOn(prepareOnMainQueue ? Queue.mainQueue() : viewProcessingQueue)
}
let appliedTransition = chatListNodeViewTransition |> deliverOnMainQueue |> mapToQueue { [weak self] transition -> Signal<Void, NoError> in
if let strongSelf = self {
return strongSelf.enqueueTransition(transition)
}
return .complete()
}
self.displayedItemRangeChanged = { [weak self] range, transactionOpaqueState in
if let strongSelf = self, let range = range.loadedRange, let view = (transactionOpaqueState as? ChatListOpaqueTransactionState)?.chatListView.originalView {
var location: ChatListNodeLocation?
if range.firstIndex < 5 && view.laterIndex != nil {
location = .navigation(index: view.entries[view.entries.count - 1].index)
} else if range.firstIndex >= 5 && range.lastIndex >= view.entries.count - 5 && view.earlierIndex != nil {
location = .navigation(index: view.entries[0].index)
}
if let location = location, location != strongSelf.currentLocation {
strongSelf.currentLocation = location
strongSelf.chatListLocation.set(location)
}
}
}
self.chatListDisposable.set(appliedTransition.start())
let initialLocation: ChatListNodeLocation = .initial(count: 50)
self.currentLocation = initialLocation
self.chatListLocation.set(initialLocation)
}
deinit {
self.chatListDisposable.dispose()
}
private func enqueueTransition(_ transition: ChatListNodeListViewTransition) -> Signal<Void, NoError> {
return Signal { [weak self] subscriber in
if let strongSelf = self {
if let _ = strongSelf.enqueuedTransition {
preconditionFailure()
}
strongSelf.enqueuedTransition = (transition, {
subscriber.putCompletion()
})
if strongSelf.isNodeLoaded {
strongSelf.dequeueTransition()
} else {
if !strongSelf.didSetReady {
strongSelf.didSetReady = true
strongSelf._ready.set(true)
}
}
} else {
subscriber.putCompletion()
}
return EmptyDisposable
} |> runOn(Queue.mainQueue())
}
private func dequeueTransition() {
if let (transition, completion) = self.enqueuedTransition {
self.enqueuedTransition = nil
let completion: (ListViewDisplayedItemRange) -> Void = { [weak self] visibleRange in
if let strongSelf = self {
strongSelf.chatListView = transition.chatListView
/*if let range = visibleRange.loadedRange {
strongSelf.account.postbox.updateMessageHistoryViewVisibleRange(transition.historyView.originalView.id, earliestVisibleIndex: transition.historyView.filteredEntries[transition.historyView.filteredEntries.count - 1 - range.lastIndex].index, latestVisibleIndex: transition.historyView.filteredEntries[transition.historyView.filteredEntries.count - 1 - range.firstIndex].index)
if let visible = visibleRange.visibleRange {
if let messageId = maxIncomingMessageIdForEntries(transition.historyView.filteredEntries, indexRange: (transition.historyView.filteredEntries.count - 1 - visible.lastIndex, transition.historyView.filteredEntries.count - 1 - visible.firstIndex)) {
strongSelf.updateMaxVisibleReadIncomingMessageId(messageId)
}
}
}*/
if !strongSelf.didSetReady {
strongSelf.didSetReady = true
strongSelf._ready.set(true)
}
completion()
}
}
self.transaction(deleteIndices: transition.deleteItems, insertIndicesAndItems: transition.insertItems, updateIndicesAndItems: transition.updateItems, options: transition.options, scrollToItem: transition.scrollToItem, stationaryItemRange: transition.stationaryItemRange, updateOpaqueState: ChatListOpaqueTransactionState(chatListView: transition.chatListView), completion: completion)
}
}
func updateLayout(transition: ContainedViewLayoutTransition, updateSizeAndInsets: ListViewUpdateSizeAndInsets) {
self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: nil, updateSizeAndInsets: updateSizeAndInsets, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
if !self.dequeuedInitialTransitionOnLayout {
self.dequeuedInitialTransitionOnLayout = true
self.dequeueTransition()
}
}
func scrollToLatest() {
if let view = self.chatListView?.originalView, view.laterIndex == nil {
self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous], scrollToItem: ListViewScrollToItem(index: 0, position: .Top, animated: true, curve: .Default, directionHint: .Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
} else {
let location: ChatListNodeLocation = .scroll(index: MessageIndex.absoluteUpperBound(), sourceIndex: MessageIndex.absoluteLowerBound(), scrollPosition: .Top, animated: true)
self.currentLocation = location
self.chatListLocation.set(location)
}
}
}

View File

@@ -0,0 +1,160 @@
import Foundation
import Postbox
import TelegramCore
enum ChatListNodeEntryId: Hashable, CustomStringConvertible {
case Search
case Hole(Int64)
case PeerId(Int64)
var hashValue: Int {
switch self {
case .Search:
return 0
case let .Hole(peerId):
return peerId.hashValue
case let .PeerId(peerId):
return peerId.hashValue
}
}
var description: String {
switch self {
case .Search:
return "search"
case let .Hole(value):
return "hole(\(value))"
case let .PeerId(value):
return "peerId(\(value))"
}
}
static func <(lhs: ChatListNodeEntryId, rhs: ChatListNodeEntryId) -> Bool {
return lhs.hashValue < rhs.hashValue
}
static func ==(lhs: ChatListNodeEntryId, rhs: ChatListNodeEntryId) -> Bool {
switch lhs {
case .Search:
switch rhs {
case .Search:
return true
default:
return false
}
case let .Hole(lhsId):
switch rhs {
case .Hole(lhsId):
return true
default:
return false
}
case let .PeerId(lhsId):
switch rhs {
case let .PeerId(rhsId):
return lhsId == rhsId
default:
return false
}
}
}
}
enum ChatListNodeEntry: Comparable, Identifiable {
case SearchEntry
case MessageEntry(Message, CombinedPeerReadState?, PeerNotificationSettings?)
case HoleEntry(ChatListHole)
case Nothing(MessageIndex)
var index: MessageIndex {
switch self {
case .SearchEntry:
return MessageIndex.absoluteUpperBound()
case let .MessageEntry(message, _, _):
return MessageIndex(message)
case let .HoleEntry(hole):
return hole.index
case let .Nothing(index):
return index
}
}
var stableId: ChatListNodeEntryId {
switch self {
case .SearchEntry:
return .Search
case let .MessageEntry(message, _, _):
return .PeerId(message.id.peerId.toInt64())
case let .HoleEntry(hole):
return .Hole(Int64(hole.index.id.id))
case let .Nothing(index):
return .PeerId(index.id.peerId.toInt64())
}
}
static func <(lhs: ChatListNodeEntry, rhs: ChatListNodeEntry) -> Bool {
return lhs.index < rhs.index
}
static func ==(lhs: ChatListNodeEntry, rhs: ChatListNodeEntry) -> Bool {
switch lhs {
case .SearchEntry:
switch rhs {
case .SearchEntry:
return true
default:
return false
}
case let .MessageEntry(lhsMessage, lhsUnreadCount, lhsNotificationSettings):
switch rhs {
case let .MessageEntry(rhsMessage, rhsUnreadCount, rhsNotificationSettings):
if lhsMessage.id != rhsMessage.id || lhsMessage.flags != rhsMessage.flags || lhsUnreadCount != rhsUnreadCount {
return false
}
if let lhsNotificationSettings = lhsNotificationSettings, let rhsNotificationSettings = rhsNotificationSettings {
if !lhsNotificationSettings.isEqual(to: rhsNotificationSettings) {
return false
}
} else if (lhsNotificationSettings != nil) != (rhsNotificationSettings != nil) {
return false
}
return true
default:
break
}
case let .HoleEntry(lhsHole):
switch rhs {
case let .HoleEntry(rhsHole):
return lhsHole == rhsHole
default:
return false
}
case let .Nothing(lhsIndex):
switch rhs {
case let .Nothing(rhsIndex):
return lhsIndex == rhsIndex
default:
return false
}
}
return false
}
}
func chatListNodeEntriesForView(_ view: ChatListView) -> [ChatListNodeEntry] {
var result: [ChatListNodeEntry] = []
for entry in view.entries {
switch entry {
case let .MessageEntry(message, combinedReadState, notificationSettings):
result.append(.MessageEntry(message, combinedReadState, notificationSettings))
case let .HoleEntry(hole):
result.append(.HoleEntry(hole))
case let .Nothing(index):
result.append(.Nothing(index))
}
}
if view.laterIndex == nil {
result.append(.SearchEntry)
}
return result
}

View File

@@ -0,0 +1,69 @@
import Foundation
import Postbox
import TelegramCore
import SwiftSignalKit
import Display
enum ChatListNodeLocation: Equatable {
case initial(count: Int)
case navigation(index: MessageIndex)
case scroll(index: MessageIndex, sourceIndex: MessageIndex, scrollPosition: ListViewScrollPosition, animated: Bool)
static func ==(lhs: ChatListNodeLocation, rhs: ChatListNodeLocation) -> Bool {
switch lhs {
case let .navigation(index):
switch rhs {
case .navigation(index):
return true
default:
return false
}
default:
return false
}
}
}
struct ChatListNodeViewUpdate {
let view: ChatListView
let type: ViewUpdateType
let scrollPosition: ChatListNodeViewScrollPosition?
}
func chatListViewForLocation(_ location: ChatListNodeLocation, account: Account) -> Signal<ChatListNodeViewUpdate, NoError> {
switch location {
case let .initial(count):
let signal: Signal<(ChatListView, ViewUpdateType), NoError>
signal = account.postbox.tailChatListView(count)
return signal |> map { view, updateType -> ChatListNodeViewUpdate in
return ChatListNodeViewUpdate(view: view, type: updateType, scrollPosition: nil)
}
case let .navigation(index):
var first = true
return account.postbox.aroundChatListView(index, count: 80) |> map { view, updateType -> ChatListNodeViewUpdate in
let genericType: ViewUpdateType
if first {
first = false
genericType = ViewUpdateType.UpdateVisible
} else {
genericType = updateType
}
return ChatListNodeViewUpdate(view: view, type: genericType, scrollPosition: nil)
}
case let .scroll(index, sourceIndex, scrollPosition, animated):
let directionHint: ListViewScrollToItemDirectionHint = sourceIndex > index ? .Down : .Up
let chatScrollPosition: ChatListNodeViewScrollPosition = .index(index: index, position: scrollPosition, directionHint: directionHint, animated: animated)
var first = true
return account.postbox.aroundChatListView(index, count: 80) |> map { view, updateType -> ChatListNodeViewUpdate in
let genericType: ViewUpdateType
let scrollPosition: ChatListNodeViewScrollPosition? = first ? chatScrollPosition : nil
if first {
first = false
genericType = ViewUpdateType.UpdateVisible
} else {
genericType = updateType
}
return ChatListNodeViewUpdate(view: view, type: genericType, scrollPosition: scrollPosition)
}
}
}

View File

@@ -0,0 +1,170 @@
import Foundation
import Postbox
import TelegramCore
import SwiftSignalKit
import Display
struct ChatListNodeView {
let originalView: ChatListView
let filteredEntries: [ChatListNodeEntry]
}
enum ChatListNodeViewTransitionReason {
case initial
case interactiveChanges
case holeChanges(filledHoleDirections: [MessageIndex: HoleFillDirection], removeHoleDirections: [MessageIndex: HoleFillDirection])
case reload
}
struct ChatListNodeViewTransitionInsertEntry {
let index: Int
let previousIndex: Int?
let entry: ChatListNodeEntry
let directionHint: ListViewItemOperationDirectionHint?
}
struct ChatListNodeViewTransitionUpdateEntry {
let index: Int
let previousIndex: Int
let entry: ChatListNodeEntry
let directionHint: ListViewItemOperationDirectionHint?
}
struct ChatListNodeViewTransition {
let chatListView: ChatListNodeView
let deleteItems: [ListViewDeleteItem]
let insertEntries: [ChatListNodeViewTransitionInsertEntry]
let updateEntries: [ChatListNodeViewTransitionUpdateEntry]
let options: ListViewDeleteAndInsertOptions
let scrollToItem: ListViewScrollToItem?
let stationaryItemRange: (Int, Int)?
}
enum ChatListNodeViewScrollPosition {
case index(index: MessageIndex, position: ListViewScrollPosition, directionHint: ListViewScrollToItemDirectionHint, animated: Bool)
}
func preparedChatListNodeViewTransition(from fromView: ChatListNodeView?, to toView: ChatListNodeView, reason: ChatListNodeViewTransitionReason, account: Account, scrollPosition: ChatListNodeViewScrollPosition?) -> Signal<ChatListNodeViewTransition, NoError> {
return Signal { subscriber in
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromView?.filteredEntries ?? [], rightList: toView.filteredEntries)
var adjustedDeleteIndices: [ListViewDeleteItem] = []
let previousCount: Int
if let fromView = fromView {
previousCount = fromView.filteredEntries.count
} else {
previousCount = 0;
}
for index in deleteIndices {
adjustedDeleteIndices.append(ListViewDeleteItem(index: previousCount - 1 - index, directionHint: nil))
}
var adjustedIndicesAndItems: [ChatListNodeViewTransitionInsertEntry] = []
var adjustedUpdateItems: [ChatListNodeViewTransitionUpdateEntry] = []
let updatedCount = toView.filteredEntries.count
var options: ListViewDeleteAndInsertOptions = []
var maxAnimatedInsertionIndex = -1
var stationaryItemRange: (Int, Int)?
var scrollToItem: ListViewScrollToItem?
switch reason {
case .initial:
let _ = options.insert(.LowLatency)
let _ = options.insert(.Synchronous)
case .interactiveChanges:
let _ = options.insert(.AnimateAlpha)
let _ = options.insert(.AnimateInsertion)
for (index, _, _) in indicesAndItems.sorted(by: { $0.0 > $1.0 }) {
let adjustedIndex = updatedCount - 1 - index
if adjustedIndex == maxAnimatedInsertionIndex + 1 {
maxAnimatedInsertionIndex += 1
}
}
case .reload:
break
case let .holeChanges(filledHoleDirections, removeHoleDirections):
if let (_, removeDirection) = removeHoleDirections.first {
switch removeDirection {
case .LowerToUpper:
var holeIndex: MessageIndex?
for (index, _) in filledHoleDirections {
if holeIndex == nil || index < holeIndex! {
holeIndex = index
}
}
if let holeIndex = holeIndex {
for i in 0 ..< toView.filteredEntries.count {
if toView.filteredEntries[i].index >= holeIndex {
let index = toView.filteredEntries.count - 1 - (i - 1)
stationaryItemRange = (index, Int.max)
break
}
}
}
case .UpperToLower:
break
case .AroundIndex:
break
}
}
}
for (index, entry, previousIndex) in indicesAndItems {
let adjustedIndex = updatedCount - 1 - index
let adjustedPrevousIndex: Int?
if let previousIndex = previousIndex {
adjustedPrevousIndex = previousCount - 1 - previousIndex
} else {
adjustedPrevousIndex = nil
}
var directionHint: ListViewItemOperationDirectionHint?
if maxAnimatedInsertionIndex >= 0 && adjustedIndex <= maxAnimatedInsertionIndex {
directionHint = .Down
}
adjustedIndicesAndItems.append(ChatListNodeViewTransitionInsertEntry(index: adjustedIndex, previousIndex: adjustedPrevousIndex, entry: entry, directionHint: directionHint))
}
for (index, entry, previousIndex) in updateIndices {
let adjustedIndex = updatedCount - 1 - index
let adjustedPreviousIndex = previousCount - 1 - previousIndex
let directionHint: ListViewItemOperationDirectionHint? = nil
adjustedUpdateItems.append(ChatListNodeViewTransitionUpdateEntry(index: adjustedIndex, previousIndex: adjustedPreviousIndex, entry: entry, directionHint: directionHint))
}
if let scrollPosition = scrollPosition {
switch scrollPosition {
case let .index(scrollIndex, position, directionHint, animated):
var index = toView.filteredEntries.count - 1
for entry in toView.filteredEntries {
if entry.index >= scrollIndex {
scrollToItem = ListViewScrollToItem(index: index, position: position, animated: animated, curve: .Default, directionHint: directionHint)
break
}
index -= 1
}
if scrollToItem == nil {
var index = 0
for entry in toView.filteredEntries.reversed() {
if entry.index < scrollIndex {
scrollToItem = ListViewScrollToItem(index: index, position: position, animated: animated, curve: .Default, directionHint: directionHint)
break
}
index += 1
}
}
}
}
subscriber.putNext(ChatListNodeViewTransition(chatListView: toView, deleteItems: adjustedDeleteIndices, insertEntries: adjustedIndicesAndItems, updateEntries: adjustedUpdateItems, options: options, scrollToItem: scrollToItem, stationaryItemRange: stationaryItemRange))
subscriber.putCompletion()
return EmptyDisposable
}
}

View File

@@ -135,12 +135,13 @@ final class ChatMediaInputStickerGridItemNode: GridItemNode {
override func layout() {
super.layout()
let boundingSize = self.bounds.insetBy(dx: 6.0, dy: 6.0).size
let bounds = self.bounds
let boundingSize = bounds.insetBy(dx: 6.0, dy: 6.0).size
if let (_, _, mediaDimensions) = self.currentState {
let imageSize = mediaDimensions.aspectFitted(boundingSize)
self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: boundingSize, intrinsicInsets: UIEdgeInsets()))()
self.imageNode.frame = CGRect(origin: CGPoint(x: 6.0, y: 6.0), size: imageSize)
self.imageNode.frame = CGRect(origin: CGPoint(x: floor((bounds.size.width - imageSize.width) / 2.0), y: (bounds.size.height - imageSize.height) / 2.0), size: imageSize)
}
}

View File

@@ -117,8 +117,8 @@ class ChatMessageActionItemNode: ChatMessageItemView {
}
}
override func animateInsertion(_ currentTimestamp: Double, duration: Double) {
super.animateInsertion(currentTimestamp, duration: duration)
override func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) {
super.animateInsertion(currentTimestamp, duration: duration, short: short)
self.backgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
self.labelNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)

View File

@@ -177,8 +177,8 @@ class ChatMessageBubbleItemNode: ChatMessageItemView {
fatalError("init(coder:) has not been implemented")
}
override func animateInsertion(_ currentTimestamp: Double, duration: Double) {
super.animateInsertion(currentTimestamp, duration: duration)
override func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) {
super.animateInsertion(currentTimestamp, duration: duration, short: short)
self.backgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)

View File

@@ -95,11 +95,13 @@ public class ChatMessageItemView: ListViewItemNode {
}
}
override public func animateInsertion(_ currentTimestamp: Double, duration: Double) {
super.animateInsertion(currentTimestamp, duration: duration)
override public func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) {
if short {
self.layer.animateBoundsOriginYAdditive(from: -self.bounds.size.height, to: 0.0, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring)
} else {
self.transitionOffset = -self.bounds.size.height * 1.6
self.addTransitionOffsetAnimation(0.0, duration: duration, beginAt: currentTimestamp)
}
//self.layer.animateBoundsOriginYAdditive(from: -self.bounds.size.height * 1.4, to: 0.0, duration: duration)
}

View File

@@ -84,8 +84,8 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
}
}
override func animateInsertion(_ currentTimestamp: Double, duration: Double) {
super.animateInsertion(currentTimestamp, duration: duration)
override func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) {
super.animateInsertion(currentTimestamp, duration: duration, short: short)
self.imageNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
}

View File

@@ -102,8 +102,9 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
private var presentationInterfaceState = ChatPresentationInterfaceState()
private var keepSendButtonEnabled = false
var inputTextState: ChatTextInputState {
get {
if let textInputNode = self.textInputNode {
let text = textInputNode.attributedText?.string ?? ""
let selectionRange: Range<Int> = textInputNode.selectedRange.location ..< (textInputNode.selectedRange.location + textInputNode.selectedRange.length)
@@ -111,14 +112,28 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
} else {
return ChatTextInputState()
}
} set(value) {
}
func updateInputTextState(_ state: ChatTextInputState, keepSendButtonEnabled: Bool, animated: Bool) {
if !state.inputText.isEmpty && self.textInputNode == nil {
self.loadTextInputNode()
}
if let textInputNode = self.textInputNode {
self.updatingInputState = true
textInputNode.attributedText = NSAttributedString(string: value.inputText, font: Font.regular(17.0), textColor: UIColor.black)
textInputNode.selectedRange = NSMakeRange(value.selectionRange.lowerBound, value.selectionRange.count)
textInputNode.attributedText = NSAttributedString(string: state.inputText, font: Font.regular(17.0), textColor: UIColor.black)
textInputNode.selectedRange = NSMakeRange(state.selectionRange.lowerBound, state.selectionRange.count)
self.updatingInputState = false
self.keepSendButtonEnabled = keepSendButtonEnabled
self.updateTextNodeText(animated: animated)
}
}
func updateKeepSendButtonEnabled(keepSendButtonEnabled: Bool, animated: Bool) {
if keepSendButtonEnabled != self.keepSendButtonEnabled {
self.keepSendButtonEnabled = keepSendButtonEnabled
self.updateTextNodeText(animated: animated)
}
}
var text: String {
@@ -360,9 +375,19 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
@objc func editableTextNodeDidUpdateText(_ editableTextNode: ASEditableTextNode) {
if let textInputNode = self.textInputNode {
self.textPlaceholderNode.isHidden = editableTextNode.attributedText?.length ?? 0 != 0
self.interfaceInteraction?.updateTextInputState(self.inputTextState)
self.updateTextNodeText(animated: true)
}
}
if let text = self.textInputNode?.attributedText, text.length != 0 {
private func updateTextNodeText(animated: Bool) {
var hasText = false
if let textInputNode = self.textInputNode, let attributedText = textInputNode.attributedText, attributedText.length != 0 {
hasText = true
}
self.textPlaceholderNode.isHidden = hasText
if hasText || self.keepSendButtonEnabled {
if self.sendButton.alpha.isZero {
self.sendButton.alpha = 1.0
self.micButton.alpha = 0.0
@@ -380,15 +405,12 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
}
}
self.interfaceInteraction?.updateTextInputState(self.inputTextState)
let (accessoryButtonsWidth, textFieldHeight) = self.calculateTextFieldMetrics(width: self.bounds.size.width)
let panelHeight = self.panelHeight(textFieldHeight: textFieldHeight)
if !self.bounds.size.height.isEqual(to: panelHeight) {
self.updateHeight()
}
}
}
@objc func editableTextNodeDidChangeSelection(_ editableTextNode: ASEditableTextNode, fromSelectedRange: NSRange, toSelectedRange: NSRange, dueToEditing: Bool) {
if !dueToEditing && !updatingInputState {
@@ -412,10 +434,8 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
@objc func sendButtonPressed() {
let text = self.textInputNode?.attributedText?.string ?? ""
if !text.isEmpty {
self.sendMessage()
}
}
@objc func attachmentButtonPressed() {
self.displayAttachmentMenu()

View File

@@ -52,8 +52,8 @@ class ChatUnreadItemNode: ListViewItemNode {
self.scrollPositioningInsets = UIEdgeInsets(top: 5.0, left: 0.0, bottom: 5.0, right: 0.0)
}
override func animateInsertion(_ currentTimestamp: Double, duration: Double) {
super.animateInsertion(currentTimestamp, duration: duration)
override func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) {
super.animateInsertion(currentTimestamp, duration: duration, short: short)
self.backgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration)
self.labelNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration)

View File

@@ -12,14 +12,14 @@ private let statusFont = Font.regular(13.0)
class ContactsPeerItem: ListViewItem {
let account: Account
let peer: Peer
let peer: Peer?
let presence: PeerPresence?
let action: (Peer) -> Void
let selectable: Bool = true
let headerAccessoryItem: ListViewAccessoryItem?
init(account: Account, peer: Peer, presence: PeerPresence?, index: PeerNameIndex?, action: @escaping (Peer) -> Void) {
init(account: Account, peer: Peer?, presence: PeerPresence?, index: PeerNameIndex?, action: @escaping (Peer) -> Void) {
self.account = account
self.peer = peer
self.presence = presence
@@ -115,7 +115,9 @@ class ContactsPeerItem: ListViewItem {
}
func selected(listView: ListView) {
self.action(self.peer)
if let peer = self.peer {
self.action(peer)
}
}
}
@@ -130,7 +132,7 @@ class ContactsPeerItemNode: ListViewItemNode {
private let titleNode: TextNode
private let statusNode: TextNode
private var avatarState: (Account, Peer)?
private var avatarState: (Account, Peer?)?
private var peerPresenceManager: PeerPresenceStatusManager?
private var layoutParams: (ContactsPeerItem, CGFloat, Bool, Bool)?
@@ -228,8 +230,7 @@ class ContactsPeerItemNode: ListViewItemNode {
var titleAttributedString: NSAttributedString?
var statusAttributedString: NSAttributedString?
let peer = item.peer
if let peer = item.peer {
if let user = peer as? TelegramUser {
if let firstName = user.firstName, let lastName = user.lastName, !firstName.isEmpty, !lastName.isEmpty {
let string = NSMutableAttributedString()
@@ -249,14 +250,13 @@ class ContactsPeerItemNode: ListViewItemNode {
let timestamp = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970
let (string, activity) = stringAndActivityForUserPresence(presence, relativeTo: Int32(timestamp))
statusAttributedString = NSAttributedString(string: string, font: statusFont, textColor: activity ? UIColor(0x007ee5) : UIColor(0xa6a6a6))
} else {
statusAttributedString = NSAttributedString(string: "last seen recently", font: statusFont, textColor: UIColor(0xa6a6a6))
}
} else if let group = peer as? TelegramGroup {
titleAttributedString = NSAttributedString(string: group.title, font: titleBoldFont, textColor: UIColor.black)
} else if let channel = peer as? TelegramChannel {
titleAttributedString = NSAttributedString(string: channel.title, font: titleBoldFont, textColor: UIColor.black)
}
}
let (titleLayout, titleApply) = makeTitleLayout(titleAttributedString, nil, 1, .end, CGSize(width: max(0.0, width - leftInset - rightInset), height: CGFloat.infinity), nil)
@@ -264,18 +264,24 @@ class ContactsPeerItemNode: ListViewItemNode {
let nodeLayout = ListViewItemNodeLayout(contentSize: CGSize(width: width, height: 48.0), insets: UIEdgeInsets(top: first ? 29.0 : 0.0, left: 0.0, bottom: 0.0, right: 0.0))
let titleFrame: CGRect
if statusAttributedString != nil {
titleFrame = CGRect(origin: CGPoint(x: leftInset, y: 4.0), size: titleLayout.size)
} else {
titleFrame = CGRect(origin: CGPoint(x: leftInset, y: 13.0), size: titleLayout.size)
}
return (nodeLayout, { [weak self] in
if let strongSelf = self {
strongSelf.layoutParams = (item, width, first, last)
if strongSelf.avatarState == nil || strongSelf.avatarState!.0 !== item.account || !strongSelf.avatarState!.1.isEqual(peer) {
strongSelf.avatarNode.setPeer(account: item.account, peer: item.peer)
if let peer = item.peer {
strongSelf.avatarNode.setPeer(account: item.account, peer: peer)
}
strongSelf.avatarNode.frame = CGRect(origin: CGPoint(x: 14.0, y: 4.0), size: CGSize(width: 40.0, height: 40.0))
let _ = titleApply()
strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: leftInset, y: 4.0), size: titleLayout.size)
strongSelf.titleNode.frame = titleFrame
let _ = statusApply()
strongSelf.statusNode.frame = CGRect(origin: CGPoint(x: leftInset, y: 25.0), size: statusLayout.size)
@@ -299,7 +305,7 @@ class ContactsPeerItemNode: ListViewItemNode {
accessoryItemNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -29.0), size: CGSize(width: bounds.size.width, height: 29.0))
}
override func animateInsertion(_ currentTimestamp: Double, duration: Double) {
override func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) {
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration * 0.5)
}

View File

@@ -0,0 +1,106 @@
import Foundation
import AsyncDisplayKit
import TelegramCore
import Postbox
import SwiftSignalKit
import Display
final class ForwardAccessoryPanelNode: AccessoryPanelNode {
private let messageDisposable = MetaDisposable()
let messageIds: [MessageId]
let closeButton: ASButtonNode
let lineNode: ASDisplayNode
let titleNode: ASTextNode
let textNode: ASTextNode
init(account: Account, messageIds: [MessageId]) {
self.messageIds = messageIds
self.closeButton = ASButtonNode()
self.closeButton.setImage(UIImage(bundleImageName: "Chat/Input/Acessory Panels/CloseButton")?.precomposed(), for: [])
self.closeButton.hitTestSlop = UIEdgeInsetsMake(-8.0, -8.0, -8.0, -8.0)
self.closeButton.displaysAsynchronously = false
self.lineNode = ASDisplayNode()
self.lineNode.backgroundColor = UIColor(0x007ee5)
self.titleNode = ASTextNode()
self.titleNode.truncationMode = .byTruncatingTail
self.titleNode.maximumNumberOfLines = 1
self.titleNode.displaysAsynchronously = false
self.textNode = ASTextNode()
self.textNode.truncationMode = .byTruncatingTail
self.textNode.maximumNumberOfLines = 1
self.textNode.displaysAsynchronously = false
super.init()
self.closeButton.addTarget(self, action: #selector(self.closePressed), forControlEvents: [.touchUpInside])
self.addSubnode(self.closeButton)
self.addSubnode(self.lineNode)
self.addSubnode(self.titleNode)
self.addSubnode(self.textNode)
self.messageDisposable.set((account.postbox.messagesAtIds(messageIds)
|> deliverOnMainQueue).start(next: { [weak self] messages in
if let strongSelf = self {
var authors = ""
var uniquePeerIds = Set<PeerId>()
var text = ""
for message in messages {
if let author = message.author, !uniquePeerIds.contains(author.id) {
uniquePeerIds.insert(author.id)
if !authors.isEmpty {
authors.append(", ")
}
authors.append(author.compactDisplayTitle)
}
}
if messages.count == 1 {
text = messages[0].text
} else {
text = "\(messages.count) messages"
}
strongSelf.titleNode.attributedText = NSAttributedString(string: authors, font: Font.regular(14.5), textColor: UIColor(0x007ee5))
strongSelf.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(14.5), textColor: UIColor.black)
strongSelf.setNeedsLayout()
}
}))
}
deinit {
self.messageDisposable.dispose()
}
override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize {
return CGSize(width: constrainedSize.width, height: 40.0)
}
override func layout() {
super.layout()
let bounds = self.bounds
let closeButtonSize = self.closeButton.measure(CGSize(width: 100.0, height: 100.0))
self.closeButton.frame = CGRect(origin: CGPoint(x: bounds.size.width - self.insets.right - closeButtonSize.width, y: 12.0), size: closeButtonSize)
self.lineNode.frame = CGRect(origin: CGPoint(x: self.insets.left, y: 8.0), size: CGSize(width: 2.0, height: bounds.size.height - 5.0))
let titleSize = self.titleNode.measure(CGSize(width: bounds.size.width - 11.0 - insets.left - insets.right - 14.0, height: bounds.size.height))
self.titleNode.frame = CGRect(origin: CGPoint(x: self.insets.left + 11.0, y: 7.0), size: titleSize)
let textSize = self.textNode.measure(CGSize(width: bounds.size.width - 11.0 - insets.left - insets.right - 14.0, height: bounds.size.height))
self.textNode.frame = CGRect(origin: CGPoint(x: self.insets.left + 11.0, y: 25.0), size: textSize)
}
@objc func closePressed() {
if let dismiss = self.dismiss {
dismiss()
}
}
}

View File

@@ -3,7 +3,7 @@ import Foundation
private class FrameworkBundleClass: NSObject {
}
private let frameworkBundle: Bundle = Bundle(for: FrameworkBundleClass.self)
let frameworkBundle: Bundle = Bundle(for: FrameworkBundleClass.self)
private let screenScaleFactor = Int(UIScreen.main.scale)
extension UIImage {

View File

@@ -156,7 +156,7 @@ class GalleryController: ViewController {
let view = account.postbox.aroundMessageHistoryViewForPeerId(messageId.peerId, index: MessageIndex(message!), count: 50, anchorIndex: MessageIndex(message!), fixedCombinedReadState: nil, tagMask: tags)
return view
|> mapToSignal { (view, _) -> Signal<GalleryMessageHistoryView?, Void> in
|> mapToSignal { (view, _, _) -> Signal<GalleryMessageHistoryView?, Void> in
let mapped = GalleryMessageHistoryView.view(view)
return .single(mapped)
}

View File

@@ -213,8 +213,8 @@ final class ListMessageFileItemNode: ListMessageNode {
}
}
override public func animateInsertion(_ currentTimestamp: Double, duration: Double) {
super.animateInsertion(currentTimestamp, duration: duration)
override public func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) {
super.animateInsertion(currentTimestamp, duration: duration, short: short)
self.transitionOffset = self.bounds.size.height * 1.6
self.addTransitionOffsetAnimation(0.0, duration: duration, beginAt: currentTimestamp)

View File

@@ -79,8 +79,8 @@ final class ListMessageSnippetItemNode: ListMessageNode {
}
}
override public func animateInsertion(_ currentTimestamp: Double, duration: Double) {
super.animateInsertion(currentTimestamp, duration: duration)
override public func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) {
super.animateInsertion(currentTimestamp, duration: duration, short: short)
self.transitionOffset = self.bounds.size.height * 1.6
self.addTransitionOffsetAnimation(0.0, duration: duration, beginAt: currentTimestamp)

View File

@@ -246,7 +246,7 @@ class PeerInfoActionItemNode: ListViewItemNode {
}
}
override func animateInsertion(_ currentTimestamp: Double, duration: Double) {
override func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) {
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
}

View File

@@ -243,7 +243,7 @@ class PeerInfoDisclosureItemNode: ListViewItemNode {
}
}
override func animateInsertion(_ currentTimestamp: Double, duration: Double) {
override func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) {
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
}

View File

@@ -205,7 +205,7 @@ class PeerInfoPeerActionItemNode: ListViewItemNode {
}
}
override func animateInsertion(_ currentTimestamp: Double, duration: Double) {
override func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) {
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
}

View File

@@ -299,7 +299,7 @@ class PeerInfoPeerItemNode: ListViewItemNode {
}
}
override func animateInsertion(_ currentTimestamp: Double, duration: Double) {
override func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) {
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
}

View File

@@ -110,7 +110,7 @@ class PeerInfoTextWithLabelItemNode: ListViewItemNode {
}
}
override func animateInsertion(_ currentTimestamp: Double, duration: Double) {
override func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) {
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
}

View File

@@ -0,0 +1,134 @@
import Foundation
import SwiftSignalKit
import Display
import TelegramCore
import Postbox
public final class PeerSelectionController: ViewController {
private let account: Account
var peerSelected: ((PeerId) -> Void)?
private var peerSelectionNode: PeerSelectionControllerNode {
return super.displayNode as! PeerSelectionControllerNode
}
let openMessageFromSearchDisposable: MetaDisposable = MetaDisposable()
public init(account: Account) {
self.account = account
super.init()
self.title = "Forward"
self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Cancel", style: .plain, target: self, action: #selector(self.cancelPressed))
self.scrollToTop = { [weak self] in
if let strongSelf = self {
strongSelf.peerSelectionNode.chatListNode.scrollToLatest()
}
}
}
required public init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
self.openMessageFromSearchDisposable.dispose()
}
override public func loadDisplayNode() {
self.displayNode = PeerSelectionControllerNode(account: self.account, dismiss: { [weak self] in
self?.presentingViewController?.dismiss(animated: false, completion: nil)
})
self.displayNode.backgroundColor = .white
self.peerSelectionNode.navigationBar = self.navigationBar
self.peerSelectionNode.requestDeactivateSearch = { [weak self] in
self?.deactivateSearch()
}
self.peerSelectionNode.chatListNode.activateSearch = { [weak self] in
self?.activateSearch()
}
self.peerSelectionNode.chatListNode.peerSelected = { [weak self] peerId in
if let strongSelf = self, let peerSelected = strongSelf.peerSelected {
peerSelected(peerId)
}
}
self.peerSelectionNode.requestOpenMessageFromSearch = { [weak self] peer, messageId in
if let strongSelf = self {
let storedPeer = strongSelf.account.postbox.modify { modifier -> Void in
if modifier.getPeer(peer.id) == nil {
modifier.updatePeers([peer], update: { previousPeer, updatedPeer in
return updatedPeer
})
}
}
strongSelf.openMessageFromSearchDisposable.set((storedPeer |> deliverOnMainQueue).start(completed: { [weak strongSelf] in
if let strongSelf = strongSelf {
(strongSelf.navigationController as? NavigationController)?.pushViewController(ChatController(account: strongSelf.account, peerId: messageId.peerId, messageId: messageId))
}
}))
}
}
self.peerSelectionNode.requestOpenPeerFromSearch = { [weak self] peerId in
if let strongSelf = self {
(strongSelf.navigationController as? NavigationController)?.pushViewController(ChatController(account: strongSelf.account, peerId: peerId))
}
}
self.displayNodeDidLoad()
}
override public func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
}
override public func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.peerSelectionNode.animateIn()
}
override public func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
}
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
super.containerLayoutUpdated(layout, transition: transition)
self.peerSelectionNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationBar.frame.maxY, transition: transition)
}
@objc func cancelPressed() {
self.dismiss()
}
private func activateSearch() {
if self.displayNavigationBar {
if let scrollToTop = self.scrollToTop {
scrollToTop()
}
self.peerSelectionNode.activateSearch()
self.setDisplayNavigationBar(false, transition: .animated(duration: 0.5, curve: .spring))
}
}
private func deactivateSearch() {
if !self.displayNavigationBar {
self.peerSelectionNode.deactivateSearch()
self.setDisplayNavigationBar(true, transition: .animated(duration: 0.5, curve: .spring))
}
}
public func dismiss() {
self.peerSelectionNode.animateOut()
}
}

View File

@@ -0,0 +1,139 @@
import Foundation
import AsyncDisplayKit
import Display
import Postbox
import TelegramCore
final class PeerSelectionControllerNode: ASDisplayNode {
private let account: Account
private let dismiss: () -> Void
let chatListNode: ChatListNode
var navigationBar: NavigationBar?
private var searchDisplayController: SearchDisplayController?
private var containerLayout: (ContainerViewLayout, CGFloat)?
var requestDeactivateSearch: (() -> Void)?
var requestOpenPeerFromSearch: ((PeerId) -> Void)?
var requestOpenMessageFromSearch: ((Peer, MessageId) -> Void)?
init(account: Account, dismiss: @escaping () -> Void) {
self.account = account
self.dismiss = dismiss
self.chatListNode = ChatListNode(account: account, mode: .peers)
super.init(viewBlock: {
return UITracingLayerView()
}, didLoad: nil)
self.addSubnode(self.chatListNode)
}
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
self.containerLayout = (layout, navigationBarHeight)
var insets = layout.insets(options: [.input])
insets.top += max(navigationBarHeight, layout.insets(options: [.statusBar]).top)
self.chatListNode.bounds = CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: layout.size.height)
self.chatListNode.position = CGPoint(x: layout.size.width / 2.0, y: layout.size.height / 2.0)
var duration: Double = 0.0
var curve: UInt = 0
switch transition {
case .immediate:
break
case let .animated(animationDuration, animationCurve):
duration = animationDuration
switch animationCurve {
case .easeInOut:
break
case .spring:
curve = 7
}
}
let listViewCurve: ListViewAnimationCurve
var speedFactor: CGFloat = 1.0
if curve == 7 {
listViewCurve = .Spring(duration: duration)
} else {
listViewCurve = .Default
}
let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: layout.size, insets: insets, duration: duration, curve: listViewCurve)
self.chatListNode.updateLayout(transition: transition, updateSizeAndInsets: updateSizeAndInsets)
if let searchDisplayController = self.searchDisplayController {
searchDisplayController.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: transition)
}
}
func activateSearch() {
guard let (containerLayout, navigationBarHeight) = self.containerLayout, let navigationBar = self.navigationBar else {
return
}
var maybePlaceholderNode: SearchBarPlaceholderNode?
self.chatListNode.forEachItemNode { node in
if let node = node as? ChatListSearchItemNode {
maybePlaceholderNode = node.searchBarNode
}
}
if let _ = self.searchDisplayController {
return
}
if let placeholderNode = maybePlaceholderNode {
self.searchDisplayController = SearchDisplayController(contentNode: ChatListSearchContainerNode(account: self.account, openPeer: { [weak self] peerId in
if let requestOpenPeerFromSearch = self?.requestOpenPeerFromSearch {
requestOpenPeerFromSearch(peerId)
}
}, openMessage: { [weak self] peer, messageId in
if let requestOpenMessageFromSearch = self?.requestOpenMessageFromSearch {
requestOpenMessageFromSearch(peer, messageId)
}
}), cancel: { [weak self] in
if let requestDeactivateSearch = self?.requestDeactivateSearch {
requestDeactivateSearch()
}
})
self.searchDisplayController?.containerLayoutUpdated(containerLayout, navigationBarHeight: navigationBarHeight, transition: .immediate)
self.searchDisplayController?.activate(insertSubnode: { subnode in
self.insertSubnode(subnode, belowSubnode: navigationBar)
}, placeholder: placeholderNode)
}
}
func deactivateSearch() {
if let searchDisplayController = self.searchDisplayController {
var maybePlaceholderNode: SearchBarPlaceholderNode?
self.chatListNode.forEachItemNode { node in
if let node = node as? ChatListSearchItemNode {
maybePlaceholderNode = node.searchBarNode
}
}
searchDisplayController.deactivate(placeholder: maybePlaceholderNode)
self.searchDisplayController = nil
}
}
func animateIn() {
self.layer.animatePosition(from: CGPoint(x: self.layer.position.x, y: self.layer.position.y + self.layer.bounds.size.height), to: self.layer.position, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, completion: { [weak self] _ in
})
}
func animateOut() {
self.layer.animatePosition(from: self.layer.position, to: CGPoint(x: self.layer.position.x, y: self.layer.position.y + self.layer.bounds.size.height), duration: 0.2, timingFunction: kCAMediaTimingFunctionEaseInEaseOut, removeOnCompletion: false, completion: { [weak self] _ in
if let strongSelf = self {
strongSelf.dismiss()
}
})
}
}

View File

@@ -4,7 +4,7 @@ import Postbox
import TelegramCore
import Display
func preparedChatHistoryViewTransition(from fromView: ChatHistoryView?, to toView: ChatHistoryView, reason: ChatHistoryViewTransitionReason, account: Account, peerId: PeerId, controllerInteraction: ChatControllerInteraction, scrollPosition: ChatHistoryViewScrollPosition?) -> Signal<ChatHistoryViewTransition, NoError> {
func preparedChatHistoryViewTransition(from fromView: ChatHistoryView?, to toView: ChatHistoryView, reason: ChatHistoryViewTransitionReason, account: Account, peerId: PeerId, controllerInteraction: ChatControllerInteraction, scrollPosition: ChatHistoryViewScrollPosition?, initialData: InitialMessageHistoryData?) -> Signal<ChatHistoryViewTransition, NoError> {
return Signal { subscriber in
//let updateIndices: [(Int, ChatHistoryEntry, Int)] = []
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromView?.filteredEntries ?? [], rightList: toView.filteredEntries)
@@ -169,7 +169,7 @@ func preparedChatHistoryViewTransition(from fromView: ChatHistoryView?, to toVie
}
}
subscriber.putNext(ChatHistoryViewTransition(historyView: toView, deleteItems: adjustedDeleteIndices, insertEntries: adjustedIndicesAndItems, updateEntries: adjustedUpdateItems, options: options, scrollToItem: scrollToItem, stationaryItemRange: stationaryItemRange))
subscriber.putNext(ChatHistoryViewTransition(historyView: toView, deleteItems: adjustedDeleteIndices, insertEntries: adjustedIndicesAndItems, updateEntries: adjustedUpdateItems, options: options, scrollToItem: scrollToItem, stationaryItemRange: stationaryItemRange, initialData: initialData))
subscriber.putCompletion()
return EmptyDisposable

View File

@@ -0,0 +1,34 @@
import Foundation
import SwiftSignalKit
import AudioToolbox
private func loadSystemSoundFromBundle(name: String) -> SystemSoundID? {
let path = "\(frameworkBundle.resourcePath!)/\(name)"
let url = URL(fileURLWithPath: path)
var sound: SystemSoundID = 0
if AudioServicesCreateSystemSoundID(url as CFURL, &sound) == noErr {
return sound
}
return nil
}
final class ServiceSoundManager {
private let queue = Queue()
private var messageDeliverySound: SystemSoundID?
init() {
self.queue.async {
self.messageDeliverySound = loadSystemSoundFromBundle(name: "MessageSent.caf")
}
}
func playMessageDeliveredSound() {
self.queue.async {
if let messageDeliverySound = self.messageDeliverySound {
AudioServicesPlaySystemSound(messageDeliverySound)
}
}
}
}
let serviceSoundManager = ServiceSoundManager()

Binary file not shown.