mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
User Info UI improvements
This commit is contained in:
parent
93a7caf86d
commit
65b22f6ca5
@ -371,7 +371,7 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat
|
|||||||
let _ = (updateChatListFilterSettingsInteractively(postbox: context.account.postbox, { settings in
|
let _ = (updateChatListFilterSettingsInteractively(postbox: context.account.postbox, { settings in
|
||||||
var preset = preset
|
var preset = preset
|
||||||
if currentPreset == nil {
|
if currentPreset == nil {
|
||||||
preset.id = max(1, settings.filters.map({ $0.id }).max() ?? 1)
|
preset.id = max(2, settings.filters.map({ $0.id }).max() ?? 2)
|
||||||
}
|
}
|
||||||
var settings = settings
|
var settings = settings
|
||||||
settings.filters = settings.filters.filter { $0 != preset && $0 != currentPreset }
|
settings.filters = settings.filters.filter { $0 != preset && $0 != currentPreset }
|
||||||
|
@ -193,8 +193,10 @@ private enum ChatListRecentEntry: Comparable, Identifiable {
|
|||||||
}
|
}
|
||||||
}, setPeerIdWithRevealedOptions: setPeerIdWithRevealedOptions, deletePeer: deletePeer, contextAction: peerContextAction.flatMap { peerContextAction in
|
}, setPeerIdWithRevealedOptions: setPeerIdWithRevealedOptions, deletePeer: deletePeer, contextAction: peerContextAction.flatMap { peerContextAction in
|
||||||
return { node, gesture in
|
return { node, gesture in
|
||||||
if let chatPeer = peer.peer.peers[peer.peer.peerId] {
|
if let chatPeer = peer.peer.peers[peer.peer.peerId], chatPeer.id.namespace != Namespaces.Peer.SecretChat {
|
||||||
peerContextAction(chatPeer, .recentSearch, node, gesture)
|
peerContextAction(chatPeer, .recentSearch, node, gesture)
|
||||||
|
} else {
|
||||||
|
gesture?.cancel()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -415,7 +417,7 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
|
|||||||
interaction.peerSelected(peer)
|
interaction.peerSelected(peer)
|
||||||
}, contextAction: peerContextAction.flatMap { peerContextAction in
|
}, contextAction: peerContextAction.flatMap { peerContextAction in
|
||||||
return { node, gesture in
|
return { node, gesture in
|
||||||
if let chatPeer = chatPeer {
|
if let chatPeer = chatPeer, chatPeer.id.namespace != Namespaces.Peer.SecretChat {
|
||||||
peerContextAction(chatPeer, .search, node, gesture)
|
peerContextAction(chatPeer, .search, node, gesture)
|
||||||
} else {
|
} else {
|
||||||
gesture?.cancel()
|
gesture?.cancel()
|
||||||
|
@ -567,6 +567,30 @@ public extension ContainedViewLayoutTransition {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func animateTransformScale(view: UIView, from fromScale: CGFloat, completion: ((Bool) -> Void)? = nil) {
|
||||||
|
let t = view.layer.transform
|
||||||
|
let currentScale = sqrt((t.m11 * t.m11) + (t.m12 * t.m12) + (t.m13 * t.m13))
|
||||||
|
if currentScale.isEqual(to: fromScale) {
|
||||||
|
if let completion = completion {
|
||||||
|
completion(true)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch self {
|
||||||
|
case .immediate:
|
||||||
|
if let completion = completion {
|
||||||
|
completion(true)
|
||||||
|
}
|
||||||
|
case let .animated(duration, curve):
|
||||||
|
view.layer.animateScale(from: fromScale, to: currentScale, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, completion: { result in
|
||||||
|
if let completion = completion {
|
||||||
|
completion(result)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func updateTransformScale(node: ASDisplayNode, scale: CGFloat, beginWithCurrentState: Bool = false, completion: ((Bool) -> Void)? = nil) {
|
func updateTransformScale(node: ASDisplayNode, scale: CGFloat, beginWithCurrentState: Bool = false, completion: ((Bool) -> Void)? = nil) {
|
||||||
let t = node.layer.transform
|
let t = node.layer.transform
|
||||||
let currentScale = sqrt((t.m11 * t.m11) + (t.m12 * t.m12) + (t.m13 * t.m13))
|
let currentScale = sqrt((t.m11 * t.m11) + (t.m12 * t.m12) + (t.m13 * t.m13))
|
||||||
|
@ -38,6 +38,20 @@ public class ImmediateTextNode: TextNode {
|
|||||||
let node = TextNode()
|
let node = TextNode()
|
||||||
node.cachedLayout = self.cachedLayout
|
node.cachedLayout = self.cachedLayout
|
||||||
node.frame = self.frame
|
node.frame = self.frame
|
||||||
|
if let subnodes = self.subnodes {
|
||||||
|
for subnode in subnodes {
|
||||||
|
if let subnode = subnode as? ASImageNode {
|
||||||
|
let copySubnode = ASImageNode()
|
||||||
|
copySubnode.isLayerBacked = subnode.isLayerBacked
|
||||||
|
copySubnode.image = subnode.image
|
||||||
|
copySubnode.displaysAsynchronously = false
|
||||||
|
copySubnode.displayWithoutProcessing = true
|
||||||
|
copySubnode.frame = subnode.frame
|
||||||
|
copySubnode.alpha = subnode.alpha
|
||||||
|
node.addSubnode(copySubnode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return node
|
return node
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -133,6 +133,13 @@ final class NavigationContainer: ASDisplayNode, UIGestureRecognizerDelegate {
|
|||||||
}*/
|
}*/
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func hasNonReadyControllers() -> Bool {
|
||||||
|
if let pending = self.state.pending, !pending.isReady {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -185,6 +192,17 @@ final class NavigationContainer: ASDisplayNode, UIGestureRecognizerDelegate {
|
|||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
if let top = strongSelf.state.top {
|
if let top = strongSelf.state.top {
|
||||||
strongSelf.syncKeyboard(leftEdge: top.value.displayNode.frame.minX, transition: transition)
|
strongSelf.syncKeyboard(leftEdge: top.value.displayNode.frame.minX, transition: transition)
|
||||||
|
|
||||||
|
var updatedStatusBarStyle = strongSelf.statusBarStyle
|
||||||
|
if let childTransition = strongSelf.state.transition, childTransition.coordinator.progress >= 0.3 {
|
||||||
|
updatedStatusBarStyle = childTransition.previous.value.statusBar.statusBarStyle
|
||||||
|
} else {
|
||||||
|
updatedStatusBarStyle = top.value.statusBar.statusBarStyle
|
||||||
|
}
|
||||||
|
if strongSelf.statusBarStyle != updatedStatusBarStyle {
|
||||||
|
strongSelf.statusBarStyle = updatedStatusBarStyle
|
||||||
|
strongSelf.statusBarStyleUpdated?(.animated(duration: 0.3, curve: .easeInOut))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -337,7 +355,11 @@ final class NavigationContainer: ASDisplayNode, UIGestureRecognizerDelegate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.applyLayout(layout: updatedLayout, to: top, isMaster: true, transition: transition)
|
self.applyLayout(layout: updatedLayout, to: top, isMaster: true, transition: transition)
|
||||||
updatedStatusBarStyle = top.value.statusBar.statusBarStyle
|
if let childTransition = self.state.transition, childTransition.coordinator.progress >= 0.3 {
|
||||||
|
updatedStatusBarStyle = childTransition.previous.value.statusBar.statusBarStyle
|
||||||
|
} else {
|
||||||
|
updatedStatusBarStyle = top.value.statusBar.statusBarStyle
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
updatedStatusBarStyle = .Ignore
|
updatedStatusBarStyle = .Ignore
|
||||||
}
|
}
|
||||||
|
@ -274,6 +274,18 @@ open class NavigationController: UINavigationController, ContainableController,
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if let rootContainer = self.rootContainer {
|
||||||
|
switch rootContainer {
|
||||||
|
case let .flat(container):
|
||||||
|
if container.hasNonReadyControllers() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
case let .split(splitContainer):
|
||||||
|
if splitContainer.hasNonReadyControllers() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1001,27 +1013,8 @@ open class NavigationController: UINavigationController, ContainableController,
|
|||||||
}
|
}
|
||||||
|
|
||||||
public func pushViewController(_ controller: ViewController, animated: Bool = true, completion: @escaping () -> Void) {
|
public func pushViewController(_ controller: ViewController, animated: Bool = true, completion: @escaping () -> Void) {
|
||||||
let navigateAction: () -> Void = { [weak self] in
|
self.pushViewController(controller, animated: animated)
|
||||||
guard let strongSelf = self else {
|
completion()
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if !controller.hasActiveInput {
|
|
||||||
//strongSelf.view.endEditing(true)
|
|
||||||
}
|
|
||||||
/*strongSelf.scheduleAfterLayout({
|
|
||||||
guard let strongSelf = self else {
|
|
||||||
return
|
|
||||||
}*/
|
|
||||||
strongSelf.pushViewController(controller, animated: animated)
|
|
||||||
completion()
|
|
||||||
//})
|
|
||||||
}
|
|
||||||
|
|
||||||
/*if let lastController = self.viewControllers.last as? ViewController, !lastController.attemptNavigation(navigateAction) {
|
|
||||||
} else {*/
|
|
||||||
navigateAction()
|
|
||||||
//}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
open override func pushViewController(_ viewController: UIViewController, animated: Bool) {
|
open override func pushViewController(_ viewController: UIViewController, animated: Bool) {
|
||||||
|
@ -57,6 +57,16 @@ final class NavigationSplitContainer: ASDisplayNode {
|
|||||||
self.view.addSubview(self.detailScrollToTopView)
|
self.view.addSubview(self.detailScrollToTopView)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func hasNonReadyControllers() -> Bool {
|
||||||
|
if self.masterContainer.hasNonReadyControllers() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if self.detailContainer.hasNonReadyControllers() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func updateTheme(theme: NavigationControllerTheme) {
|
func updateTheme(theme: NavigationControllerTheme) {
|
||||||
self.separator.backgroundColor = theme.navigationBar.separatorColor
|
self.separator.backgroundColor = theme.navigationBar.separatorColor
|
||||||
}
|
}
|
||||||
|
@ -93,6 +93,8 @@ public enum ViewControllerNavigationPresentation {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var blocksInteractionUntilReady: Bool = false
|
||||||
|
|
||||||
public final var isOpaqueWhenInOverlay: Bool = false
|
public final var isOpaqueWhenInOverlay: Bool = false
|
||||||
public final var blocksBackgroundWhenInOverlay: Bool = false
|
public final var blocksBackgroundWhenInOverlay: Bool = false
|
||||||
public final var automaticallyControlPresentationContextLayout: Bool = true
|
public final var automaticallyControlPresentationContextLayout: Bool = true
|
||||||
|
@ -15,6 +15,8 @@ static_library(
|
|||||||
frameworks = [
|
frameworks = [
|
||||||
"$SDKROOT/System/Library/Frameworks/Foundation.framework",
|
"$SDKROOT/System/Library/Frameworks/Foundation.framework",
|
||||||
"$SDKROOT/System/Library/Frameworks/UIKit.framework",
|
"$SDKROOT/System/Library/Frameworks/UIKit.framework",
|
||||||
"$SDKROOT/System/Library/Frameworks/Photos.framework",
|
],
|
||||||
|
weak_frameworks = [
|
||||||
|
"Photos",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
@ -26,6 +26,8 @@ static_library(
|
|||||||
"$SDKROOT/System/Library/Frameworks/Foundation.framework",
|
"$SDKROOT/System/Library/Frameworks/Foundation.framework",
|
||||||
"$SDKROOT/System/Library/Frameworks/UIKit.framework",
|
"$SDKROOT/System/Library/Frameworks/UIKit.framework",
|
||||||
"$SDKROOT/System/Library/Frameworks/QuickLook.framework",
|
"$SDKROOT/System/Library/Frameworks/QuickLook.framework",
|
||||||
"$SDKROOT/System/Library/Frameworks/Photos.framework",
|
],
|
||||||
|
weak_frameworks = [
|
||||||
|
"Photos",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
@ -18,6 +18,8 @@ static_library(
|
|||||||
"$SDKROOT/System/Library/Frameworks/Foundation.framework",
|
"$SDKROOT/System/Library/Frameworks/Foundation.framework",
|
||||||
"$SDKROOT/System/Library/Frameworks/UIKit.framework",
|
"$SDKROOT/System/Library/Frameworks/UIKit.framework",
|
||||||
"$SDKROOT/System/Library/Frameworks/MobileCoreServices.framework",
|
"$SDKROOT/System/Library/Frameworks/MobileCoreServices.framework",
|
||||||
"$SDKROOT/System/Library/Frameworks/Photos.framework",
|
],
|
||||||
|
weak_frameworks = [
|
||||||
|
"Photos",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
@ -22,7 +22,7 @@ public final class SearchDisplayController {
|
|||||||
|
|
||||||
private var isSearchingDisposable: Disposable?
|
private var isSearchingDisposable: Disposable?
|
||||||
|
|
||||||
public init(presentationData: PresentationData, mode: SearchDisplayControllerMode = .navigation, contentNode: SearchDisplayControllerContentNode, cancel: @escaping () -> Void) {
|
public init(presentationData: PresentationData, mode: SearchDisplayControllerMode = .navigation, placeholder: String? = nil, contentNode: SearchDisplayControllerContentNode, cancel: @escaping () -> Void) {
|
||||||
self.searchBar = SearchBarNode(theme: SearchBarNodeTheme(theme: presentationData.theme, hasSeparator: false), strings: presentationData.strings, fieldStyle: .modern)
|
self.searchBar = SearchBarNode(theme: SearchBarNodeTheme(theme: presentationData.theme, hasSeparator: false), strings: presentationData.strings, fieldStyle: .modern)
|
||||||
self.mode = mode
|
self.mode = mode
|
||||||
self.contentNode = contentNode
|
self.contentNode = contentNode
|
||||||
@ -48,6 +48,9 @@ public final class SearchDisplayController {
|
|||||||
self?.searchBar.prefixString = prefix
|
self?.searchBar.prefixString = prefix
|
||||||
self?.searchBar.text = query
|
self?.searchBar.text = query
|
||||||
}
|
}
|
||||||
|
if let placeholder = placeholder {
|
||||||
|
self.searchBar.placeholderString = NSAttributedString(string: placeholder, font: Font.regular(17.0), textColor: presentationData.theme.rootController.navigationSearchBar.inputPlaceholderTextColor)
|
||||||
|
}
|
||||||
self.contentNode.setPlaceholder = { [weak self] string in
|
self.contentNode.setPlaceholder = { [weak self] string in
|
||||||
guard string != self?.searchBar.placeholderString?.string else {
|
guard string != self?.searchBar.placeholderString?.string else {
|
||||||
return
|
return
|
||||||
@ -153,6 +156,7 @@ public final class SearchDisplayController {
|
|||||||
if let placeholder = placeholder {
|
if let placeholder = placeholder {
|
||||||
self.searchBar.animateIn(from: placeholder, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring)
|
self.searchBar.animateIn(from: placeholder, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring)
|
||||||
} else {
|
} else {
|
||||||
|
self.searchBar.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue)
|
||||||
self.contentNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue)
|
self.contentNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -92,8 +92,10 @@ static_library(
|
|||||||
"$SDKROOT/System/Library/Frameworks/UIKit.framework",
|
"$SDKROOT/System/Library/Frameworks/UIKit.framework",
|
||||||
"$SDKROOT/System/Library/Frameworks/MessageUI.framework",
|
"$SDKROOT/System/Library/Frameworks/MessageUI.framework",
|
||||||
"$SDKROOT/System/Library/Frameworks/LocalAuthentication.framework",
|
"$SDKROOT/System/Library/Frameworks/LocalAuthentication.framework",
|
||||||
"$SDKROOT/System/Library/Frameworks/Photos.framework",
|
|
||||||
"$SDKROOT/System/Library/Frameworks/QuickLook.framework",
|
"$SDKROOT/System/Library/Frameworks/QuickLook.framework",
|
||||||
"$SDKROOT/System/Library/Frameworks/CoreTelephony.framework",
|
"$SDKROOT/System/Library/Frameworks/CoreTelephony.framework",
|
||||||
],
|
],
|
||||||
|
weak_frameworks = [
|
||||||
|
"Photos",
|
||||||
|
],
|
||||||
)
|
)
|
||||||
|
@ -80,12 +80,20 @@ final class ChatAvatarNavigationNode: ASDisplayNode {
|
|||||||
(self.view as? ChatAvatarNavigationNodeView)?.targetNode = self
|
(self.view as? ChatAvatarNavigationNodeView)?.targetNode = self
|
||||||
(self.view as? ChatAvatarNavigationNodeView)?.chatController = self.chatController
|
(self.view as? ChatAvatarNavigationNodeView)?.chatController = self.chatController
|
||||||
|
|
||||||
self.avatarNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.avatarTapGesture(_:))))
|
let tapRecognizer = TapLongTapOrDoubleTapGestureRecognizer(target: self, action: #selector(self.avatarTapGesture(_:)))
|
||||||
|
self.avatarNode.view.addGestureRecognizer(tapRecognizer)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc private func avatarTapGesture(_ recognizer: UITapGestureRecognizer) {
|
@objc private func avatarTapGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) {
|
||||||
if case .ended = recognizer.state {
|
if case .ended = recognizer.state {
|
||||||
self.tapped?()
|
if let (gesture, location) = recognizer.lastRecognizedGestureAndLocation {
|
||||||
|
switch gesture {
|
||||||
|
case .tap:
|
||||||
|
self.tapped?()
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1866,12 +1866,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
|
|
||||||
self.controllerInteraction = controllerInteraction
|
self.controllerInteraction = controllerInteraction
|
||||||
|
|
||||||
var displayNavigationAvatar = false
|
if case let .peer(peerId) = chatLocation, peerId != context.account.peerId, subject != .scheduledMessages {
|
||||||
if case let .peer(peerId) = chatLocation, peerId != context.account.peerId {
|
|
||||||
displayNavigationAvatar = true
|
|
||||||
self.navigationBar?.userInfo = PeerInfoNavigationSourceTag(peerId: peerId)
|
self.navigationBar?.userInfo = PeerInfoNavigationSourceTag(peerId: peerId)
|
||||||
}
|
}
|
||||||
self.chatTitleView = ChatTitleView(account: self.context.account, theme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder, displayAvatar: displayNavigationAvatar)
|
self.chatTitleView = ChatTitleView(account: self.context.account, theme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder, displayAvatar: true)
|
||||||
if let avatarNode = self.chatTitleView?.avatarNode {
|
if let avatarNode = self.chatTitleView?.avatarNode {
|
||||||
avatarNode.chatController = self
|
avatarNode.chatController = self
|
||||||
avatarNode.contextAction = { [weak self] node, gesture in
|
avatarNode.contextAction = { [weak self] node, gesture in
|
||||||
@ -1988,8 +1986,16 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
if let peer = peerViewMainPeer(peerView) {
|
if let peer = peerViewMainPeer(peerView) {
|
||||||
strongSelf.chatTitleView?.titleContent = .peer(peerView: peerView, onlineMemberCount: onlineMemberCount, isScheduledMessages: isScheduledMessages)
|
strongSelf.chatTitleView?.titleContent = .peer(peerView: peerView, onlineMemberCount: onlineMemberCount, isScheduledMessages: isScheduledMessages)
|
||||||
strongSelf.chatTitleView?.avatarNode?.avatarNode.setPeer(context: strongSelf.context, theme: strongSelf.presentationData.theme, peer: peer, overrideImage: peer.isDeleted ? .deletedIcon : .none)
|
let imageOverride: AvatarNodeImageOverride?
|
||||||
(strongSelf.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.contextActionIsEnabled = peer.restrictionText(platform: "ios", contentSettings: strongSelf.context.currentContentSettings.with { $0 }) == nil
|
if strongSelf.context.account.peerId == peer.id {
|
||||||
|
imageOverride = .savedMessagesIcon
|
||||||
|
} else if peer.isDeleted {
|
||||||
|
imageOverride = .deletedIcon
|
||||||
|
} else {
|
||||||
|
imageOverride = nil
|
||||||
|
}
|
||||||
|
strongSelf.chatTitleView?.avatarNode?.avatarNode.setPeer(context: strongSelf.context, theme: strongSelf.presentationData.theme, peer: peer, overrideImage: imageOverride)
|
||||||
|
strongSelf.chatTitleView?.avatarNode?.contextActionIsEnabled = peer.restrictionText(platform: "ios", contentSettings: strongSelf.context.currentContentSettings.with { $0 }) == nil && imageOverride == nil && peer.smallProfileImage != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if strongSelf.peerView === peerView && strongSelf.reportIrrelvantGeoNotice == peerReportNotice && strongSelf.hasScheduledMessages == hasScheduledMessages {
|
if strongSelf.peerView === peerView && strongSelf.reportIrrelvantGeoNotice == peerReportNotice && strongSelf.hasScheduledMessages == hasScheduledMessages {
|
||||||
@ -5075,6 +5081,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
self.leftNavigationButton = nil
|
self.leftNavigationButton = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.chatTitleView?.displayAvatar = updatedChatPresentationInterfaceState.interfaceState.selectionState == nil
|
||||||
|
|
||||||
if let button = rightNavigationButtonForChatInterfaceState(updatedChatPresentationInterfaceState, strings: updatedChatPresentationInterfaceState.strings, currentButton: self.rightNavigationButton, target: self, selector: #selector(self.rightNavigationButtonAction), chatInfoNavigationButton: self.chatInfoNavigationButton) {
|
if let button = rightNavigationButtonForChatInterfaceState(updatedChatPresentationInterfaceState, strings: updatedChatPresentationInterfaceState.strings, currentButton: self.rightNavigationButton, target: self, selector: #selector(self.rightNavigationButtonAction), chatInfoNavigationButton: self.chatInfoNavigationButton) {
|
||||||
if self.rightNavigationButton != button {
|
if self.rightNavigationButton != button {
|
||||||
var animated = transition.isAnimated
|
var animated = transition.isAnimated
|
||||||
|
@ -72,7 +72,12 @@ func rightNavigationButtonForChatInterfaceState(_ presentationInterfaceState: Ch
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if presentationInterfaceState.isScheduledMessages {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
if case .standard(true) = presentationInterfaceState.mode {
|
if case .standard(true) = presentationInterfaceState.mode {
|
||||||
|
return nil
|
||||||
} else if let peer = presentationInterfaceState.renderedPeer?.peer {
|
} else if let peer = presentationInterfaceState.renderedPeer?.peer {
|
||||||
if presentationInterfaceState.accountPeerId == peer.id {
|
if presentationInterfaceState.accountPeerId == peer.id {
|
||||||
if presentationInterfaceState.isScheduledMessages {
|
if presentationInterfaceState.isScheduledMessages {
|
||||||
|
@ -179,6 +179,15 @@ final class ChatTitleView: UIView, NavigationBarTitleView {
|
|||||||
|
|
||||||
var pressed: (() -> Void)?
|
var pressed: (() -> Void)?
|
||||||
|
|
||||||
|
var displayAvatar: Bool = true {
|
||||||
|
didSet {
|
||||||
|
if self.displayAvatar != oldValue {
|
||||||
|
self.avatarNode?.isHidden = !self.displayAvatar
|
||||||
|
self.setNeedsLayout()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var titleContent: ChatTitleContent? {
|
var titleContent: ChatTitleContent? {
|
||||||
didSet {
|
didSet {
|
||||||
if let titleContent = self.titleContent {
|
if let titleContent = self.titleContent {
|
||||||
@ -529,19 +538,13 @@ final class ChatTitleView: UIView, NavigationBarTitleView {
|
|||||||
if highlighted {
|
if highlighted {
|
||||||
strongSelf.titleNode.layer.removeAnimation(forKey: "opacity")
|
strongSelf.titleNode.layer.removeAnimation(forKey: "opacity")
|
||||||
strongSelf.activityNode.layer.removeAnimation(forKey: "opacity")
|
strongSelf.activityNode.layer.removeAnimation(forKey: "opacity")
|
||||||
strongSelf.titleLeftIconNode.layer.removeAnimation(forKey: "opacity")
|
|
||||||
strongSelf.titleRightIconNode.layer.removeAnimation(forKey: "opacity")
|
|
||||||
strongSelf.titleCredibilityIconNode.layer.removeAnimation(forKey: "opacity")
|
strongSelf.titleCredibilityIconNode.layer.removeAnimation(forKey: "opacity")
|
||||||
strongSelf.titleNode.alpha = 0.4
|
strongSelf.titleNode.alpha = 0.4
|
||||||
strongSelf.activityNode.alpha = 0.4
|
strongSelf.activityNode.alpha = 0.4
|
||||||
strongSelf.titleLeftIconNode.alpha = 0.4
|
|
||||||
strongSelf.titleRightIconNode.alpha = 0.4
|
|
||||||
strongSelf.titleCredibilityIconNode.alpha = 0.4
|
strongSelf.titleCredibilityIconNode.alpha = 0.4
|
||||||
} else {
|
} else {
|
||||||
strongSelf.titleNode.alpha = 1.0
|
strongSelf.titleNode.alpha = 1.0
|
||||||
strongSelf.activityNode.alpha = 1.0
|
strongSelf.activityNode.alpha = 1.0
|
||||||
strongSelf.titleLeftIconNode.alpha = 1.0
|
|
||||||
strongSelf.titleRightIconNode.alpha = 1.0
|
|
||||||
strongSelf.titleCredibilityIconNode.alpha = 1.0
|
strongSelf.titleCredibilityIconNode.alpha = 1.0
|
||||||
strongSelf.titleNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
|
strongSelf.titleNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
|
||||||
strongSelf.activityNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
|
strongSelf.activityNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
|
||||||
@ -592,7 +595,7 @@ final class ChatTitleView: UIView, NavigationBarTitleView {
|
|||||||
|
|
||||||
if let image = self.titleLeftIconNode.image {
|
if let image = self.titleLeftIconNode.image {
|
||||||
if self.titleLeftIconNode.supernode == nil {
|
if self.titleLeftIconNode.supernode == nil {
|
||||||
self.contentContainer.addSubnode(self.titleLeftIconNode)
|
self.titleNode.addSubnode(self.titleLeftIconNode)
|
||||||
}
|
}
|
||||||
leftIconWidth = image.size.width + 6.0
|
leftIconWidth = image.size.width + 6.0
|
||||||
} else if self.titleLeftIconNode.supernode != nil {
|
} else if self.titleLeftIconNode.supernode != nil {
|
||||||
@ -610,7 +613,7 @@ final class ChatTitleView: UIView, NavigationBarTitleView {
|
|||||||
|
|
||||||
if let image = self.titleRightIconNode.image {
|
if let image = self.titleRightIconNode.image {
|
||||||
if self.titleRightIconNode.supernode == nil {
|
if self.titleRightIconNode.supernode == nil {
|
||||||
self.contentContainer.addSubnode(self.titleRightIconNode)
|
self.titleNode.addSubnode(self.titleRightIconNode)
|
||||||
}
|
}
|
||||||
rightIconWidth = image.size.width + 3.0
|
rightIconWidth = image.size.width + 3.0
|
||||||
} else if self.titleRightIconNode.supernode != nil {
|
} else if self.titleRightIconNode.supernode != nil {
|
||||||
@ -622,68 +625,43 @@ final class ChatTitleView: UIView, NavigationBarTitleView {
|
|||||||
let avatarSize = CGSize(width: 37.0, height: 37.0)
|
let avatarSize = CGSize(width: 37.0, height: 37.0)
|
||||||
let avatarFrame = CGRect(origin: CGPoint(x: leftInset + 10.0, y: floor((size.height - avatarSize.height) / 2.0)), size: avatarSize)
|
let avatarFrame = CGRect(origin: CGPoint(x: leftInset + 10.0, y: floor((size.height - avatarSize.height) / 2.0)), size: avatarSize)
|
||||||
avatarNode.frame = avatarFrame
|
avatarNode.frame = avatarFrame
|
||||||
leftInset += avatarSize.width + 10.0 + 8.0
|
if self.displayAvatar {
|
||||||
|
leftInset += avatarSize.width + 10.0 + 8.0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.button.frame = CGRect(origin: CGPoint(x: leftInset - 20.0, y: 0.0), size: CGSize(width: clearBounds.width - leftInset, height: size.height))
|
self.button.frame = CGRect(origin: CGPoint(x: leftInset - 20.0, y: 0.0), size: CGSize(width: clearBounds.width - leftInset, height: size.height))
|
||||||
|
|
||||||
let titleSideInset: CGFloat = 3.0
|
let titleSideInset: CGFloat = 3.0
|
||||||
if size.height > 40.0 {
|
var titleSize = self.titleNode.updateLayout(CGSize(width: clearBounds.width - leftIconWidth - credibilityIconWidth - rightIconWidth - titleSideInset * 2.0 - leftInset, height: size.height))
|
||||||
var titleSize = self.titleNode.updateLayout(CGSize(width: clearBounds.width - leftIconWidth - credibilityIconWidth - rightIconWidth - titleSideInset * 2.0 - leftInset, height: size.height))
|
titleSize.width += credibilityIconWidth
|
||||||
titleSize.width += credibilityIconWidth
|
let activitySize = self.activityNode.updateLayout(clearBounds.size, alignment: .left)
|
||||||
let activitySize = self.activityNode.updateLayout(clearBounds.size, alignment: .left)
|
let titleInfoSpacing: CGFloat = 0.0
|
||||||
let titleInfoSpacing: CGFloat = 0.0
|
|
||||||
|
var titleFrame: CGRect
|
||||||
var titleFrame: CGRect
|
|
||||||
|
if activitySize.height.isZero {
|
||||||
if activitySize.height.isZero {
|
titleFrame = CGRect(origin: CGPoint(x: leftInset + leftIconWidth, y: floor((size.height - titleSize.height) / 2.0)), size: titleSize)
|
||||||
titleFrame = CGRect(origin: CGPoint(x: leftInset + leftIconWidth, y: floor((size.height - titleSize.height) / 2.0)), size: titleSize)
|
|
||||||
self.titleNode.frame = titleFrame
|
|
||||||
} else {
|
|
||||||
let combinedHeight = titleSize.height + activitySize.height + titleInfoSpacing
|
|
||||||
|
|
||||||
titleFrame = CGRect(origin: CGPoint(x: leftInset + leftIconWidth, y: floor((size.height - combinedHeight) / 2.0)), size: titleSize)
|
|
||||||
self.titleNode.frame = titleFrame
|
|
||||||
|
|
||||||
var activityFrame = CGRect(origin: CGPoint(x: leftInset, y: floor((size.height - combinedHeight) / 2.0) + titleSize.height + titleInfoSpacing), size: activitySize)
|
|
||||||
self.activityNode.frame = activityFrame
|
|
||||||
}
|
|
||||||
|
|
||||||
if let image = self.titleLeftIconNode.image {
|
|
||||||
self.titleLeftIconNode.frame = CGRect(origin: CGPoint(x: titleFrame.minX - image.size.width - 3.0 - UIScreenPixel, y: titleFrame.minY + 4.0), size: image.size)
|
|
||||||
}
|
|
||||||
if let image = self.titleCredibilityIconNode.image {
|
|
||||||
self.titleCredibilityIconNode.frame = CGRect(origin: CGPoint(x: titleFrame.maxX - image.size.width - 1.0, y: titleFrame.minY + 2.0), size: image.size)
|
|
||||||
}
|
|
||||||
if let image = self.titleRightIconNode.image {
|
|
||||||
self.titleRightIconNode.frame = CGRect(origin: CGPoint(x: titleFrame.maxX + 3.0, y: titleFrame.minY + 6.0), size: image.size)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let titleSize = self.titleNode.updateLayout(CGSize(width: floor(clearBounds.width / 2.0 - leftIconWidth - credibilityIconWidth - rightIconWidth - titleSideInset * 2.0), height: size.height))
|
|
||||||
let activitySize = self.activityNode.updateLayout(CGSize(width: floor(clearBounds.width / 2.0), height: size.height), alignment: .center)
|
|
||||||
|
|
||||||
let titleInfoSpacing: CGFloat = 8.0
|
|
||||||
let combinedWidth = titleSize.width + leftIconWidth + credibilityIconWidth + rightIconWidth + activitySize.width + titleInfoSpacing
|
|
||||||
|
|
||||||
let titleFrame = CGRect(origin: CGPoint(x: leftIconWidth + floor((clearBounds.width - combinedWidth) / 2.0), y: floor((size.height - titleSize.height) / 2.0)), size: titleSize)
|
|
||||||
self.titleNode.frame = titleFrame
|
self.titleNode.frame = titleFrame
|
||||||
self.activityNode.frame = CGRect(origin: CGPoint(x: floor((clearBounds.width - combinedWidth) / 2.0 + titleSize.width + leftIconWidth + credibilityIconWidth + rightIconWidth + titleInfoSpacing), y: floor((size.height - activitySize.height) / 2.0)), size: activitySize)
|
} else {
|
||||||
|
let combinedHeight = titleSize.height + activitySize.height + titleInfoSpacing
|
||||||
|
|
||||||
if let image = self.titleLeftIconNode.image {
|
titleFrame = CGRect(origin: CGPoint(x: leftInset + leftIconWidth, y: floor((size.height - combinedHeight) / 2.0)), size: titleSize)
|
||||||
self.titleLeftIconNode.frame = CGRect(origin: CGPoint(x: titleFrame.minX, y: titleFrame.minY + 4.0), size: image.size)
|
self.titleNode.frame = titleFrame
|
||||||
}
|
|
||||||
if let image = self.titleCredibilityIconNode.image {
|
var activityFrame = CGRect(origin: CGPoint(x: leftInset, y: floor((size.height - combinedHeight) / 2.0) + titleSize.height + titleInfoSpacing), size: activitySize)
|
||||||
self.titleCredibilityIconNode.frame = CGRect(origin: CGPoint(x: titleFrame.maxX - image.size.width - 1.0, y: titleFrame.minY + 6.0), size: image.size)
|
self.activityNode.frame = activityFrame
|
||||||
}
|
|
||||||
if let image = self.titleRightIconNode.image {
|
|
||||||
self.titleRightIconNode.frame = CGRect(origin: CGPoint(x: titleFrame.maxX - image.size.width - 1.0, y: titleFrame.minY + 6.0), size: image.size)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*if let networkStatusNode = self.networkStatusNode {
|
if let image = self.titleLeftIconNode.image {
|
||||||
transition.updateFrame(node: networkStatusNode, frame: CGRect(origin: CGPoint(), size: size))
|
self.titleLeftIconNode.frame = CGRect(origin: CGPoint(x: -image.size.width - 3.0 - UIScreenPixel, y: 4.0), size: image.size)
|
||||||
networkStatusNode.updateLayout(size: size, transition: transition)
|
}
|
||||||
}*/
|
if let image = self.titleCredibilityIconNode.image {
|
||||||
|
self.titleCredibilityIconNode.frame = CGRect(origin: CGPoint(x: titleFrame.maxX - image.size.width - 1.0, y: titleFrame.minY + 2.0), size: image.size)
|
||||||
|
}
|
||||||
|
if let image = self.titleRightIconNode.image {
|
||||||
|
self.titleRightIconNode.frame = CGRect(origin: CGPoint(x: titleFrame.width + 3.0, y: 6.0), size: image.size)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func buttonPressed() {
|
@objc func buttonPressed() {
|
||||||
|
@ -658,7 +658,7 @@ final class ListMessageFileItemNode: ListMessageNode {
|
|||||||
strongSelf.addSubnode(waveformScrubbingNode)
|
strongSelf.addSubnode(waveformScrubbingNode)
|
||||||
}
|
}
|
||||||
|
|
||||||
strongSelf.waveformScrubbingNode?.frame = CGRect(origin: CGPoint(x: leftOffset + leftInset, y: 10.0), size: CGSize(width: params.width - (leftOffset + leftInset) - 16.0, height: 12.0))
|
transition.updateFrame(node: waveformScrubbingNode, frame: CGRect(origin: CGPoint(x: leftOffset + leftInset, y: 10.0), size: CGSize(width: params.width - leftInset - 16.0, height: 12.0)))
|
||||||
|
|
||||||
waveformNode.setup(color: item.theme.list.controlSecondaryColor, waveform: waveform)
|
waveformNode.setup(color: item.theme.list.controlSecondaryColor, waveform: waveform)
|
||||||
waveformForegroundNode.setup(color: item.theme.list.itemAccentColor, waveform: waveform)
|
waveformForegroundNode.setup(color: item.theme.list.itemAccentColor, waveform: waveform)
|
||||||
@ -809,7 +809,7 @@ final class ListMessageFileItemNode: ListMessageNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.waveformScrubbingNode?.enableScrubbing = enableScrubbing
|
self.waveformScrubbingNode?.enableScrubbing = enableScrubbing
|
||||||
if let musicIsPlaying = musicIsPlaying, !isVoice {
|
if let musicIsPlaying = musicIsPlaying, !isVoice, !isInstantVideo {
|
||||||
if self.playbackOverlayNode == nil {
|
if self.playbackOverlayNode == nil {
|
||||||
let playbackOverlayNode = ListMessagePlaybackOverlayNode()
|
let playbackOverlayNode = ListMessagePlaybackOverlayNode()
|
||||||
playbackOverlayNode.frame = self.iconImageNode.frame
|
playbackOverlayNode.frame = self.iconImageNode.frame
|
||||||
|
@ -35,24 +35,24 @@ private struct GroupsInCommonListEntry: Comparable, Identifiable {
|
|||||||
return lhs.index < rhs.index
|
return lhs.index < rhs.index
|
||||||
}
|
}
|
||||||
|
|
||||||
func item(context: AccountContext, presentationData: PresentationData, openPeer: @escaping (Peer) -> Void) -> ListViewItem {
|
func item(context: AccountContext, presentationData: PresentationData, openPeer: @escaping (Peer) -> Void, openPeerContextAction: @escaping (Peer, ASDisplayNode, ContextGesture?) -> Void) -> ListViewItem {
|
||||||
let peer = self.peer
|
let peer = self.peer
|
||||||
return ItemListPeerItem(presentationData: ItemListPresentationData(presentationData), dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, context: context, peer: self.peer, presence: nil, text: .none, label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), switchValue: nil, enabled: true, selectable: true, sectionId: 0, action: {
|
return ItemListPeerItem(presentationData: ItemListPresentationData(presentationData), dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, context: context, peer: self.peer, presence: nil, text: .none, label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), switchValue: nil, enabled: true, selectable: true, sectionId: 0, action: {
|
||||||
openPeer(peer)
|
openPeer(peer)
|
||||||
}, setPeerIdWithRevealedOptions: { _, _ in
|
}, setPeerIdWithRevealedOptions: { _, _ in
|
||||||
}, removePeer: { _ in
|
}, removePeer: { _ in
|
||||||
}, contextAction: { node, gesture in
|
}, contextAction: { node, gesture in
|
||||||
//arguments.contextAction(peer, node, gesture)
|
openPeerContextAction(peer, node, gesture)
|
||||||
}, hasTopStripe: false, noInsets: true)
|
}, hasTopStripe: false, noInsets: true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func preparedTransition(from fromEntries: [GroupsInCommonListEntry], to toEntries: [GroupsInCommonListEntry], context: AccountContext, presentationData: PresentationData, openPeer: @escaping (Peer) -> Void) -> GroupsInCommonListTransaction {
|
private func preparedTransition(from fromEntries: [GroupsInCommonListEntry], to toEntries: [GroupsInCommonListEntry], context: AccountContext, presentationData: PresentationData, openPeer: @escaping (Peer) -> Void, openPeerContextAction: @escaping (Peer, ASDisplayNode, ContextGesture?) -> Void) -> GroupsInCommonListTransaction {
|
||||||
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries)
|
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries)
|
||||||
|
|
||||||
let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) }
|
let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) }
|
||||||
let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, openPeer: openPeer), directionHint: nil) }
|
let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, openPeer: openPeer, openPeerContextAction: openPeerContextAction), directionHint: nil) }
|
||||||
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, openPeer: openPeer), directionHint: nil) }
|
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, openPeer: openPeer, openPeerContextAction: openPeerContextAction), directionHint: nil) }
|
||||||
|
|
||||||
return GroupsInCommonListTransaction(deletions: deletions, insertions: insertions, updates: updates)
|
return GroupsInCommonListTransaction(deletions: deletions, insertions: insertions, updates: updates)
|
||||||
}
|
}
|
||||||
@ -60,7 +60,8 @@ private func preparedTransition(from fromEntries: [GroupsInCommonListEntry], to
|
|||||||
final class PeerInfoGroupsInCommonPaneNode: ASDisplayNode, PeerInfoPaneNode {
|
final class PeerInfoGroupsInCommonPaneNode: ASDisplayNode, PeerInfoPaneNode {
|
||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
private let peerId: PeerId
|
private let peerId: PeerId
|
||||||
private let paneInteraction: PeerInfoPaneInteraction
|
private let chatControllerInteraction: ChatControllerInteraction
|
||||||
|
private let openPeerContextAction: (Peer, ASDisplayNode, ContextGesture?) -> Void
|
||||||
|
|
||||||
private let listNode: ListView
|
private let listNode: ListView
|
||||||
private var peers: [Peer] = []
|
private var peers: [Peer] = []
|
||||||
@ -75,10 +76,11 @@ final class PeerInfoGroupsInCommonPaneNode: ASDisplayNode, PeerInfoPaneNode {
|
|||||||
return self.ready.get()
|
return self.ready.get()
|
||||||
}
|
}
|
||||||
|
|
||||||
init(context: AccountContext, peerId: PeerId, interaction: PeerInfoPaneInteraction, peers: [Peer]) {
|
init(context: AccountContext, peerId: PeerId, chatControllerInteraction: ChatControllerInteraction, openPeerContextAction: @escaping (Peer, ASDisplayNode, ContextGesture?) -> Void, peers: [Peer]) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.peerId = peerId
|
self.peerId = peerId
|
||||||
self.paneInteraction = interaction
|
self.chatControllerInteraction = chatControllerInteraction
|
||||||
|
self.openPeerContextAction = openPeerContextAction
|
||||||
|
|
||||||
self.listNode = ListView()
|
self.listNode = ListView()
|
||||||
|
|
||||||
@ -102,14 +104,14 @@ final class PeerInfoGroupsInCommonPaneNode: ASDisplayNode, PeerInfoPaneNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func update(size: CGSize, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition) {
|
func update(size: CGSize, sideInset: CGFloat, bottomInset: CGFloat, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition) {
|
||||||
let isFirstLayout = self.currentParams == nil
|
let isFirstLayout = self.currentParams == nil
|
||||||
self.currentParams = (size, isScrollingLockedAtTop, presentationData)
|
self.currentParams = (size, isScrollingLockedAtTop, presentationData)
|
||||||
|
|
||||||
transition.updateFrame(node: self.listNode, frame: CGRect(origin: CGPoint(), size: size))
|
transition.updateFrame(node: self.listNode, frame: CGRect(origin: CGPoint(), size: size))
|
||||||
let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition)
|
let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition)
|
||||||
|
|
||||||
self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: nil, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: size, insets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0), headerInsets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0), scrollIndicatorInsets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0), duration: duration, curve: curve), stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
|
self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: nil, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: size, insets: UIEdgeInsets(top: 0.0, left: sideInset, bottom: bottomInset, right: sideInset), duration: duration, curve: curve), stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
|
||||||
|
|
||||||
self.listNode.scrollEnabled = !isScrollingLockedAtTop
|
self.listNode.scrollEnabled = !isScrollingLockedAtTop
|
||||||
|
|
||||||
@ -124,7 +126,9 @@ final class PeerInfoGroupsInCommonPaneNode: ASDisplayNode, PeerInfoPaneNode {
|
|||||||
entries.append(GroupsInCommonListEntry(index: entries.count, peer: peer))
|
entries.append(GroupsInCommonListEntry(index: entries.count, peer: peer))
|
||||||
}
|
}
|
||||||
let transaction = preparedTransition(from: self.currentEntries, to: entries, context: self.context, presentationData: presentationData, openPeer: { [weak self] peer in
|
let transaction = preparedTransition(from: self.currentEntries, to: entries, context: self.context, presentationData: presentationData, openPeer: { [weak self] peer in
|
||||||
self?.paneInteraction.openPeer(peer)
|
self?.chatControllerInteraction.openPeer(peer.id, .default, nil)
|
||||||
|
}, openPeerContextAction: { [weak self] peer, node, gesture in
|
||||||
|
self?.openPeerContextAction(peer, node, gesture)
|
||||||
})
|
})
|
||||||
self.currentEntries = entries
|
self.currentEntries = entries
|
||||||
self.enqueuedTransactions.append(transaction)
|
self.enqueuedTransactions.append(transaction)
|
||||||
@ -156,13 +160,22 @@ final class PeerInfoGroupsInCommonPaneNode: ASDisplayNode, PeerInfoPaneNode {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func updateHiddenMedia() {
|
||||||
|
}
|
||||||
|
|
||||||
func transferVelocity(_ velocity: CGFloat) {
|
func transferVelocity(_ velocity: CGFloat) {
|
||||||
|
if velocity > 0.0 {
|
||||||
|
self.listNode.transferVelocity(velocity)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func transitionNodeForGallery(messageId: MessageId, media: Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? {
|
func transitionNodeForGallery(messageId: MessageId, media: Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func addToTransitionSurface(view: UIView) {
|
||||||
|
}
|
||||||
|
|
||||||
func updateSelectedMessages(animated: Bool) {
|
func updateSelectedMessages(animated: Bool) {
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,125 @@
|
|||||||
|
import AsyncDisplayKit
|
||||||
|
import Display
|
||||||
|
import TelegramCore
|
||||||
|
import SyncCore
|
||||||
|
import SwiftSignalKit
|
||||||
|
import Postbox
|
||||||
|
import TelegramPresentationData
|
||||||
|
import AccountContext
|
||||||
|
import ContextUI
|
||||||
|
import PhotoResources
|
||||||
|
import TelegramUIPreferences
|
||||||
|
|
||||||
|
final class PeerInfoListPaneNode: ASDisplayNode, PeerInfoPaneNode {
|
||||||
|
private let context: AccountContext
|
||||||
|
private let peerId: PeerId
|
||||||
|
private let chatControllerInteraction: ChatControllerInteraction
|
||||||
|
|
||||||
|
private let listNode: ChatHistoryListNode
|
||||||
|
|
||||||
|
private var currentParams: (size: CGSize, isScrollingLockedAtTop: Bool, presentationData: PresentationData)?
|
||||||
|
|
||||||
|
private let ready = Promise<Bool>()
|
||||||
|
private var didSetReady: Bool = false
|
||||||
|
var isReady: Signal<Bool, NoError> {
|
||||||
|
return self.ready.get()
|
||||||
|
}
|
||||||
|
|
||||||
|
private let selectedMessagesPromise = Promise<Set<MessageId>?>(nil)
|
||||||
|
private var selectedMessages: Set<MessageId>? {
|
||||||
|
didSet {
|
||||||
|
if self.selectedMessages != oldValue {
|
||||||
|
self.selectedMessagesPromise.set(.single(self.selectedMessages))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var hiddenMediaDisposable: Disposable?
|
||||||
|
|
||||||
|
init(context: AccountContext, chatControllerInteraction: ChatControllerInteraction, peerId: PeerId, tagMask: MessageTags) {
|
||||||
|
self.context = context
|
||||||
|
self.peerId = peerId
|
||||||
|
self.chatControllerInteraction = chatControllerInteraction
|
||||||
|
|
||||||
|
self.selectedMessages = chatControllerInteraction.selectionState.flatMap { $0.selectedIds }
|
||||||
|
self.selectedMessagesPromise.set(.single(self.selectedMessages))
|
||||||
|
|
||||||
|
self.listNode = ChatHistoryListNode(context: context, chatLocation: .peer(peerId), tagMask: tagMask, subject: nil, controllerInteraction: chatControllerInteraction, selectedMessages: self.selectedMessagesPromise.get(), mode: .list(search: false, reversed: false))
|
||||||
|
|
||||||
|
super.init()
|
||||||
|
|
||||||
|
self.listNode.preloadPages = true
|
||||||
|
self.addSubnode(self.listNode)
|
||||||
|
|
||||||
|
self.ready.set(self.listNode.historyState.get()
|
||||||
|
|> take(1)
|
||||||
|
|> map { _ -> Bool in true })
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
self.hiddenMediaDisposable?.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
func scrollToTop() -> Bool {
|
||||||
|
let offset = self.listNode.visibleContentOffset()
|
||||||
|
switch offset {
|
||||||
|
case let .known(value) where value <= CGFloat.ulpOfOne:
|
||||||
|
return false
|
||||||
|
default:
|
||||||
|
self.listNode.scrollToEndOfHistory()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func update(size: CGSize, sideInset: CGFloat, bottomInset: CGFloat, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition) {
|
||||||
|
self.currentParams = (size, isScrollingLockedAtTop, presentationData)
|
||||||
|
|
||||||
|
transition.updateFrame(node: self.listNode, frame: CGRect(origin: CGPoint(), size: size))
|
||||||
|
let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition)
|
||||||
|
self.listNode.updateLayout(transition: transition, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: size, insets: UIEdgeInsets(top: 0.0, left: sideInset, bottom: bottomInset, right: sideInset), duration: duration, curve: curve))
|
||||||
|
self.listNode.scrollEnabled = !isScrollingLockedAtTop
|
||||||
|
}
|
||||||
|
|
||||||
|
func findLoadedMessage(id: MessageId) -> Message? {
|
||||||
|
self.listNode.messageInCurrentHistoryView(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateHiddenMedia() {
|
||||||
|
self.listNode.forEachItemNode { itemNode in
|
||||||
|
if let itemNode = itemNode as? ListMessageNode {
|
||||||
|
itemNode.updateHiddenMedia()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func transferVelocity(_ velocity: CGFloat) {
|
||||||
|
if velocity > 0.0 {
|
||||||
|
self.listNode.transferVelocity(velocity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func transitionNodeForGallery(messageId: MessageId, media: Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? {
|
||||||
|
var transitionNode: (ASDisplayNode, CGRect, () -> (UIView?, UIView?))?
|
||||||
|
self.listNode.forEachItemNode { itemNode in
|
||||||
|
if let itemNode = itemNode as? ListMessageNode {
|
||||||
|
if let result = itemNode.transitionNode(id: messageId, media: media) {
|
||||||
|
transitionNode = result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return transitionNode
|
||||||
|
}
|
||||||
|
|
||||||
|
func addToTransitionSurface(view: UIView) {
|
||||||
|
self.view.addSubview(view)
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateSelectedMessages(animated: Bool) {
|
||||||
|
self.listNode.forEachItemNode { itemNode in
|
||||||
|
if let itemNode = itemNode as? ChatMessageItemView {
|
||||||
|
itemNode.updateSelectionState(animated: animated)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.selectedMessages = self.chatControllerInteraction.selectionState.flatMap { $0.selectedIds }
|
||||||
|
}
|
||||||
|
}
|
@ -16,17 +16,20 @@ private let mediaBadgeBackgroundColor = UIColor(white: 0.0, alpha: 0.6)
|
|||||||
private let mediaBadgeTextColor = UIColor.white
|
private let mediaBadgeTextColor = UIColor.white
|
||||||
|
|
||||||
private final class VisualMediaItemInteraction {
|
private final class VisualMediaItemInteraction {
|
||||||
let openMessage: (MessageId) -> Void
|
let openMessage: (Message) -> Void
|
||||||
let toggleSelection: (MessageId) -> Void
|
let openMessageContextActions: (Message, ASDisplayNode, CGRect, ContextGesture?) -> Void
|
||||||
|
let toggleSelection: (MessageId, Bool) -> Void
|
||||||
|
|
||||||
var hiddenMedia: [MessageId: [Media]] = [:]
|
var hiddenMedia: [MessageId: [Media]] = [:]
|
||||||
var selectedMessageIds: Set<MessageId>?
|
var selectedMessageIds: Set<MessageId>?
|
||||||
|
|
||||||
init(
|
init(
|
||||||
openMessage: @escaping (MessageId) -> Void,
|
openMessage: @escaping (Message) -> Void,
|
||||||
toggleSelection: @escaping (MessageId) -> Void
|
openMessageContextActions: @escaping (Message, ASDisplayNode, CGRect, ContextGesture?) -> Void,
|
||||||
|
toggleSelection: @escaping (MessageId, Bool) -> Void
|
||||||
) {
|
) {
|
||||||
self.openMessage = openMessage
|
self.openMessage = openMessage
|
||||||
|
self.openMessageContextActions = openMessageContextActions
|
||||||
self.toggleSelection = toggleSelection
|
self.toggleSelection = toggleSelection
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -68,7 +71,12 @@ private final class VisualMediaItemNode: ASDisplayNode {
|
|||||||
self.containerNode.addSubnode(self.imageNode)
|
self.containerNode.addSubnode(self.imageNode)
|
||||||
self.containerNode.addSubnode(self.mediaBadgeNode)
|
self.containerNode.addSubnode(self.mediaBadgeNode)
|
||||||
|
|
||||||
self.containerNode.isGestureEnabled = false
|
self.containerNode.activated = { [weak self] gesture in
|
||||||
|
guard let strongSelf = self, let item = strongSelf.item else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
strongSelf.interaction.openMessageContextActions(item.0.message, strongSelf.containerNode, strongSelf.containerNode.bounds, gesture)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
@ -79,13 +87,17 @@ private final class VisualMediaItemNode: ASDisplayNode {
|
|||||||
override func didLoad() {
|
override func didLoad() {
|
||||||
super.didLoad()
|
super.didLoad()
|
||||||
|
|
||||||
self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))))
|
self.view.addGestureRecognizer(TapLongTapOrDoubleTapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))))
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func tapGesture(_ recognizer: UITapGestureRecognizer) {
|
@objc func tapGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) {
|
||||||
if case .ended = recognizer.state {
|
if case .ended = recognizer.state {
|
||||||
if let (item, _, _, _) = self.item {
|
if let (gesture, _) = recognizer.lastRecognizedGestureAndLocation {
|
||||||
self.interaction.openMessage(item.message.id)
|
if case .tap = gesture {
|
||||||
|
if let (item, _, _, _) = self.item {
|
||||||
|
self.interaction.openMessage(item.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -132,8 +144,8 @@ private final class VisualMediaItemNode: ASDisplayNode {
|
|||||||
|
|
||||||
let isStreamable = isMediaStreamable(message: item.message, media: file)
|
let isStreamable = isMediaStreamable(message: item.message, media: file)
|
||||||
|
|
||||||
let statusState: RadialStatusNodeState
|
let statusState: RadialStatusNodeState = .none
|
||||||
if isStreamable {
|
/*if isStreamable {
|
||||||
statusState = .none
|
statusState = .none
|
||||||
} else {
|
} else {
|
||||||
switch status {
|
switch status {
|
||||||
@ -145,7 +157,7 @@ private final class VisualMediaItemNode: ASDisplayNode {
|
|||||||
case .Remote:
|
case .Remote:
|
||||||
statusState = .download(.white)
|
statusState = .download(.white)
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
|
|
||||||
switch statusState {
|
switch statusState {
|
||||||
case .none:
|
case .none:
|
||||||
@ -234,7 +246,11 @@ private final class VisualMediaItemNode: ASDisplayNode {
|
|||||||
} else {
|
} else {
|
||||||
let selectionNode = GridMessageSelectionNode(theme: theme, toggle: { [weak self] value in
|
let selectionNode = GridMessageSelectionNode(theme: theme, toggle: { [weak self] value in
|
||||||
if let strongSelf = self, let messageId = strongSelf.item?.0.message.id {
|
if let strongSelf = self, let messageId = strongSelf.item?.0.message.id {
|
||||||
strongSelf.interaction.toggleSelection(messageId)
|
var toggledValue = true
|
||||||
|
if let selectedMessageIds = strongSelf.interaction.selectedMessageIds, selectedMessageIds.contains(messageId) {
|
||||||
|
toggledValue = false
|
||||||
|
}
|
||||||
|
strongSelf.interaction.toggleSelection(messageId, toggledValue)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -305,7 +321,7 @@ private final class VisualMediaItem {
|
|||||||
final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScrollViewDelegate {
|
final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScrollViewDelegate {
|
||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
private let peerId: PeerId
|
private let peerId: PeerId
|
||||||
private let interaction: PeerInfoPaneInteraction
|
private let chatControllerInteraction: ChatControllerInteraction
|
||||||
|
|
||||||
private let scrollNode: ASScrollNode
|
private let scrollNode: ASScrollNode
|
||||||
|
|
||||||
@ -314,7 +330,7 @@ final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro
|
|||||||
return self._itemInteraction!
|
return self._itemInteraction!
|
||||||
}
|
}
|
||||||
|
|
||||||
private var currentParams: (size: CGSize, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, presentationData: PresentationData)?
|
private var currentParams: (size: CGSize, sideInset: CGFloat, bottomInset: CGFloat, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, presentationData: PresentationData)?
|
||||||
|
|
||||||
private let ready = Promise<Bool>()
|
private let ready = Promise<Bool>()
|
||||||
private var didSetReady: Bool = false
|
private var didSetReady: Bool = false
|
||||||
@ -334,24 +350,27 @@ final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro
|
|||||||
|
|
||||||
private var decelerationAnimator: ConstantDisplayLinkAnimator?
|
private var decelerationAnimator: ConstantDisplayLinkAnimator?
|
||||||
|
|
||||||
init(context: AccountContext, openMessage: @escaping (MessageId) -> Bool, peerId: PeerId, interaction: PeerInfoPaneInteraction) {
|
init(context: AccountContext, chatControllerInteraction: ChatControllerInteraction, peerId: PeerId) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.peerId = peerId
|
self.peerId = peerId
|
||||||
self.interaction = interaction
|
self.chatControllerInteraction = chatControllerInteraction
|
||||||
|
|
||||||
self.scrollNode = ASScrollNode()
|
self.scrollNode = ASScrollNode()
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
self._itemInteraction = VisualMediaItemInteraction(
|
self._itemInteraction = VisualMediaItemInteraction(
|
||||||
openMessage: { id in
|
openMessage: { [weak self] message in
|
||||||
openMessage(id)
|
self?.chatControllerInteraction.openMessage(message, .default)
|
||||||
},
|
},
|
||||||
toggleSelection: { id in
|
openMessageContextActions: { [weak self] message, sourceNode, sourceRect, gesture in
|
||||||
interaction.toggleMessageSelected(id)
|
self?.chatControllerInteraction.openMessageContextActions(message, sourceNode, sourceRect, gesture)
|
||||||
|
},
|
||||||
|
toggleSelection: { [weak self] id, value in
|
||||||
|
self?.chatControllerInteraction.toggleMessagesSelection([id], value)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
self.itemInteraction.selectedMessageIds = self.interaction.selectedMessageIds
|
self.itemInteraction.selectedMessageIds = chatControllerInteraction.selectionState.flatMap { $0.selectedIds }
|
||||||
|
|
||||||
self.scrollNode.view.showsVerticalScrollIndicator = false
|
self.scrollNode.view.showsVerticalScrollIndicator = false
|
||||||
if #available(iOS 11.0, *) {
|
if #available(iOS 11.0, *) {
|
||||||
@ -416,8 +435,8 @@ final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro
|
|||||||
let wasFirstHistoryView = self.isFirstHistoryView
|
let wasFirstHistoryView = self.isFirstHistoryView
|
||||||
self.isFirstHistoryView = false
|
self.isFirstHistoryView = false
|
||||||
|
|
||||||
if let (size, visibleHeight, isScrollingLockedAtTop, presentationData) = self.currentParams {
|
if let (size, sideInset, bottomInset, visibleHeight, isScrollingLockedAtTop, presentationData) = self.currentParams {
|
||||||
self.update(size: size, visibleHeight: visibleHeight, isScrollingLockedAtTop: isScrollingLockedAtTop, presentationData: presentationData, synchronous: wasFirstHistoryView, transition: .immediate)
|
self.update(size: size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, isScrollingLockedAtTop: isScrollingLockedAtTop, presentationData: presentationData, synchronous: wasFirstHistoryView, transition: .immediate)
|
||||||
if !self.didSetReady {
|
if !self.didSetReady {
|
||||||
self.didSetReady = true
|
self.didSetReady = true
|
||||||
self.ready.set(.single(true))
|
self.ready.set(.single(true))
|
||||||
@ -444,6 +463,12 @@ final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func updateHiddenMedia() {
|
||||||
|
for (_, itemNode) in self.visibleMediaItems {
|
||||||
|
itemNode.updateHiddenMedia()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func transferVelocity(_ velocity: CGFloat) {
|
func transferVelocity(_ velocity: CGFloat) {
|
||||||
if velocity > 0.0 {
|
if velocity > 0.0 {
|
||||||
//print("transferVelocity \(velocity)")
|
//print("transferVelocity \(velocity)")
|
||||||
@ -493,15 +518,19 @@ final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func addToTransitionSurface(view: UIView) {
|
||||||
|
self.scrollNode.view.addSubview(view)
|
||||||
|
}
|
||||||
|
|
||||||
func updateSelectedMessages(animated: Bool) {
|
func updateSelectedMessages(animated: Bool) {
|
||||||
self.itemInteraction.selectedMessageIds = self.interaction.selectedMessageIds
|
self.itemInteraction.selectedMessageIds = self.chatControllerInteraction.selectionState.flatMap { $0.selectedIds }
|
||||||
for (_, itemNode) in self.visibleMediaItems {
|
for (_, itemNode) in self.visibleMediaItems {
|
||||||
itemNode.updateSelectionState(animated: animated)
|
itemNode.updateSelectionState(animated: animated)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func update(size: CGSize, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition) {
|
func update(size: CGSize, sideInset: CGFloat, bottomInset: CGFloat, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition) {
|
||||||
self.currentParams = (size, visibleHeight, isScrollingLockedAtTop, presentationData)
|
self.currentParams = (size, sideInset, bottomInset, visibleHeight, isScrollingLockedAtTop, presentationData)
|
||||||
|
|
||||||
transition.updateFrame(node: self.scrollNode, frame: CGRect(origin: CGPoint(), size: size))
|
transition.updateFrame(node: self.scrollNode, frame: CGRect(origin: CGPoint(), size: size))
|
||||||
|
|
||||||
@ -510,10 +539,10 @@ final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro
|
|||||||
let itemSize: CGFloat = floor(size.width / CGFloat(itemsInRow))
|
let itemSize: CGFloat = floor(size.width / CGFloat(itemsInRow))
|
||||||
|
|
||||||
let rowCount: Int = self.mediaItems.count / itemsInRow + (self.mediaItems.count % itemsInRow == 0 ? 0 : 1)
|
let rowCount: Int = self.mediaItems.count / itemsInRow + (self.mediaItems.count % itemsInRow == 0 ? 0 : 1)
|
||||||
let contentHeight = CGFloat(rowCount + 1) * itemSpacing + CGFloat(rowCount) * itemSize
|
let contentHeight = CGFloat(rowCount + 1) * itemSpacing + CGFloat(rowCount) * itemSize + bottomInset
|
||||||
|
|
||||||
self.scrollNode.view.contentSize = CGSize(width: size.width, height: contentHeight)
|
self.scrollNode.view.contentSize = CGSize(width: size.width, height: contentHeight)
|
||||||
self.updateVisibleItems(size: size, visibleHeight: visibleHeight, theme: presentationData.theme, synchronousLoad: synchronous)
|
self.updateVisibleItems(size: size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, theme: presentationData.theme, synchronousLoad: synchronous)
|
||||||
|
|
||||||
if isScrollingLockedAtTop {
|
if isScrollingLockedAtTop {
|
||||||
transition.updateBounds(node: self.scrollNode, bounds: CGRect(origin: CGPoint(), size: self.scrollNode.bounds.size))
|
transition.updateBounds(node: self.scrollNode, bounds: CGRect(origin: CGPoint(), size: self.scrollNode.bounds.size))
|
||||||
@ -527,8 +556,8 @@ final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro
|
|||||||
}
|
}
|
||||||
|
|
||||||
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||||
if let (size, visibleHeight, _, presentationData) = self.currentParams {
|
if let (size, sideInset, bottomInset, visibleHeight, _, presentationData) = self.currentParams {
|
||||||
self.updateVisibleItems(size: size, visibleHeight: visibleHeight, theme: presentationData.theme, synchronousLoad: false)
|
self.updateVisibleItems(size: size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, theme: presentationData.theme, synchronousLoad: false)
|
||||||
|
|
||||||
if scrollView.contentOffset.y >= scrollView.contentSize.height - scrollView.bounds.height * 2.0, let currentView = self.currentView, currentView.earlierId != nil {
|
if scrollView.contentOffset.y >= scrollView.contentSize.height - scrollView.bounds.height * 2.0, let currentView = self.currentView, currentView.earlierId != nil {
|
||||||
if !self.isRequestingView {
|
if !self.isRequestingView {
|
||||||
@ -539,10 +568,12 @@ final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateVisibleItems(size: CGSize, visibleHeight: CGFloat, theme: PresentationTheme, synchronousLoad: Bool) {
|
private func updateVisibleItems(size: CGSize, sideInset: CGFloat, bottomInset: CGFloat, visibleHeight: CGFloat, theme: PresentationTheme, synchronousLoad: Bool) {
|
||||||
|
let availableWidth = size.width - sideInset * 2.0
|
||||||
|
|
||||||
let itemSpacing: CGFloat = 1.0
|
let itemSpacing: CGFloat = 1.0
|
||||||
let itemsInRow: Int = max(3, min(6, Int(size.width / 140.0)))
|
let itemsInRow: Int = max(3, min(6, Int(availableWidth / 140.0)))
|
||||||
let itemSize: CGFloat = floor(size.width / CGFloat(itemsInRow))
|
let itemSize: CGFloat = floor(availableWidth / CGFloat(itemsInRow))
|
||||||
|
|
||||||
let rowCount: Int = self.mediaItems.count / itemsInRow + (self.mediaItems.count % itemsInRow == 0 ? 0 : 1)
|
let rowCount: Int = self.mediaItems.count / itemsInRow + (self.mediaItems.count % itemsInRow == 0 ? 0 : 1)
|
||||||
|
|
||||||
@ -562,8 +593,8 @@ final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro
|
|||||||
validIds.insert(stableId)
|
validIds.insert(stableId)
|
||||||
let rowIndex = i / Int(itemsInRow)
|
let rowIndex = i / Int(itemsInRow)
|
||||||
let columnIndex = i % Int(itemsInRow)
|
let columnIndex = i % Int(itemsInRow)
|
||||||
let itemOrigin = CGPoint(x: CGFloat(columnIndex) * (itemSize + itemSpacing), y: itemSpacing + CGFloat(rowIndex) * (itemSize + itemSpacing))
|
let itemOrigin = CGPoint(x: sideInset + CGFloat(columnIndex) * (itemSize + itemSpacing), y: itemSpacing + CGFloat(rowIndex) * (itemSize + itemSpacing))
|
||||||
let itemFrame = CGRect(origin: itemOrigin, size: CGSize(width: columnIndex == itemsInRow ? (size.width - itemOrigin.x) : itemSize, height: itemSize))
|
let itemFrame = CGRect(origin: itemOrigin, size: CGSize(width: columnIndex == itemsInRow ? (availableWidth - itemOrigin.x) : itemSize, height: itemSize))
|
||||||
let itemNode: VisualMediaItemNode
|
let itemNode: VisualMediaItemNode
|
||||||
if let current = self.visibleMediaItems[stableId] {
|
if let current = self.visibleMediaItems[stableId] {
|
||||||
itemNode = current
|
itemNode = current
|
306
submodules/TelegramUI/TelegramUI/PeerInfo/PeerInfoData.swift
Normal file
306
submodules/TelegramUI/TelegramUI/PeerInfo/PeerInfoData.swift
Normal file
@ -0,0 +1,306 @@
|
|||||||
|
import Foundation
|
||||||
|
import UIKit
|
||||||
|
import Postbox
|
||||||
|
import SyncCore
|
||||||
|
import TelegramCore
|
||||||
|
import SwiftSignalKit
|
||||||
|
import AccountContext
|
||||||
|
import PeerPresenceStatusManager
|
||||||
|
import TelegramStringFormatting
|
||||||
|
import TelegramPresentationData
|
||||||
|
|
||||||
|
final class PeerInfoState {
|
||||||
|
let isEditing: Bool
|
||||||
|
let isSearching: Bool
|
||||||
|
let selectedMessageIds: Set<MessageId>?
|
||||||
|
|
||||||
|
init(
|
||||||
|
isEditing: Bool,
|
||||||
|
isSearching: Bool,
|
||||||
|
selectedMessageIds: Set<MessageId>?
|
||||||
|
) {
|
||||||
|
self.isEditing = isEditing
|
||||||
|
self.isSearching = isSearching
|
||||||
|
self.selectedMessageIds = selectedMessageIds
|
||||||
|
}
|
||||||
|
|
||||||
|
func withIsEditing(_ isEditing: Bool) -> PeerInfoState {
|
||||||
|
return PeerInfoState(
|
||||||
|
isEditing: isEditing,
|
||||||
|
isSearching: self.isSearching,
|
||||||
|
selectedMessageIds: self.selectedMessageIds
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func withSelectedMessageIds(_ selectedMessageIds: Set<MessageId>?) -> PeerInfoState {
|
||||||
|
return PeerInfoState(
|
||||||
|
isEditing: self.isEditing,
|
||||||
|
isSearching: self.isSearching,
|
||||||
|
selectedMessageIds: selectedMessageIds
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final class PeerInfoScreenData {
|
||||||
|
let peer: Peer?
|
||||||
|
let cachedData: CachedPeerData?
|
||||||
|
let status: PeerInfoStatusData?
|
||||||
|
let notificationSettings: TelegramPeerNotificationSettings?
|
||||||
|
let globalNotificationSettings: GlobalNotificationSettings?
|
||||||
|
let isContact: Bool
|
||||||
|
let availablePanes: [PeerInfoPaneKey]
|
||||||
|
let groupsInCommon: [Peer]?
|
||||||
|
|
||||||
|
init(
|
||||||
|
peer: Peer?,
|
||||||
|
cachedData: CachedPeerData?,
|
||||||
|
status: PeerInfoStatusData?,
|
||||||
|
notificationSettings: TelegramPeerNotificationSettings?,
|
||||||
|
globalNotificationSettings: GlobalNotificationSettings?,
|
||||||
|
isContact: Bool,
|
||||||
|
availablePanes: [PeerInfoPaneKey],
|
||||||
|
groupsInCommon: [Peer]?
|
||||||
|
) {
|
||||||
|
self.peer = peer
|
||||||
|
self.cachedData = cachedData
|
||||||
|
self.status = status
|
||||||
|
self.notificationSettings = notificationSettings
|
||||||
|
self.globalNotificationSettings = globalNotificationSettings
|
||||||
|
self.isContact = isContact
|
||||||
|
self.availablePanes = availablePanes
|
||||||
|
self.groupsInCommon = groupsInCommon
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum PeerInfoScreenInputData: Equatable {
|
||||||
|
case none
|
||||||
|
case user(userId: PeerId, secretChatId: PeerId?, isBot: Bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
func peerInfoAvailableMediaPanes(context: AccountContext, peerId: PeerId) -> Signal<[PeerInfoPaneKey], NoError> {
|
||||||
|
let tags: [(MessageTags, PeerInfoPaneKey)] = [
|
||||||
|
(.photoOrVideo, .media),
|
||||||
|
(.file, .files),
|
||||||
|
(.music, .music),
|
||||||
|
(.voiceOrInstantVideo, .voice),
|
||||||
|
(.webPage, .links)
|
||||||
|
]
|
||||||
|
return combineLatest(tags.map { tagAndKey -> Signal<PeerInfoPaneKey?, NoError> in
|
||||||
|
let (tag, key) = tagAndKey
|
||||||
|
return context.account.viewTracker.aroundMessageHistoryViewForLocation(.peer(peerId), index: .upperBound, anchorIndex: .upperBound, count: 20, clipHoles: false, fixedCombinedReadStates: nil, tagMask: tag)
|
||||||
|
|> map { (view, _, _) -> PeerInfoPaneKey? in
|
||||||
|
if view.entries.isEmpty {
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
return key
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|> map { keys -> [PeerInfoPaneKey] in
|
||||||
|
return keys.compactMap { $0 }
|
||||||
|
}
|
||||||
|
|> distinctUntilChanged
|
||||||
|
/*return context.account.postbox.combinedView(keys: tags.map { (tag, _) -> PostboxViewKey in
|
||||||
|
return .historyTagInfo(peerId: peerId, tag: tag)
|
||||||
|
})
|
||||||
|
|> map { view -> [PeerInfoPaneKey] in
|
||||||
|
return tags.compactMap { (tag, key) -> PeerInfoPaneKey? in
|
||||||
|
if let info = view.views[.historyTagInfo(peerId: peerId, tag: tag)] as? HistoryTagInfoView, !info.isEmpty {
|
||||||
|
return key
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|> distinctUntilChanged*/
|
||||||
|
}
|
||||||
|
|
||||||
|
struct PeerInfoStatusData: Equatable {
|
||||||
|
var text: String
|
||||||
|
var isActivity: Bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat) -> Signal<PeerInfoScreenData, NoError> {
|
||||||
|
return context.account.postbox.combinedView(keys: [.basicPeer(peerId)])
|
||||||
|
|> map { view -> PeerInfoScreenInputData in
|
||||||
|
guard let peer = (view.views[.basicPeer(peerId)] as? BasicPeerView)?.peer else {
|
||||||
|
return .none
|
||||||
|
}
|
||||||
|
if let user = peer as? TelegramUser {
|
||||||
|
return .user(userId: user.id, secretChatId: nil, isBot: user.botInfo != nil)
|
||||||
|
} else {
|
||||||
|
preconditionFailure()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|> distinctUntilChanged
|
||||||
|
|> mapToSignal { inputData -> Signal<PeerInfoScreenData, NoError> in
|
||||||
|
switch inputData {
|
||||||
|
case .none:
|
||||||
|
return .single(PeerInfoScreenData(
|
||||||
|
peer: nil,
|
||||||
|
cachedData: nil,
|
||||||
|
status: nil,
|
||||||
|
notificationSettings: nil,
|
||||||
|
globalNotificationSettings: nil,
|
||||||
|
isContact: false,
|
||||||
|
availablePanes: [],
|
||||||
|
groupsInCommon: nil
|
||||||
|
))
|
||||||
|
case let .user(peerId, secretChatId, isBot):
|
||||||
|
let groupsInCommonSignal: Signal<[Peer]?, NoError>
|
||||||
|
if isBot {
|
||||||
|
groupsInCommonSignal = .single([])
|
||||||
|
} else {
|
||||||
|
groupsInCommonSignal = .single(nil)
|
||||||
|
|> then(
|
||||||
|
groupsInCommon(account: context.account, peerId: peerId)
|
||||||
|
|> map(Optional.init)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
enum StatusInputData: Equatable {
|
||||||
|
case none
|
||||||
|
case presence(TelegramUserPresence)
|
||||||
|
case bot
|
||||||
|
}
|
||||||
|
let status = Signal<PeerInfoStatusData?, NoError> { subscriber in
|
||||||
|
class Manager {
|
||||||
|
var currentValue: TelegramUserPresence? = nil
|
||||||
|
var updateManager: QueueLocalObject<PeerPresenceStatusManager>? = nil
|
||||||
|
}
|
||||||
|
let manager = Atomic<Manager>(value: Manager())
|
||||||
|
let notify: () -> Void = {
|
||||||
|
let data = manager.with { manager -> PeerInfoStatusData? in
|
||||||
|
if let presence = manager.currentValue {
|
||||||
|
let timestamp = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970
|
||||||
|
let (text, isActivity) = stringAndActivityForUserPresence(strings: strings, dateTimeFormat: dateTimeFormat, presence: presence, relativeTo: Int32(timestamp), expanded: true)
|
||||||
|
return PeerInfoStatusData(text: text, isActivity: isActivity)
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
subscriber.putNext(data)
|
||||||
|
}
|
||||||
|
let disposable = (context.account.viewTracker.peerView(peerId, updateData: false)
|
||||||
|
|> map { view -> StatusInputData in
|
||||||
|
guard let user = view.peers[peerId] as? TelegramUser else {
|
||||||
|
return .none
|
||||||
|
}
|
||||||
|
if user.isDeleted {
|
||||||
|
return .none
|
||||||
|
}
|
||||||
|
if user.botInfo != nil {
|
||||||
|
return .bot
|
||||||
|
}
|
||||||
|
if user.flags.contains(.isSupport) {
|
||||||
|
return .none
|
||||||
|
}
|
||||||
|
guard let presence = view.peerPresences[peerId] as? TelegramUserPresence else {
|
||||||
|
return .none
|
||||||
|
}
|
||||||
|
return .presence(presence)
|
||||||
|
}
|
||||||
|
|> distinctUntilChanged).start(next: { inputData in
|
||||||
|
switch inputData {
|
||||||
|
case .bot:
|
||||||
|
subscriber.putNext(PeerInfoStatusData(text: strings.Bot_GenericBotStatus, isActivity: false))
|
||||||
|
default:
|
||||||
|
var presence: TelegramUserPresence?
|
||||||
|
if case let .presence(value) = inputData {
|
||||||
|
presence = value
|
||||||
|
}
|
||||||
|
let _ = manager.with { manager -> Void in
|
||||||
|
manager.currentValue = presence
|
||||||
|
if let presence = presence {
|
||||||
|
let updateManager: QueueLocalObject<PeerPresenceStatusManager>
|
||||||
|
if let current = manager.updateManager {
|
||||||
|
updateManager = current
|
||||||
|
} else {
|
||||||
|
updateManager = QueueLocalObject<PeerPresenceStatusManager>(queue: .mainQueue(), generate: {
|
||||||
|
return PeerPresenceStatusManager(update: {
|
||||||
|
notify()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
updateManager.with { updateManager in
|
||||||
|
updateManager.reset(presence: presence)
|
||||||
|
}
|
||||||
|
} else if let _ = manager.updateManager {
|
||||||
|
manager.updateManager = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
notify()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return disposable
|
||||||
|
}
|
||||||
|
|> distinctUntilChanged
|
||||||
|
let globalNotificationsKey: PostboxViewKey = .preferences(keys: Set<ValueBoxKey>([PreferencesKeys.globalNotifications]))
|
||||||
|
var combinedKeys: [PostboxViewKey] = []
|
||||||
|
combinedKeys.append(globalNotificationsKey)
|
||||||
|
if let secretChatId = secretChatId {
|
||||||
|
combinedKeys.append(.peerChatState(peerId: peerId))
|
||||||
|
}
|
||||||
|
return combineLatest(
|
||||||
|
context.account.viewTracker.peerView(peerId, updateData: true),
|
||||||
|
peerInfoAvailableMediaPanes(context: context, peerId: peerId),
|
||||||
|
context.account.postbox.combinedView(keys: combinedKeys),
|
||||||
|
status,
|
||||||
|
groupsInCommonSignal
|
||||||
|
)
|
||||||
|
|> map { peerView, availablePanes, combinedView, status, groupsInCommon -> PeerInfoScreenData in
|
||||||
|
var globalNotificationSettings: GlobalNotificationSettings = .defaultSettings
|
||||||
|
if let preferencesView = combinedView.views[globalNotificationsKey] as? PreferencesView {
|
||||||
|
if let settings = preferencesView.values[PreferencesKeys.globalNotifications] as? GlobalNotificationSettings {
|
||||||
|
globalNotificationSettings = settings
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var availablePanes = availablePanes
|
||||||
|
if let groupsInCommon = groupsInCommon, !groupsInCommon.isEmpty {
|
||||||
|
availablePanes.append(.groupsInCommon)
|
||||||
|
}
|
||||||
|
|
||||||
|
return PeerInfoScreenData(
|
||||||
|
peer: peerView.peers[peerId],
|
||||||
|
cachedData: peerView.cachedData,
|
||||||
|
status: status,
|
||||||
|
notificationSettings: peerView.notificationSettings as? TelegramPeerNotificationSettings,
|
||||||
|
globalNotificationSettings: globalNotificationSettings,
|
||||||
|
isContact: peerView.peerIsContact,
|
||||||
|
availablePanes: availablePanes,
|
||||||
|
groupsInCommon: groupsInCommon
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func peerInfoHeaderButtons(peer: Peer?, cachedData: CachedPeerData?) -> [PeerInfoHeaderButtonKey] {
|
||||||
|
var result: [PeerInfoHeaderButtonKey] = []
|
||||||
|
if let user = peer as? TelegramUser {
|
||||||
|
result.append(.message)
|
||||||
|
var callsAvailable = false
|
||||||
|
if !user.isDeleted, user.botInfo == nil, !user.flags.contains(.isSupport), let cachedUserData = cachedData as? CachedUserData {
|
||||||
|
callsAvailable = cachedUserData.callsAvailable
|
||||||
|
}
|
||||||
|
if callsAvailable {
|
||||||
|
result.append(.call)
|
||||||
|
}
|
||||||
|
result.append(.mute)
|
||||||
|
|
||||||
|
if !user.isDeleted, user.botInfo == nil && !user.flags.contains(.isSupport) {
|
||||||
|
result.append(.more)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func peerInfoCanEdit(peer: Peer?, cachedData: CachedPeerData?) -> Bool {
|
||||||
|
if let user = peer as? TelegramUser {
|
||||||
|
if user.isDeleted {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
1497
submodules/TelegramUI/TelegramUI/PeerInfo/PeerInfoHeaderNode.swift
Normal file
1497
submodules/TelegramUI/TelegramUI/PeerInfo/PeerInfoHeaderNode.swift
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,553 @@
|
|||||||
|
import Foundation
|
||||||
|
import UIKit
|
||||||
|
import AsyncDisplayKit
|
||||||
|
import Display
|
||||||
|
import SwiftSignalKit
|
||||||
|
import TelegramPresentationData
|
||||||
|
import Postbox
|
||||||
|
import SyncCore
|
||||||
|
import TelegramCore
|
||||||
|
import AccountContext
|
||||||
|
import ContextUI
|
||||||
|
|
||||||
|
protocol PeerInfoPaneNode: ASDisplayNode {
|
||||||
|
var isReady: Signal<Bool, NoError> { get }
|
||||||
|
|
||||||
|
func update(size: CGSize, sideInset: CGFloat, bottomInset: CGFloat, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition)
|
||||||
|
func scrollToTop() -> Bool
|
||||||
|
func transferVelocity(_ velocity: CGFloat)
|
||||||
|
func findLoadedMessage(id: MessageId) -> Message?
|
||||||
|
func transitionNodeForGallery(messageId: MessageId, media: Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))?
|
||||||
|
func addToTransitionSurface(view: UIView)
|
||||||
|
func updateHiddenMedia()
|
||||||
|
func updateSelectedMessages(animated: Bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
final class PeerInfoPaneWrapper {
|
||||||
|
let key: PeerInfoPaneKey
|
||||||
|
let node: PeerInfoPaneNode
|
||||||
|
private var appliedParams: (CGSize, CGFloat, CGFloat, CGFloat, Bool, PresentationData)?
|
||||||
|
|
||||||
|
init(key: PeerInfoPaneKey, node: PeerInfoPaneNode) {
|
||||||
|
self.key = key
|
||||||
|
self.node = node
|
||||||
|
}
|
||||||
|
|
||||||
|
func update(size: CGSize, sideInset: CGFloat, bottomInset: CGFloat, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition) {
|
||||||
|
if let (currentSize, currentSideInset, currentBottomInset, visibleHeight, currentIsScrollingLockedAtTop, currentPresentationData) = self.appliedParams {
|
||||||
|
if currentSize == size && currentSideInset == sideInset && currentBottomInset == bottomInset, currentIsScrollingLockedAtTop == isScrollingLockedAtTop && currentPresentationData === presentationData {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.appliedParams = (size, sideInset, bottomInset, visibleHeight, isScrollingLockedAtTop, presentationData)
|
||||||
|
self.node.update(size: size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, isScrollingLockedAtTop: isScrollingLockedAtTop, presentationData: presentationData, synchronous: synchronous, transition: transition)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum PeerInfoPaneKey {
|
||||||
|
case media
|
||||||
|
case files
|
||||||
|
case links
|
||||||
|
case voice
|
||||||
|
case music
|
||||||
|
case groupsInCommon
|
||||||
|
}
|
||||||
|
|
||||||
|
final class PeerInfoPaneTabsContainerPaneNode: ASDisplayNode {
|
||||||
|
private let pressed: () -> Void
|
||||||
|
|
||||||
|
private let titleNode: ImmediateTextNode
|
||||||
|
private let buttonNode: HighlightTrackingButtonNode
|
||||||
|
|
||||||
|
init(pressed: @escaping () -> Void) {
|
||||||
|
self.pressed = pressed
|
||||||
|
|
||||||
|
self.titleNode = ImmediateTextNode()
|
||||||
|
self.titleNode.displaysAsynchronously = false
|
||||||
|
|
||||||
|
self.buttonNode = HighlightTrackingButtonNode()
|
||||||
|
|
||||||
|
super.init()
|
||||||
|
|
||||||
|
self.addSubnode(self.titleNode)
|
||||||
|
self.addSubnode(self.buttonNode)
|
||||||
|
|
||||||
|
self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
|
||||||
|
self.buttonNode.highligthedChanged = { [weak self] highlighted in
|
||||||
|
if let strongSelf = self {
|
||||||
|
if highlighted {
|
||||||
|
strongSelf.titleNode.layer.removeAnimation(forKey: "opacity")
|
||||||
|
strongSelf.titleNode.alpha = 0.4
|
||||||
|
} else {
|
||||||
|
strongSelf.titleNode.alpha = 1.0
|
||||||
|
strongSelf.titleNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func buttonPressed() {
|
||||||
|
self.pressed()
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateText(_ title: String, isSelected: Bool, presentationData: PresentationData) {
|
||||||
|
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.medium(14.0), textColor: isSelected ? presentationData.theme.list.itemAccentColor : presentationData.theme.list.itemSecondaryTextColor)
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateLayout(height: CGFloat) -> CGFloat {
|
||||||
|
let titleSize = self.titleNode.updateLayout(CGSize(width: 200.0, height: .greatestFiniteMagnitude))
|
||||||
|
self.titleNode.frame = CGRect(origin: CGPoint(x: 0.0, y: floor((height - titleSize.height) / 2.0)), size: titleSize)
|
||||||
|
return titleSize.width
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateArea(size: CGSize, sideInset: CGFloat) {
|
||||||
|
self.buttonNode.frame = CGRect(origin: CGPoint(x: -sideInset, y: 0.0), size: CGSize(width: size.width + sideInset * 2.0, height: size.height))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct PeerInfoPaneSpecifier: Equatable {
|
||||||
|
var key: PeerInfoPaneKey
|
||||||
|
var title: String
|
||||||
|
}
|
||||||
|
|
||||||
|
final class PeerInfoPaneTabsContainerNode: ASDisplayNode {
|
||||||
|
private let scrollNode: ASScrollNode
|
||||||
|
private var paneNodes: [PeerInfoPaneKey: PeerInfoPaneTabsContainerPaneNode] = [:]
|
||||||
|
private let selectedLineNode: ASImageNode
|
||||||
|
|
||||||
|
private var currentParams: ([PeerInfoPaneSpecifier], PeerInfoPaneKey?, PresentationData)?
|
||||||
|
|
||||||
|
var requestSelectPane: ((PeerInfoPaneKey) -> Void)?
|
||||||
|
|
||||||
|
override init() {
|
||||||
|
self.scrollNode = ASScrollNode()
|
||||||
|
|
||||||
|
self.selectedLineNode = ASImageNode()
|
||||||
|
self.selectedLineNode.displaysAsynchronously = false
|
||||||
|
self.selectedLineNode.displayWithoutProcessing = true
|
||||||
|
|
||||||
|
super.init()
|
||||||
|
|
||||||
|
self.scrollNode.view.disablesInteractiveTransitionGestureRecognizer = true
|
||||||
|
self.scrollNode.view.showsHorizontalScrollIndicator = false
|
||||||
|
self.scrollNode.view.scrollsToTop = false
|
||||||
|
if #available(iOS 11.0, *) {
|
||||||
|
self.scrollNode.view.contentInsetAdjustmentBehavior = .never
|
||||||
|
}
|
||||||
|
|
||||||
|
self.addSubnode(self.scrollNode)
|
||||||
|
self.scrollNode.addSubnode(self.selectedLineNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
func update(size: CGSize, presentationData: PresentationData, paneList: [PeerInfoPaneSpecifier], selectedPane: PeerInfoPaneKey?, transition: ContainedViewLayoutTransition) {
|
||||||
|
transition.updateFrame(node: self.scrollNode, frame: CGRect(origin: CGPoint(), size: size))
|
||||||
|
|
||||||
|
let focusOnSelectedPane = self.currentParams?.1 != selectedPane
|
||||||
|
|
||||||
|
if self.currentParams?.2.theme !== presentationData.theme {
|
||||||
|
self.selectedLineNode.image = generateImage(CGSize(width: 7.0, height: 4.0), rotatedContext: { size, context in
|
||||||
|
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||||
|
context.setFillColor(presentationData.theme.list.itemAccentColor.cgColor)
|
||||||
|
context.fillEllipse(in: CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.width)))
|
||||||
|
})?.stretchableImage(withLeftCapWidth: 4, topCapHeight: 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.currentParams?.0 != paneList || self.currentParams?.1 != selectedPane || self.currentParams?.2 !== presentationData {
|
||||||
|
self.currentParams = (paneList, selectedPane, presentationData)
|
||||||
|
for specifier in paneList {
|
||||||
|
let paneNode: PeerInfoPaneTabsContainerPaneNode
|
||||||
|
var wasAdded = false
|
||||||
|
if let current = self.paneNodes[specifier.key] {
|
||||||
|
paneNode = current
|
||||||
|
} else {
|
||||||
|
wasAdded = true
|
||||||
|
paneNode = PeerInfoPaneTabsContainerPaneNode(pressed: { [weak self] in
|
||||||
|
self?.paneSelected(specifier.key)
|
||||||
|
})
|
||||||
|
self.paneNodes[specifier.key] = paneNode
|
||||||
|
self.scrollNode.addSubnode(paneNode)
|
||||||
|
}
|
||||||
|
paneNode.updateText(specifier.title, isSelected: selectedPane == specifier.key, presentationData: presentationData)
|
||||||
|
}
|
||||||
|
var removeKeys: [PeerInfoPaneKey] = []
|
||||||
|
for (key, _) in self.paneNodes {
|
||||||
|
if !paneList.contains(where: { $0.key == key }) {
|
||||||
|
removeKeys.append(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for key in removeKeys {
|
||||||
|
if let paneNode = self.paneNodes.removeValue(forKey: key) {
|
||||||
|
paneNode.removeFromSupernode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var tabSizes: [(CGSize, PeerInfoPaneTabsContainerPaneNode)] = []
|
||||||
|
var totalRawTabSize: CGFloat = 0.0
|
||||||
|
|
||||||
|
var selectedFrame: CGRect?
|
||||||
|
for specifier in paneList {
|
||||||
|
guard let paneNode = self.paneNodes[specifier.key] else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
let paneNodeWidth = paneNode.updateLayout(height: size.height)
|
||||||
|
let paneNodeSize = CGSize(width: paneNodeWidth, height: size.height)
|
||||||
|
tabSizes.append((paneNodeSize, paneNode))
|
||||||
|
totalRawTabSize += paneNodeSize.width
|
||||||
|
}
|
||||||
|
|
||||||
|
let spacing: CGFloat = 32.0
|
||||||
|
if tabSizes.count == 1 {
|
||||||
|
for i in 0 ..< tabSizes.count {
|
||||||
|
let (paneNodeSize, paneNode) = tabSizes[i]
|
||||||
|
let leftOffset: CGFloat = 16.0
|
||||||
|
|
||||||
|
let paneFrame = CGRect(origin: CGPoint(x: leftOffset, y: floor((size.height - paneNodeSize.height) / 2.0)), size: paneNodeSize)
|
||||||
|
paneNode.frame = paneFrame
|
||||||
|
let areaSideInset: CGFloat = 16.0
|
||||||
|
paneNode.updateArea(size: paneFrame.size, sideInset: areaSideInset)
|
||||||
|
paneNode.hitTestSlop = UIEdgeInsets(top: 0.0, left: -areaSideInset, bottom: 0.0, right: -areaSideInset)
|
||||||
|
|
||||||
|
if paneList[i].key == selectedPane {
|
||||||
|
selectedFrame = paneFrame
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.scrollNode.view.contentSize = CGSize(width: size.width, height: size.height)
|
||||||
|
} else if totalRawTabSize + CGFloat(tabSizes.count + 1) * spacing <= size.width {
|
||||||
|
let availableSpace = size.width
|
||||||
|
let availableSpacing = availableSpace - totalRawTabSize
|
||||||
|
let perTabSpacing = floor(availableSpacing / CGFloat(tabSizes.count + 1))
|
||||||
|
|
||||||
|
var leftOffset = perTabSpacing
|
||||||
|
for i in 0 ..< tabSizes.count {
|
||||||
|
let (paneNodeSize, paneNode) = tabSizes[i]
|
||||||
|
|
||||||
|
let paneFrame = CGRect(origin: CGPoint(x: leftOffset, y: floor((size.height - paneNodeSize.height) / 2.0)), size: paneNodeSize)
|
||||||
|
paneNode.frame = paneFrame
|
||||||
|
let areaSideInset = floor(perTabSpacing / 2.0)
|
||||||
|
paneNode.updateArea(size: paneFrame.size, sideInset: areaSideInset)
|
||||||
|
paneNode.hitTestSlop = UIEdgeInsets(top: 0.0, left: -areaSideInset, bottom: 0.0, right: -areaSideInset)
|
||||||
|
|
||||||
|
leftOffset += paneNodeSize.width + perTabSpacing
|
||||||
|
|
||||||
|
if paneList[i].key == selectedPane {
|
||||||
|
selectedFrame = paneFrame
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.scrollNode.view.contentSize = CGSize(width: size.width, height: size.height)
|
||||||
|
} else {
|
||||||
|
let sideInset: CGFloat = 16.0
|
||||||
|
var leftOffset: CGFloat = sideInset
|
||||||
|
for i in 0 ..< tabSizes.count {
|
||||||
|
let (paneNodeSize, paneNode) = tabSizes[i]
|
||||||
|
let paneFrame = CGRect(origin: CGPoint(x: leftOffset, y: floor((size.height - paneNodeSize.height) / 2.0)), size: paneNodeSize)
|
||||||
|
paneNode.frame = paneFrame
|
||||||
|
paneNode.updateArea(size: paneFrame.size, sideInset: spacing)
|
||||||
|
paneNode.hitTestSlop = UIEdgeInsets(top: 0.0, left: -spacing, bottom: 0.0, right: -spacing)
|
||||||
|
if paneList[i].key == selectedPane {
|
||||||
|
selectedFrame = paneFrame
|
||||||
|
}
|
||||||
|
leftOffset += paneNodeSize.width + spacing
|
||||||
|
}
|
||||||
|
self.scrollNode.view.contentSize = CGSize(width: leftOffset + sideInset, height: size.height)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let selectedFrame = selectedFrame {
|
||||||
|
self.selectedLineNode.isHidden = false
|
||||||
|
transition.updateFrame(node: self.selectedLineNode, frame: CGRect(origin: CGPoint(x: selectedFrame.minX, y: size.height - 4.0), size: CGSize(width: selectedFrame.width, height: 4.0)))
|
||||||
|
if focusOnSelectedPane {
|
||||||
|
if selectedPane == paneList.first?.key {
|
||||||
|
transition.updateBounds(node: self.scrollNode, bounds: CGRect(origin: CGPoint(), size: self.scrollNode.bounds.size))
|
||||||
|
} else if selectedPane == paneList.last?.key {
|
||||||
|
transition.updateBounds(node: self.scrollNode, bounds: CGRect(origin: CGPoint(x: self.scrollNode.view.contentSize.width - self.scrollNode.bounds.width, y: 0.0), size: self.scrollNode.bounds.size))
|
||||||
|
} else {
|
||||||
|
let contentOffsetX = max(0.0, min(self.scrollNode.view.contentSize.width - self.scrollNode.bounds.width, floor(selectedFrame.midX - self.scrollNode.bounds.width / 2.0)))
|
||||||
|
transition.updateBounds(node: self.scrollNode, bounds: CGRect(origin: CGPoint(x: contentOffsetX, y: 0.0), size: self.scrollNode.bounds.size))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.selectedLineNode.isHidden = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func paneSelected(_ key: PeerInfoPaneKey) {
|
||||||
|
self.requestSelectPane?(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final class PeerInfoPaneContainerNode: ASDisplayNode {
|
||||||
|
private let context: AccountContext
|
||||||
|
private let peerId: PeerId
|
||||||
|
|
||||||
|
private let coveringBackgroundNode: ASDisplayNode
|
||||||
|
private let separatorNode: ASDisplayNode
|
||||||
|
private let tabsContainerNode: PeerInfoPaneTabsContainerNode
|
||||||
|
private let tapsSeparatorNode: ASDisplayNode
|
||||||
|
|
||||||
|
let isReady = Promise<Bool>()
|
||||||
|
var didSetIsReady = false
|
||||||
|
|
||||||
|
private var currentParams: (size: CGSize, sideInset: CGFloat, bottomInset: CGFloat, visibleHeight: CGFloat, expansionFraction: CGFloat, presentationData: PresentationData, data: PeerInfoScreenData?)?
|
||||||
|
private(set) var currentPaneKey: PeerInfoPaneKey?
|
||||||
|
private(set) var currentPane: PeerInfoPaneWrapper?
|
||||||
|
|
||||||
|
private var currentCandidatePaneKey: PeerInfoPaneKey?
|
||||||
|
private var candidatePane: (PeerInfoPaneWrapper, Disposable, Bool)?
|
||||||
|
|
||||||
|
var selectionPanelNode: PeerInfoSelectionPanelNode?
|
||||||
|
|
||||||
|
var chatControllerInteraction: ChatControllerInteraction?
|
||||||
|
var openPeerContextAction: ((Peer, ASDisplayNode, ContextGesture?) -> Void)?
|
||||||
|
|
||||||
|
var currentPaneUpdated: (() -> Void)?
|
||||||
|
|
||||||
|
private var currentAvailablePanes: [PeerInfoPaneKey]?
|
||||||
|
|
||||||
|
init(context: AccountContext, peerId: PeerId) {
|
||||||
|
self.context = context
|
||||||
|
self.peerId = peerId
|
||||||
|
|
||||||
|
self.separatorNode = ASDisplayNode()
|
||||||
|
self.separatorNode.isLayerBacked = true
|
||||||
|
|
||||||
|
self.coveringBackgroundNode = ASDisplayNode()
|
||||||
|
self.coveringBackgroundNode.isLayerBacked = true
|
||||||
|
|
||||||
|
self.tabsContainerNode = PeerInfoPaneTabsContainerNode()
|
||||||
|
|
||||||
|
self.tapsSeparatorNode = ASDisplayNode()
|
||||||
|
self.tapsSeparatorNode.isLayerBacked = true
|
||||||
|
|
||||||
|
super.init()
|
||||||
|
|
||||||
|
self.addSubnode(self.separatorNode)
|
||||||
|
self.addSubnode(self.coveringBackgroundNode)
|
||||||
|
self.addSubnode(self.tabsContainerNode)
|
||||||
|
self.addSubnode(self.tapsSeparatorNode)
|
||||||
|
|
||||||
|
self.tabsContainerNode.requestSelectPane = { [weak self] key in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if strongSelf.currentPaneKey == key {
|
||||||
|
strongSelf.currentPane?.node.scrollToTop()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if strongSelf.currentCandidatePaneKey == key {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
strongSelf.currentCandidatePaneKey = key
|
||||||
|
|
||||||
|
if let (size, sideInset, bottomInset, visibleHeight, expansionFraction, presentationData, data) = strongSelf.currentParams {
|
||||||
|
strongSelf.update(size: size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, expansionFraction: expansionFraction, presentationData: presentationData, data: data, transition: .immediate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func scrollToTop() -> Bool {
|
||||||
|
if let currentPane = self.currentPane {
|
||||||
|
return currentPane.node.scrollToTop()
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func findLoadedMessage(id: MessageId) -> Message? {
|
||||||
|
return self.currentPane?.node.findLoadedMessage(id: id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateHiddenMedia() {
|
||||||
|
self.currentPane?.node.updateHiddenMedia()
|
||||||
|
}
|
||||||
|
|
||||||
|
func transitionNodeForGallery(messageId: MessageId, media: Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? {
|
||||||
|
return self.currentPane?.node.transitionNodeForGallery(messageId: messageId, media: media)
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateSelectedMessageIds(_ selectedMessageIds: Set<MessageId>?, animated: Bool) {
|
||||||
|
self.currentPane?.node.updateSelectedMessages(animated: animated)
|
||||||
|
self.candidatePane?.0.node.updateSelectedMessages(animated: animated)
|
||||||
|
}
|
||||||
|
|
||||||
|
func update(size: CGSize, sideInset: CGFloat, bottomInset: CGFloat, visibleHeight: CGFloat, expansionFraction: CGFloat, presentationData: PresentationData, data: PeerInfoScreenData?, transition: ContainedViewLayoutTransition) {
|
||||||
|
let previousAvailablePanes = self.currentAvailablePanes ?? []
|
||||||
|
let availablePanes = data?.availablePanes ?? []
|
||||||
|
self.currentAvailablePanes = availablePanes
|
||||||
|
|
||||||
|
if let currentPaneKey = self.currentPaneKey, !availablePanes.contains(currentPaneKey) {
|
||||||
|
var nextCandidatePaneKey: PeerInfoPaneKey?
|
||||||
|
if let index = previousAvailablePanes.index(of: currentPaneKey), index != 0 {
|
||||||
|
for i in (0 ... index - 1).reversed() {
|
||||||
|
if availablePanes.contains(previousAvailablePanes[i]) {
|
||||||
|
nextCandidatePaneKey = previousAvailablePanes[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if nextCandidatePaneKey == nil {
|
||||||
|
nextCandidatePaneKey = availablePanes.first
|
||||||
|
}
|
||||||
|
|
||||||
|
if let nextCandidatePaneKey = nextCandidatePaneKey {
|
||||||
|
if self.currentCandidatePaneKey != nextCandidatePaneKey {
|
||||||
|
self.currentCandidatePaneKey = nextCandidatePaneKey
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.currentCandidatePaneKey = nil
|
||||||
|
if let (_, disposable, _) = self.candidatePane {
|
||||||
|
disposable.dispose()
|
||||||
|
self.candidatePane = nil
|
||||||
|
}
|
||||||
|
if let currentPane = self.currentPane {
|
||||||
|
self.currentPane = nil
|
||||||
|
currentPane.node.removeFromSupernode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if self.currentPaneKey == nil {
|
||||||
|
self.currentCandidatePaneKey = availablePanes.first
|
||||||
|
}
|
||||||
|
|
||||||
|
let previousCurrentPaneKey = self.currentPaneKey
|
||||||
|
|
||||||
|
self.currentParams = (size, sideInset, bottomInset, visibleHeight, expansionFraction, presentationData, data)
|
||||||
|
|
||||||
|
transition.updateAlpha(node: self.coveringBackgroundNode, alpha: expansionFraction)
|
||||||
|
|
||||||
|
self.backgroundColor = presentationData.theme.list.itemBlocksBackgroundColor
|
||||||
|
self.coveringBackgroundNode.backgroundColor = presentationData.theme.rootController.navigationBar.backgroundColor
|
||||||
|
self.separatorNode.backgroundColor = presentationData.theme.list.itemBlocksSeparatorColor
|
||||||
|
self.tapsSeparatorNode.backgroundColor = presentationData.theme.list.itemBlocksSeparatorColor
|
||||||
|
|
||||||
|
let tabsHeight: CGFloat = 48.0
|
||||||
|
|
||||||
|
transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: size.width, height: UIScreenPixel)))
|
||||||
|
transition.updateFrame(node: self.coveringBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: size.width, height: tabsHeight + UIScreenPixel)))
|
||||||
|
|
||||||
|
transition.updateFrame(node: self.tapsSeparatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: tabsHeight - UIScreenPixel), size: CGSize(width: size.width, height: UIScreenPixel)))
|
||||||
|
|
||||||
|
let paneFrame = CGRect(origin: CGPoint(x: 0.0, y: tabsHeight), size: CGSize(width: size.width, height: size.height - tabsHeight))
|
||||||
|
|
||||||
|
if let currentCandidatePaneKey = self.currentCandidatePaneKey {
|
||||||
|
if self.candidatePane?.0.key != currentCandidatePaneKey {
|
||||||
|
self.candidatePane?.1.dispose()
|
||||||
|
|
||||||
|
let paneNode: PeerInfoPaneNode
|
||||||
|
switch currentCandidatePaneKey {
|
||||||
|
case .media:
|
||||||
|
paneNode = PeerInfoVisualMediaPaneNode(context: self.context, chatControllerInteraction: self.chatControllerInteraction!, peerId: self.peerId)
|
||||||
|
case .files:
|
||||||
|
paneNode = PeerInfoListPaneNode(context: self.context, chatControllerInteraction: self.chatControllerInteraction!, peerId: self.peerId, tagMask: .file)
|
||||||
|
case .links:
|
||||||
|
paneNode = PeerInfoListPaneNode(context: self.context, chatControllerInteraction: self.chatControllerInteraction!, peerId: self.peerId, tagMask: .webPage)
|
||||||
|
case .voice:
|
||||||
|
paneNode = PeerInfoListPaneNode(context: self.context, chatControllerInteraction: self.chatControllerInteraction!, peerId: self.peerId, tagMask: .voiceOrInstantVideo)
|
||||||
|
case .music:
|
||||||
|
paneNode = PeerInfoListPaneNode(context: self.context, chatControllerInteraction: self.chatControllerInteraction!, peerId: self.peerId, tagMask: .music)
|
||||||
|
case .groupsInCommon:
|
||||||
|
paneNode = PeerInfoGroupsInCommonPaneNode(context: self.context, peerId: peerId, chatControllerInteraction: self.chatControllerInteraction!, openPeerContextAction: self.openPeerContextAction!, peers: data?.groupsInCommon ?? [])
|
||||||
|
}
|
||||||
|
|
||||||
|
let disposable = MetaDisposable()
|
||||||
|
self.candidatePane = (PeerInfoPaneWrapper(key: currentCandidatePaneKey, node: paneNode), disposable, false)
|
||||||
|
|
||||||
|
var shouldReLayout = false
|
||||||
|
disposable.set((paneNode.isReady
|
||||||
|
|> take(1)
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak self] _ in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if let (candidatePane, disposable, _) = strongSelf.candidatePane {
|
||||||
|
strongSelf.candidatePane = (candidatePane, disposable, true)
|
||||||
|
|
||||||
|
if shouldReLayout {
|
||||||
|
if let (size, sideInset, bottomInset, visibleHeight, expansionFraction, presentationData, data) = strongSelf.currentParams {
|
||||||
|
strongSelf.update(size: size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, expansionFraction: expansionFraction, presentationData: presentationData, data: data, transition: strongSelf.currentPane != nil ? .animated(duration: 0.35, curve: .spring) : .immediate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
shouldReLayout = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let (candidatePane, _, isReady) = self.candidatePane, isReady {
|
||||||
|
let previousPane = self.currentPane
|
||||||
|
self.candidatePane = nil
|
||||||
|
self.currentPaneKey = candidatePane.key
|
||||||
|
self.currentCandidatePaneKey = nil
|
||||||
|
self.currentPane = candidatePane
|
||||||
|
|
||||||
|
if let selectionPanelNode = self.selectionPanelNode {
|
||||||
|
self.insertSubnode(candidatePane.node, belowSubnode: selectionPanelNode)
|
||||||
|
} else {
|
||||||
|
self.addSubnode(candidatePane.node)
|
||||||
|
}
|
||||||
|
candidatePane.node.frame = paneFrame
|
||||||
|
candidatePane.update(size: paneFrame.size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: max(0.0, visibleHeight - paneFrame.minY), isScrollingLockedAtTop: expansionFraction < 1.0 - CGFloat.ulpOfOne, presentationData: presentationData, synchronous: true, transition: .immediate)
|
||||||
|
|
||||||
|
if let previousPane = previousPane {
|
||||||
|
let directionToRight: Bool
|
||||||
|
if let previousIndex = availablePanes.index(of: previousPane.key), let updatedIndex = availablePanes.index(of: candidatePane.key) {
|
||||||
|
directionToRight = previousIndex < updatedIndex
|
||||||
|
} else {
|
||||||
|
directionToRight = false
|
||||||
|
}
|
||||||
|
|
||||||
|
let offset: CGFloat = directionToRight ? previousPane.node.bounds.width : -previousPane.node.bounds.width
|
||||||
|
|
||||||
|
transition.animatePositionAdditive(node: candidatePane.node, offset: CGPoint(x: offset, y: 0.0))
|
||||||
|
let previousNode = previousPane.node
|
||||||
|
transition.updateFrame(node: previousNode, frame: paneFrame.offsetBy(dx: -offset, dy: 0.0), completion: { [weak previousNode] _ in
|
||||||
|
previousNode?.removeFromSupernode()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else if let currentPane = self.currentPane {
|
||||||
|
let paneWasAdded = currentPane.node.supernode == nil
|
||||||
|
if paneWasAdded {
|
||||||
|
self.addSubnode(currentPane.node)
|
||||||
|
}
|
||||||
|
|
||||||
|
let paneTransition: ContainedViewLayoutTransition = paneWasAdded ? .immediate : transition
|
||||||
|
paneTransition.updateFrame(node: currentPane.node, frame: paneFrame)
|
||||||
|
currentPane.update(size: paneFrame.size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, isScrollingLockedAtTop: expansionFraction < 1.0 - CGFloat.ulpOfOne, presentationData: presentationData, synchronous: paneWasAdded, transition: paneTransition)
|
||||||
|
}
|
||||||
|
|
||||||
|
transition.updateFrame(node: self.tabsContainerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: tabsHeight)))
|
||||||
|
self.tabsContainerNode.update(size: CGSize(width: size.width, height: tabsHeight), presentationData: presentationData, paneList: availablePanes.map { key in
|
||||||
|
let title: String
|
||||||
|
switch key {
|
||||||
|
case .media:
|
||||||
|
title = "Media"
|
||||||
|
case .files:
|
||||||
|
title = "Files"
|
||||||
|
case .links:
|
||||||
|
title = "Links"
|
||||||
|
case .voice:
|
||||||
|
title = "Voice Messages"
|
||||||
|
case .music:
|
||||||
|
title = "Audio"
|
||||||
|
case .groupsInCommon:
|
||||||
|
title = "Groups"
|
||||||
|
}
|
||||||
|
return PeerInfoPaneSpecifier(key: key, title: title)
|
||||||
|
}, selectedPane: self.currentPaneKey, transition: transition)
|
||||||
|
|
||||||
|
if let (candidatePane, _, _) = self.candidatePane {
|
||||||
|
let paneTransition: ContainedViewLayoutTransition = .immediate
|
||||||
|
paneTransition.updateFrame(node: candidatePane.node, frame: paneFrame)
|
||||||
|
candidatePane.update(size: paneFrame.size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, isScrollingLockedAtTop: expansionFraction < 1.0 - CGFloat.ulpOfOne, presentationData: presentationData, synchronous: true, transition: paneTransition)
|
||||||
|
}
|
||||||
|
if !self.didSetIsReady && data != nil {
|
||||||
|
if let currentPane = self.currentPane {
|
||||||
|
self.didSetIsReady = true
|
||||||
|
self.isReady.set(currentPane.node.isReady)
|
||||||
|
} else if self.candidatePane == nil {
|
||||||
|
self.didSetIsReady = true
|
||||||
|
self.isReady.set(.single(true))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let previousCurrentPaneKey = previousCurrentPaneKey, self.currentPaneKey != previousCurrentPaneKey {
|
||||||
|
self.currentPaneUpdated?()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
@ -1,217 +0,0 @@
|
|||||||
import AsyncDisplayKit
|
|
||||||
import Display
|
|
||||||
import TelegramCore
|
|
||||||
import SyncCore
|
|
||||||
import SwiftSignalKit
|
|
||||||
import Postbox
|
|
||||||
import TelegramPresentationData
|
|
||||||
import AccountContext
|
|
||||||
import ContextUI
|
|
||||||
import PhotoResources
|
|
||||||
import TelegramUIPreferences
|
|
||||||
|
|
||||||
final class PeerInfoListPaneNode: ASDisplayNode, PeerInfoPaneNode {
|
|
||||||
private let context: AccountContext
|
|
||||||
private let peerId: PeerId
|
|
||||||
private let paneInteraction: PeerInfoPaneInteraction
|
|
||||||
private let controllerInteraction: ChatControllerInteraction
|
|
||||||
|
|
||||||
private let listNode: ChatHistoryListNode
|
|
||||||
|
|
||||||
private var currentParams: (size: CGSize, isScrollingLockedAtTop: Bool, presentationData: PresentationData)?
|
|
||||||
|
|
||||||
private let ready = Promise<Bool>()
|
|
||||||
private var didSetReady: Bool = false
|
|
||||||
var isReady: Signal<Bool, NoError> {
|
|
||||||
return self.ready.get()
|
|
||||||
}
|
|
||||||
|
|
||||||
private let selectedMessagesPromise = Promise<Set<MessageId>?>(nil)
|
|
||||||
private var selectedMessages: Set<MessageId>? {
|
|
||||||
didSet {
|
|
||||||
if self.selectedMessages != oldValue {
|
|
||||||
self.selectedMessagesPromise.set(.single(self.selectedMessages))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private var hiddenMediaDisposable: Disposable?
|
|
||||||
|
|
||||||
init(context: AccountContext, openMessage: @escaping (MessageId) -> Bool, peerId: PeerId, tagMask: MessageTags, interaction: PeerInfoPaneInteraction) {
|
|
||||||
self.context = context
|
|
||||||
self.peerId = peerId
|
|
||||||
self.paneInteraction = interaction
|
|
||||||
|
|
||||||
var openMessageImpl: ((MessageId) -> Bool)?
|
|
||||||
var toggleMessageSelectionImpl: (([MessageId]) -> Void)?
|
|
||||||
self.controllerInteraction = ChatControllerInteraction(openMessage: { message, _ in
|
|
||||||
return openMessageImpl?(message.id) ?? false
|
|
||||||
}, openPeer: { _, _, _ in
|
|
||||||
}, openPeerMention: { _ in
|
|
||||||
}, openMessageContextMenu: { _, _, _, _, _ in
|
|
||||||
}, openMessageContextActions: { _, _, _, _ in
|
|
||||||
}, navigateToMessage: { _, _ in
|
|
||||||
}, tapMessage: nil, clickThroughMessage: {
|
|
||||||
}, toggleMessagesSelection: { ids, _ in
|
|
||||||
toggleMessageSelectionImpl?(ids)
|
|
||||||
}, sendCurrentMessage: { _ in
|
|
||||||
}, sendMessage: { _ in
|
|
||||||
}, sendSticker: { _, _, _, _ in
|
|
||||||
return false
|
|
||||||
}, sendGif: { _, _, _ in
|
|
||||||
return false
|
|
||||||
}, requestMessageActionCallback: { _, _, _ in
|
|
||||||
}, requestMessageActionUrlAuth: { _, _, _ in
|
|
||||||
}, activateSwitchInline: { _, _ in
|
|
||||||
}, openUrl: { _, _, _, _ in
|
|
||||||
}, shareCurrentLocation: {
|
|
||||||
}, shareAccountContact: {
|
|
||||||
}, sendBotCommand: { _, _ in
|
|
||||||
}, openInstantPage: { _, _ in
|
|
||||||
}, openWallpaper: { _ in
|
|
||||||
}, openTheme: {_ in
|
|
||||||
}, openHashtag: { _, _ in
|
|
||||||
}, updateInputState: { _ in
|
|
||||||
}, updateInputMode: { _ in
|
|
||||||
}, openMessageShareMenu: { _ in
|
|
||||||
}, presentController: { _, _ in
|
|
||||||
}, navigationController: {
|
|
||||||
return nil
|
|
||||||
}, chatControllerNode: {
|
|
||||||
return nil
|
|
||||||
}, reactionContainerNode: {
|
|
||||||
return nil
|
|
||||||
}, presentGlobalOverlayController: { _, _ in
|
|
||||||
}, callPeer: { _ in
|
|
||||||
}, longTap: { _, _ in
|
|
||||||
}, openCheckoutOrReceipt: { _ in
|
|
||||||
}, openSearch: {
|
|
||||||
}, setupReply: { _ in
|
|
||||||
}, canSetupReply: { _ in
|
|
||||||
return false
|
|
||||||
}, navigateToFirstDateMessage: { _ in
|
|
||||||
}, requestRedeliveryOfFailedMessages: { _ in
|
|
||||||
}, addContact: { _ in
|
|
||||||
}, rateCall: { _, _ in
|
|
||||||
}, requestSelectMessagePollOptions: { _, _ in
|
|
||||||
}, requestOpenMessagePollResults: { _, _ in
|
|
||||||
}, openAppStorePage: {
|
|
||||||
}, displayMessageTooltip: { _, _, _, _ in
|
|
||||||
}, seekToTimecode: { _, _, _ in
|
|
||||||
}, scheduleCurrentMessage: {
|
|
||||||
}, sendScheduledMessagesNow: { _ in
|
|
||||||
}, editScheduledMessagesTime: { _ in
|
|
||||||
}, performTextSelectionAction: { _, _, _ in
|
|
||||||
}, updateMessageReaction: { _, _ in
|
|
||||||
}, openMessageReactions: { _ in
|
|
||||||
}, displaySwipeToReplyHint: {
|
|
||||||
}, dismissReplyMarkupMessage: { _ in
|
|
||||||
}, openMessagePollResults: { _, _ in
|
|
||||||
}, openPollCreation: { _ in
|
|
||||||
}, requestMessageUpdate: { _ in
|
|
||||||
}, cancelInteractiveKeyboardGestures: {
|
|
||||||
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(loopAnimatedStickers: false))
|
|
||||||
self.controllerInteraction.selectionState = self.paneInteraction.selectedMessageIds.flatMap { ids in
|
|
||||||
return ChatInterfaceSelectionState(selectedIds: ids)
|
|
||||||
}
|
|
||||||
self.selectedMessages = self.paneInteraction.selectedMessageIds
|
|
||||||
self.selectedMessagesPromise.set(.single(self.selectedMessages))
|
|
||||||
|
|
||||||
self.listNode = ChatHistoryListNode(context: context, chatLocation: .peer(peerId), tagMask: tagMask, subject: nil, controllerInteraction: controllerInteraction, selectedMessages: self.selectedMessagesPromise.get(), mode: .list(search: false, reversed: false))
|
|
||||||
|
|
||||||
super.init()
|
|
||||||
|
|
||||||
openMessageImpl = { id in
|
|
||||||
return openMessage(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleMessageSelectionImpl = { [weak self] ids in
|
|
||||||
for id in ids {
|
|
||||||
self?.paneInteraction.toggleMessageSelected(id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.hiddenMediaDisposable = context.sharedContext.mediaManager.galleryHiddenMediaManager.hiddenIds().start(next: { [weak self] ids in
|
|
||||||
guard let strongSelf = self else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var hiddenMedia: [MessageId: [Media]] = [:]
|
|
||||||
for id in ids {
|
|
||||||
if case let .chat(accountId, messageId, media) = id, accountId == strongSelf.context.account.id {
|
|
||||||
hiddenMedia[messageId] = [media]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
strongSelf.controllerInteraction.hiddenMedia = hiddenMedia
|
|
||||||
strongSelf.listNode.forEachItemNode { itemNode in
|
|
||||||
if let itemNode = itemNode as? ListMessageNode {
|
|
||||||
itemNode.updateHiddenMedia()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
self.listNode.preloadPages = true
|
|
||||||
self.addSubnode(self.listNode)
|
|
||||||
|
|
||||||
self.ready.set(self.listNode.historyState.get()
|
|
||||||
|> take(1)
|
|
||||||
|> map { _ -> Bool in true })
|
|
||||||
}
|
|
||||||
|
|
||||||
deinit {
|
|
||||||
self.hiddenMediaDisposable?.dispose()
|
|
||||||
}
|
|
||||||
|
|
||||||
func scrollToTop() -> Bool {
|
|
||||||
let offset = self.listNode.visibleContentOffset()
|
|
||||||
switch offset {
|
|
||||||
case let .known(value) where value <= CGFloat.ulpOfOne:
|
|
||||||
return false
|
|
||||||
default:
|
|
||||||
self.listNode.scrollToEndOfHistory()
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func update(size: CGSize, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition) {
|
|
||||||
self.currentParams = (size, isScrollingLockedAtTop, presentationData)
|
|
||||||
|
|
||||||
transition.updateFrame(node: self.listNode, frame: CGRect(origin: CGPoint(), size: size))
|
|
||||||
let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition)
|
|
||||||
self.listNode.updateLayout(transition: transition, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: size, insets: UIEdgeInsets(), duration: duration, curve: curve))
|
|
||||||
self.listNode.scrollEnabled = !isScrollingLockedAtTop
|
|
||||||
}
|
|
||||||
|
|
||||||
func findLoadedMessage(id: MessageId) -> Message? {
|
|
||||||
self.listNode.messageInCurrentHistoryView(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func transferVelocity(_ velocity: CGFloat) {
|
|
||||||
if velocity > 0.0 {
|
|
||||||
self.listNode.transferVelocity(velocity)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func transitionNodeForGallery(messageId: MessageId, media: Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? {
|
|
||||||
var transitionNode: (ASDisplayNode, CGRect, () -> (UIView?, UIView?))?
|
|
||||||
self.listNode.forEachItemNode { itemNode in
|
|
||||||
if let itemNode = itemNode as? ListMessageNode {
|
|
||||||
if let result = itemNode.transitionNode(id: messageId, media: media) {
|
|
||||||
transitionNode = result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return transitionNode
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateSelectedMessages(animated: Bool) {
|
|
||||||
self.controllerInteraction.selectionState = self.paneInteraction.selectedMessageIds.flatMap { ids in
|
|
||||||
return ChatInterfaceSelectionState(selectedIds: ids)
|
|
||||||
}
|
|
||||||
self.listNode.forEachItemNode { itemNode in
|
|
||||||
if let itemNode = itemNode as? ChatMessageItemView {
|
|
||||||
itemNode.updateSelectionState(animated: animated)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.selectedMessages = self.paneInteraction.selectedMessageIds
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user