This commit is contained in:
Isaac
2026-01-13 19:21:49 +04:00
parent 3773777141
commit 2db08cde89
167 changed files with 4322 additions and 3473 deletions
@@ -138,7 +138,7 @@ final class AuthorizationSequenceSignUpController: ViewController {
})))
let contextController = ContextController(presentationData: self.presentationData, source: .reference(AuthorizationContextReferenceContentSource(controller: self, sourceNode: node)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
let contextController = makeContextController(presentationData: self.presentationData, source: .reference(AuthorizationContextReferenceContentSource(controller: self, sourceNode: node)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
self.present(contextController, in: .window(.root))
}
@@ -443,7 +443,7 @@ final class BrowserAddressListComponent: Component {
}
let items = ContextController.Items(content: .list(itemList))
let controller = ContextController(
let controller = makeContextController(
presentationData: presentationData,
source: .extracted(BrowserAddressListContextExtractedContentSource(contentView: sourceView)),
items: .single(items),
@@ -273,7 +273,7 @@ public final class BrowserBookmarksScreen: ViewController {
})))
let items = ContextController.Items(content: .list(itemList))
let controller = ContextController(
let controller = makeContextController(
presentationData: presentationData,
source: .extracted(BrowserBookmarksContextExtractedContentSource(contentNode: sourceNode)),
items: .single(items),
@@ -1198,7 +1198,7 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg
}
private func activatePinchPreview(sourceNode: PinchSourceContainerNode) {
let pinchController = PinchController(sourceNode: sourceNode, getContentAreaInScreenSpace: { [weak self] in
let pinchController = makePinchController(sourceNode: sourceNode, getContentAreaInScreenSpace: { [weak self] in
guard let self else {
return CGRect()
}
@@ -1230,7 +1230,7 @@ public class BrowserScreen: ViewController, MinimizableController {
return ContextController.Items(content: .list(items))
}
let contextController = ContextController(presentationData: self.presentationData, source: source, items: items)
let contextController = makeContextController(presentationData: self.presentationData, source: source, items: items)
contextController.dismissed = { [weak content] in
if let webContent = content as? BrowserWebContent {
webContent.releaseInstantView()
@@ -1342,7 +1342,7 @@ public class BrowserScreen: ViewController, MinimizableController {
return
}
let contextController = ContextController(presentationData: self.presentationData, source: source, items: .single(ContextController.Items(content: .list(items))))
let contextController = makeContextController(presentationData: self.presentationData, source: source, items: .single(ContextController.Items(content: .list(items))))
self.controller?.present(contextController, in: .window(.root))
}
@@ -517,7 +517,7 @@ public final class CallListController: TelegramBaseController {
}
}
let contextController = ContextController(presentationData: self.presentationData, source: .extracted(ExtractedContentSourceImpl(controller: self, sourceNode: buttonNode.contentNode, keepInPlace: false, blurBackground: false)), items: .single(ContextController.Items(content: .list(items))), gesture: nil)
let contextController = makeContextController(presentationData: self.presentationData, source: .extracted(ExtractedContentSourceImpl(controller: self, sourceNode: buttonNode.contentNode, keepInPlace: false, blurBackground: false)), items: .single(ContextController.Items(content: .list(items))), gesture: nil)
self.presentInGlobalOverlay(contextController)
}
@@ -782,7 +782,7 @@ public final class CallListController: TelegramBaseController {
})
})))
let controller = ContextController(presentationData: self.presentationData, source: .reference(CallListTabBarContextReferenceContentSource(controller: self, sourceView: sourceView)), items: .single(ContextController.Items(content: .list(items))), recognizer: nil, gesture: gesture)
let controller = makeContextController(presentationData: self.presentationData, source: .reference(CallListTabBarContextReferenceContentSource(controller: self, sourceView: sourceView)), items: .single(ContextController.Items(content: .list(items))), recognizer: nil, gesture: gesture)
self.context.sharedContext.mainWindow?.presentInGlobalOverlay(controller)
}
@@ -1285,10 +1285,10 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
}
if let sourceNode {
let controller = ContextController(presentationData: self.presentationData, source: .extracted(ChatListHeaderBarContextExtractedContentSource(controller: self, sourceNode: sourceNode, sourceView: sourceView, keepInPlace: keepInPlace)), items: .single(ContextController.Items(content: .list(items))), recognizer: nil, gesture: gesture)
let controller = makeContextController(presentationData: self.presentationData, source: .extracted(ChatListHeaderBarContextExtractedContentSource(controller: self, sourceNode: sourceNode, sourceView: sourceView, keepInPlace: keepInPlace)), items: .single(ContextController.Items(content: .list(items))), recognizer: nil, gesture: gesture)
self.context.sharedContext.mainWindow?.presentInGlobalOverlay(controller)
} else if let sourceView {
let controller = ContextController(presentationData: self.presentationData, source: .reference(ChatListHeaderBarContextReferenceContentSource(controller: self, sourceView: sourceView)), items: .single(ContextController.Items(content: .list(items))), recognizer: nil, gesture: gesture)
let controller = makeContextController(presentationData: self.presentationData, source: .reference(ChatListHeaderBarContextReferenceContentSource(controller: self, sourceView: sourceView)), items: .single(ContextController.Items(content: .list(items))), recognizer: nil, gesture: gesture)
self.context.sharedContext.mainWindow?.presentInGlobalOverlay(controller)
}
})
@@ -1873,7 +1873,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
case let .groupReference(groupReference):
let chatListController = ChatListControllerImpl(context: strongSelf.context, location: .chatList(groupId: groupReference.groupId), controlsHistoryPreload: false, hideNetworkActivityStatus: true, previewing: true, enableDebugActions: false)
chatListController.navigationPresentation = .master
let contextController = ContextController(presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatListController, sourceNode: node, navigationController: strongSelf.navigationController as? NavigationController)), items: archiveContextMenuItems(context: strongSelf.context, groupId: groupReference.groupId._asGroup(), chatListController: strongSelf) |> map { ContextController.Items(content: .list($0)) }, gesture: gesture)
let contextController = makeContextController(presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatListController, sourceNode: node, navigationController: strongSelf.navigationController as? NavigationController)), items: archiveContextMenuItems(context: strongSelf.context, groupId: groupReference.groupId._asGroup(), chatListController: strongSelf) |> map { ContextController.Items(content: .list($0)) }, gesture: gesture)
strongSelf.presentInGlobalOverlay(contextController)
case let .peer(peerData):
let peer = peerData.peer
@@ -1891,12 +1891,12 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
chatController.canReadHistory.set(false)
source = .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node, navigationController: strongSelf.navigationController as? NavigationController))
let contextController = ContextController(presentationData: strongSelf.presentationData, source: source, items: chatForumTopicMenuItems(context: strongSelf.context, peerId: peer.peerId, threadId: threadId, isPinned: nil, isClosed: nil, chatListController: strongSelf, joined: joined, canSelect: false) |> map { ContextController.Items(content: .list($0)) }, gesture: gesture)
let contextController = makeContextController(presentationData: strongSelf.presentationData, source: source, items: chatForumTopicMenuItems(context: strongSelf.context, peerId: peer.peerId, threadId: threadId, isPinned: nil, isClosed: nil, chatListController: strongSelf, joined: joined, canSelect: false) |> map { ContextController.Items(content: .list($0)) }, gesture: gesture)
strongSelf.presentInGlobalOverlay(contextController)
} else {
let chatListController = ChatListControllerImpl(context: strongSelf.context, location: .forum(peerId: channel.id), controlsHistoryPreload: false, hideNetworkActivityStatus: true, previewing: true, enableDebugActions: false)
chatListController.navigationPresentation = .master
let contextController = ContextController(presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatListController, sourceNode: node, navigationController: strongSelf.navigationController as? NavigationController)), items: chatContextMenuItems(context: strongSelf.context, peerId: peer.peerId, promoInfo: promoInfo, source: .chatList(filter: strongSelf.chatListDisplayNode.mainContainerNode.currentItemNode.chatListFilter), chatListController: strongSelf, joined: joined) |> map { ContextController.Items(content: .list($0)) }, gesture: gesture)
let contextController = makeContextController(presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatListController, sourceNode: node, navigationController: strongSelf.navigationController as? NavigationController)), items: chatContextMenuItems(context: strongSelf.context, peerId: peer.peerId, promoInfo: promoInfo, source: .chatList(filter: strongSelf.chatListDisplayNode.mainContainerNode.currentItemNode.chatListFilter), chatListController: strongSelf, joined: joined) |> map { ContextController.Items(content: .list($0)) }, gesture: gesture)
strongSelf.presentInGlobalOverlay(contextController)
}
} else if let peer = peer.peer, peer.id == strongSelf.context.account.peerId, peerData.displayAsTopicList {
@@ -1908,7 +1908,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
source = .controller(ContextControllerContentSourceImpl(controller: peerInfoController, sourceNode: node, navigationController: strongSelf.navigationController as? NavigationController))
}
let contextController = ContextController(presentationData: strongSelf.presentationData, source: source, items: chatContextMenuItems(context: strongSelf.context, peerId: peer.id, promoInfo: promoInfo, source: .chatList(filter: strongSelf.chatListDisplayNode.mainContainerNode.currentItemNode.chatListFilter), chatListController: strongSelf, joined: joined) |> map { ContextController.Items(content: .list($0)) }, gesture: gesture)
let contextController = makeContextController(presentationData: strongSelf.presentationData, source: source, items: chatContextMenuItems(context: strongSelf.context, peerId: peer.id, promoInfo: promoInfo, source: .chatList(filter: strongSelf.chatListDisplayNode.mainContainerNode.currentItemNode.chatListFilter), chatListController: strongSelf, joined: joined) |> map { ContextController.Items(content: .list($0)) }, gesture: gesture)
strongSelf.presentInGlobalOverlay(contextController)
}
} else {
@@ -1926,7 +1926,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
source = .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node, navigationController: strongSelf.navigationController as? NavigationController))
}
let contextController = ContextController(context: strongSelf.context, presentationData: strongSelf.presentationData, source: source, items: chatContextMenuItems(context: strongSelf.context, peerId: peer.peerId, promoInfo: promoInfo, source: .chatList(filter: strongSelf.chatListDisplayNode.mainContainerNode.currentItemNode.chatListFilter), chatListController: strongSelf, joined: joined) |> map { ContextController.Items(content: .list($0)) }, gesture: gesture)
let contextController = makeContextController(context: strongSelf.context, presentationData: strongSelf.presentationData, source: source, items: chatContextMenuItems(context: strongSelf.context, peerId: peer.peerId, promoInfo: promoInfo, source: .chatList(filter: strongSelf.chatListDisplayNode.mainContainerNode.currentItemNode.chatListFilter), chatListController: strongSelf, joined: joined) |> map { ContextController.Items(content: .list($0)) }, gesture: gesture)
strongSelf.presentInGlobalOverlay(contextController)
dismissPreviewingImpl = { [weak self, weak contextController] animateIn in
@@ -1960,7 +1960,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
chatController.canReadHistory.set(false)
source = .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node, navigationController: strongSelf.navigationController as? NavigationController))
let contextController = ContextController(presentationData: strongSelf.presentationData, source: source, items: chatForumTopicMenuItems(context: strongSelf.context, peerId: peer.peerId, threadId: threadId, isPinned: isPinned, isClosed: threadInfo?.isClosed, chatListController: strongSelf, joined: joined, canSelect: true) |> map { ContextController.Items(content: .list($0)) }, gesture: gesture)
let contextController = makeContextController(presentationData: strongSelf.presentationData, source: source, items: chatForumTopicMenuItems(context: strongSelf.context, peerId: peer.peerId, threadId: threadId, isPinned: isPinned, isClosed: threadInfo?.isClosed, chatListController: strongSelf, joined: joined, canSelect: true) |> map { ContextController.Items(content: .list($0)) }, gesture: gesture)
strongSelf.presentInGlobalOverlay(contextController)
}
}
@@ -1995,7 +1995,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
if case let .channel(channel) = peer, channel.isForumOrMonoForum {
let chatListController = ChatListControllerImpl(context: strongSelf.context, location: .forum(peerId: channel.id), controlsHistoryPreload: false, hideNetworkActivityStatus: true, previewing: true, enableDebugActions: false)
chatListController.navigationPresentation = .master
let contextController = ContextController(presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatListController, sourceNode: node, navigationController: strongSelf.navigationController as? NavigationController)), items: chatContextMenuItems(context: strongSelf.context, peerId: peer.id, promoInfo: nil, source: .search(source), chatListController: strongSelf, joined: false) |> map { ContextController.Items(content: .list($0)) }, gesture: gesture)
let contextController = makeContextController(presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatListController, sourceNode: node, navigationController: strongSelf.navigationController as? NavigationController)), items: chatContextMenuItems(context: strongSelf.context, peerId: peer.id, promoInfo: nil, source: .search(source), chatListController: strongSelf, joined: false) |> map { ContextController.Items(content: .list($0)) }, gesture: gesture)
strongSelf.presentInGlobalOverlay(contextController)
} else {
let contextContentSource: ContextContentSource
@@ -2011,7 +2011,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
contextContentSource = .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node, navigationController: strongSelf.navigationController as? NavigationController))
}
let contextController = ContextController(context: strongSelf.context, presentationData: strongSelf.presentationData, source: contextContentSource, items: chatContextMenuItems(context: strongSelf.context, peerId: peer.id, promoInfo: nil, source: .search(source), chatListController: strongSelf, joined: false) |> map { ContextController.Items(content: .list($0)) }, gesture: gesture)
let contextController = makeContextController(context: strongSelf.context, presentationData: strongSelf.presentationData, source: contextContentSource, items: chatContextMenuItems(context: strongSelf.context, peerId: peer.id, promoInfo: nil, source: .search(source), chatListController: strongSelf, joined: false) |> map { ContextController.Items(content: .list($0)) }, gesture: gesture)
strongSelf.presentInGlobalOverlay(contextController)
}
}
@@ -3425,7 +3425,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
})))
}
let controller = ContextController(presentationData: self.presentationData, source: .extracted(ChatListHeaderBarContextExtractedContentSource(controller: self, sourceNode: sourceNode, sourceView: nil, keepInPlace: false)), items: .single(ContextController.Items(content: .list(items))), recognizer: nil, gesture: gesture)
let controller = makeContextController(presentationData: self.presentationData, source: .extracted(ChatListHeaderBarContextExtractedContentSource(controller: self, sourceNode: sourceNode, sourceView: nil, keepInPlace: false)), items: .single(ContextController.Items(content: .list(items))), recognizer: nil, gesture: gesture)
self.context.sharedContext.mainWindow?.presentInGlobalOverlay(controller)
})
}
@@ -3878,7 +3878,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
}
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let contextController = ContextController(presentationData: presentationData, source: .reference(HeaderContextReferenceContentSource(controller: sourceController, sourceView: sourceView)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
let contextController = makeContextController(presentationData: presentationData, source: .reference(HeaderContextReferenceContentSource(controller: sourceController, sourceView: sourceView)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
sourceController.presentInGlobalOverlay(contextController)
})
}
@@ -3938,7 +3938,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
})))
}
let contextController = ContextController(presentationData: presentationData, source: .reference(HeaderContextReferenceContentSource(controller: self, sourceView: sourceView)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
let contextController = makeContextController(presentationData: presentationData, source: .reference(HeaderContextReferenceContentSource(controller: self, sourceView: sourceView)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
self.presentInGlobalOverlay(contextController)
})
}
@@ -6206,7 +6206,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
}
}
let controller = ContextController(context: strongSelf.context, presentationData: strongSelf.presentationData, source: .reference(ChatListTabBarContextReferenceContentSource(controller: strongSelf, sourceView: sourceView)), items: .single(ContextController.Items(content: .list(items))), recognizer: nil, gesture: gesture)
let controller = makeContextController(context: strongSelf.context, presentationData: strongSelf.presentationData, source: .reference(ChatListTabBarContextReferenceContentSource(controller: strongSelf, sourceView: sourceView)), items: .single(ContextController.Items(content: .list(items))), recognizer: nil, gesture: gesture)
strongSelf.context.sharedContext.mainWindow?.presentInGlobalOverlay(controller)
})
}
@@ -6408,7 +6408,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
})
})))
let contextController = ContextController(presentationData: presentationData, source: .reference(AdsInfoContextReferenceContentSource(controller: controller, sourceView: referenceView, insets: .zero, contentInsets: .zero)), items: .single(ContextController.Items(content: .list(actions))), gesture: nil)
let contextController = makeContextController(presentationData: presentationData, source: .reference(AdsInfoContextReferenceContentSource(controller: controller, sourceView: referenceView, insets: .zero, contentInsets: .zero)), items: .single(ContextController.Items(content: .list(actions))), gesture: nil)
controller.presentInGlobalOverlay(contextController)
}
@@ -1927,7 +1927,7 @@ func chatListFilterPresetController(context: AccountContext, currentPreset initi
})
})))
let contextController = ContextController(presentationData: presentationData, source: .extracted(InviteLinkContextExtractedContentSource(controller: controller, sourceNode: node, keepInPlace: false, blurBackground: true)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
let contextController = makeContextController(presentationData: presentationData, source: .extracted(InviteLinkContextExtractedContentSource(controller: controller, sourceNode: node, keepInPlace: false, blurBackground: true)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
presentInGlobalOverlayImpl?(contextController)
})
},
@@ -1968,7 +1968,7 @@ func chatListFilterPresetController(context: AccountContext, currentPreset initi
})
})))
let contextController = ContextController(presentationData: presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
let contextController = makeContextController(presentationData: presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
presentInGlobalOverlayImpl?(contextController)
},
updateTagColor: { color in
@@ -1174,7 +1174,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
return items
}
let controller = ContextController(presentationData: self.presentationData, source: .extracted(MessageContextExtractedContentSource(sourceNode: node, shouldBeDismissed: shouldBeDismissed)), items: items |> map { ContextController.Items(content: .list($0)) }, recognizer: nil, gesture: gesture)
let controller = makeContextController(presentationData: self.presentationData, source: .extracted(MessageContextExtractedContentSource(sourceNode: node, shouldBeDismissed: shouldBeDismissed)), items: items |> map { ContextController.Items(content: .list($0)) }, recognizer: nil, gesture: gesture)
self.presentInGlobalOverlay?(controller, nil)
return
@@ -1248,7 +1248,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
return items
}
let controller = ContextController(presentationData: self.presentationData, source: .extracted(MessageContextExtractedContentSource(sourceNode: node)), items: items |> map { ContextController.Items(content: .list($0)) }, recognizer: nil, gesture: gesture)
let controller = makeContextController(presentationData: self.presentationData, source: .extracted(MessageContextExtractedContentSource(sourceNode: node)), items: items |> map { ContextController.Items(content: .list($0)) }, recognizer: nil, gesture: gesture)
self.presentInGlobalOverlay?(controller, nil)
}
@@ -1312,7 +1312,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
switch previewData {
case let .gallery(gallery):
gallery.setHintWillBePresentedInPreviewingContext(true)
let contextController = ContextController(presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: gallery, sourceNode: node)), items: items |> map { ContextController.Items(content: .list($0)) }, gesture: gesture)
let contextController = makeContextController(presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: gallery, sourceNode: node)), items: items |> map { ContextController.Items(content: .list($0)) }, gesture: gesture)
strongSelf.presentInGlobalOverlay?(contextController, nil)
case .instantPage:
break
@@ -5622,7 +5622,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
f(.default)
self.searchScopePromise.set(.channels)
})))
let contextController = ContextController(presentationData: self.presentationData, source: .reference(ChatListSearchReferenceContentSource(sourceNode: sourceNode)), items: .single(ContextController.Items(content: .list(items))), gesture: nil)
let contextController = makeContextController(presentationData: self.presentationData, source: .reference(ChatListSearchReferenceContentSource(sourceNode: sourceNode)), items: .single(ContextController.Items(content: .list(items))), gesture: nil)
self.interaction.present(contextController, nil)
})
}
@@ -650,7 +650,7 @@ final class ChatSendMessageContextScreenComponent: Component {
if let current = self.actionsStackNode {
actionsStackNode = current
actionsStackNode.replace(item: ContextControllerActionsListStackItem(
actionsStackNode.replace(item: makeContextControllerActionsListStackItem(
id: AnyHashable("items"),
items: items,
reactionItems: nil,
@@ -660,7 +660,7 @@ final class ChatSendMessageContextScreenComponent: Component {
dismissed: nil
), animated: !transition.animation.isImmediate)
} else {
actionsStackNode = ContextControllerActionsStackNode(
actionsStackNode = makeContextControllerActionsStackNode(
context: component.context,
getController: {
return nil
@@ -681,7 +681,7 @@ final class ChatSendMessageContextScreenComponent: Component {
}
actionsStackNode.push(
item: ContextControllerActionsListStackItem(
item: makeContextControllerActionsListStackItem(
id: AnyHashable("items"),
items: items,
reactionItems: nil,
+2 -1
View File
@@ -10,7 +10,8 @@ swift_library(
"-warnings-as-errors",
],
deps = [
"//submodules/Display"
"//submodules/Display",
"//submodules/UIKitRuntimeUtils",
],
visibility = [
"//visibility:public",
@@ -1,6 +1,7 @@
import Foundation
import UIKit
import Display
import UIKitRuntimeUtils
#if targetEnvironment(simulator)
@_silgen_name("UIAnimationDragCoefficient") func UIAnimationDragCoefficient() -> Float
@@ -84,7 +85,7 @@ public extension ComponentTransition.Animation {
}
public extension ComponentTransition {
func animateView(allowUserInteraction: Bool = false, delay: Double = 0.0, _ f: @escaping () -> Void, completion: ((Bool) -> Void)? = nil) {
func animateView(allowUserInteraction: Bool = true, delay: Double = 0.0, _ f: @escaping () -> Void, completion: ((Bool) -> Void)? = nil) {
switch self.animation {
case .none:
f()
@@ -94,9 +95,17 @@ public extension ComponentTransition {
if allowUserInteraction {
options.insert(.allowUserInteraction)
}
UIView.animate(withDuration: duration, delay: delay, options: options, animations: {
f()
}, completion: completion)
if case .spring = curve {
CALayer.push(CALayerSpringParametersOverride())
UIView.animate(withDuration: duration, delay: delay, usingSpringWithDamping: 500.0, initialSpringVelocity: 0.0, options: options, animations: {
f()
}, completion: completion)
CALayer.popSpringParametersOverride()
} else {
UIView.animate(withDuration: duration, delay: delay, options: options, animations: {
f()
}, completion: completion)
}
}
}
}
@@ -636,7 +636,13 @@ public class ContactsController: ViewController {
})))
return items
}
let contextController = ContextController(presentationData: self.presentationData, source: .reference(HeaderContextReferenceContentSource(controller: self, sourceView: sourceView)), items: items |> map { ContextController.Items(content: .list($0)) }, gesture: gesture)
var sourceView = sourceView
if let navigationBarComponentView = self.contactsNode.navigationBarView.view as? ChatListNavigationBar.View, let headerContentView = navigationBarComponentView.headerContent.view as? ChatListHeaderComponent.View, let value = headerContentView.navigationButtonContextContainer(sourceView: sourceView) {
sourceView = value
}
let contextController = makeContextController(presentationData: self.presentationData, source: .reference(HeaderContextReferenceContentSource(controller: self, sourceView: sourceView)), items: items |> map { ContextController.Items(content: .list($0)) }, gesture: gesture)
self.presentInGlobalOverlay(contextController)
}
@@ -800,7 +806,7 @@ public class ContactsController: ViewController {
})
})))
let controller = ContextController(presentationData: self.presentationData, source: .reference(ContactsTabBarContextReferenceContentSource(controller: self, sourceView: sourceView)), items: .single(ContextController.Items(content: .list(items))), recognizer: nil, gesture: gesture)
let controller = makeContextController(presentationData: self.presentationData, source: .reference(ContactsTabBarContextReferenceContentSource(controller: self, sourceView: sourceView)), items: .single(ContextController.Items(content: .list(items))), recognizer: nil, gesture: gesture)
self.context.sharedContext.mainWindow?.presentInGlobalOverlay(controller)
}
@@ -467,12 +467,12 @@ final class ContactsControllerNode: ASDisplayNode, ASGestureRecognizerDelegate {
let items = contactContextMenuItems(context: self.context, peerId: peer.id, contactsController: contactsController, isStories: isStories) |> map { ContextController.Items(content: .list($0)) }
if isStories, let node = node?.subnodes?.first(where: { $0 is ContextExtractedContentContainingNode }) as? ContextExtractedContentContainingNode {
let controller = ContextController(presentationData: self.presentationData, source: .extracted(ContactContextExtractedContentSource(sourceNode: node, shouldBeDismissed: .single(false))), items: items, recognizer: nil, gesture: gesture)
let controller = makeContextController(presentationData: self.presentationData, source: .extracted(ContactContextExtractedContentSource(sourceNode: node, shouldBeDismissed: .single(false))), items: items, recognizer: nil, gesture: gesture)
contactsController.presentInGlobalOverlay(controller)
} else {
let chatController = self.context.sharedContext.makeChatController(context: self.context, chatLocation: .peer(id: peer.id), subject: nil, botStart: nil, mode: .standard(.previewing), params: nil)
chatController.canReadHistory.set(false)
let contextController = ContextController(presentationData: self.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node)), items: items, gesture: gesture)
let contextController = makeContextController(presentationData: self.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node)), items: items, gesture: gesture)
contactsController.presentInGlobalOverlay(contextController)
}
}
File diff suppressed because it is too large Load Diff
+61 -111
View File
@@ -5,6 +5,39 @@ import Display
import SwiftSignalKit
import TelegramPresentationData
public enum PeekControllerContentPresentation {
case contained
case freeform
}
public enum PeerControllerMenuActivation {
case drag
case press
}
public protocol PeekControllerContent {
func presentation() -> PeekControllerContentPresentation
func menuActivation() -> PeerControllerMenuActivation
func menuItems() -> [ContextMenuItem]
func node() -> PeekControllerContentNode & ASDisplayNode
func topAccessoryNode() -> ASDisplayNode?
func fullScreenAccessoryNode(blurView: UIVisualEffectView) -> (PeekControllerAccessoryNode & ASDisplayNode)?
func isEqual(to: PeekControllerContent) -> Bool
}
public protocol PeekControllerContentNode {
func ready() -> Signal<Bool, NoError>
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize
}
public protocol PeekControllerAccessoryNode {
var dismiss: () -> Void { get set }
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition)
}
public final class PeekControllerTheme {
public let isDark: Bool
public let menuBackgroundColor: UIColor
@@ -30,115 +63,32 @@ extension PeekControllerTheme {
}
}
public final class PeekController: ViewController, ContextControllerProtocol {
public var useComplexItemsTransitionAnimation: Bool = false
public var immediateItemsTransitionAnimation = false
public func getActionsMinHeight() -> ContextController.ActionsHeight? {
return nil
}
public func setItems(_ items: Signal<ContextController.Items, NoError>, minHeight: ContextController.ActionsHeight?, animated: Bool) {
}
public func setItems(_ items: Signal<ContextController.Items, NoError>, minHeight: ContextController.ActionsHeight?, previousActionsTransition: ContextController.PreviousActionsTransition) {
}
public func pushItems(items: Signal<ContextController.Items, NoError>) {
self.controllerNode.pushItems(items: items)
}
public func popItems() {
self.controllerNode.popItems()
}
private var controllerNode: PeekControllerNode {
return self.displayNode as! PeekControllerNode
}
public var contentNode: PeekControllerContentNode & ASDisplayNode {
return self.controllerNode.contentNode
}
private let presentationData: PresentationData
private let content: PeekControllerContent
var sourceView: () -> (UIView, CGRect)?
private let activateImmediately: Bool
public var visibilityUpdated: ((Bool) -> Void)?
public var getOverlayViews: (() -> [UIView])?
public var appeared: (() -> Void)?
public var disappeared: (() -> Void)?
private var animatedIn = false
private let _ready = Promise<Bool>()
override public var ready: Promise<Bool> {
return self._ready
}
public init(presentationData: PresentationData, content: PeekControllerContent, sourceView: @escaping () -> (UIView, CGRect)?, activateImmediately: Bool = false) {
self.presentationData = presentationData
self.content = content
self.sourceView = sourceView
self.activateImmediately = activateImmediately
super.init(navigationBarPresentationData: nil)
self.statusBar.statusBarStyle = .Ignore
}
required public init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override public func loadDisplayNode() {
self.displayNode = PeekControllerNode(presentationData: self.presentationData, controller: self, content: self.content, requestDismiss: { [weak self] in
self?.dismiss()
})
self.displayNodeDidLoad()
}
private func getSourceRect() -> CGRect {
if let (sourceView, sourceRect) = self.sourceView() {
return sourceView.convert(sourceRect, to: self.view)
} else {
let size = self.displayNode.bounds.size
return CGRect(origin: CGPoint(x: floor((size.width - 10.0) / 2.0), y: floor((size.height - 10.0) / 2.0)), size: CGSize(width: 10.0, height: 10.0))
}
}
override public func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if !self.animatedIn {
self.animatedIn = true
self.controllerNode.animateIn(from: self.getSourceRect())
self.visibilityUpdated?(true)
if self.activateImmediately {
self.controllerNode.activateMenu(immediately: true)
}
}
}
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
super.containerLayoutUpdated(layout, transition: transition)
self.controllerNode.containerLayoutUpdated(layout, transition: transition)
}
override public func dismiss(completion: (() -> Void)? = nil) {
self.visibilityUpdated?(false)
self.controllerNode.animateOut(to: self.getSourceRect(), completion: { [weak self] in
self?.presentingViewController?.dismiss(animated: false, completion: nil)
})
}
public func dismiss(result: ContextMenuActionResult, completion: (() -> Void)?) {
self.dismiss(completion: completion)
}
public protocol PeekController: ViewController, ContextControllerProtocol {
var visibilityUpdated: ((Bool) -> Void)? { get set }
var getOverlayViews: (() -> [UIView])? { get set }
var appeared: (() -> Void)? { get set }
var disappeared: (() -> Void)? { get set }
var sourceView: () -> (UIView, CGRect)? { get set }
var contentNode: PeekControllerContentNode & ASDisplayNode { get }
}
public var makePeekControllerImpl: ((
_ presentationData: PresentationData,
_ content: PeekControllerContent,
_ sourceView: @escaping () -> (UIView, CGRect)?,
_ activateImmediately: Bool
) -> PeekController)?
public func makePeekController(
presentationData: PresentationData,
content: PeekControllerContent,
sourceView: @escaping () -> (UIView, CGRect)?,
activateImmediately: Bool = false
) -> PeekController {
return makePeekControllerImpl!(
presentationData,
content,
sourceView,
activateImmediately
)
}
@@ -1,38 +0,0 @@
import Foundation
import UIKit
import AsyncDisplayKit
import Display
import SwiftSignalKit
public enum PeekControllerContentPresentation {
case contained
case freeform
}
public enum PeerControllerMenuActivation {
case drag
case press
}
public protocol PeekControllerContent {
func presentation() -> PeekControllerContentPresentation
func menuActivation() -> PeerControllerMenuActivation
func menuItems() -> [ContextMenuItem]
func node() -> PeekControllerContentNode & ASDisplayNode
func topAccessoryNode() -> ASDisplayNode?
func fullScreenAccessoryNode(blurView: UIVisualEffectView) -> (PeekControllerAccessoryNode & ASDisplayNode)?
func isEqual(to: PeekControllerContent) -> Bool
}
public protocol PeekControllerContentNode {
func ready() -> Signal<Bool, NoError>
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize
}
public protocol PeekControllerAccessoryNode {
var dismiss: () -> Void { get set }
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition)
}
@@ -4,6 +4,13 @@ import SwiftSignalKit
import AsyncDisplayKit
import Display
public protocol PeekControllerNodeProtocol: AnyObject {
func applyDraggingOffset(_ offset: CGPoint)
func activateMenu(immediately: Bool)
func endDragging(_ location: CGPoint)
func updateContent(content: PeekControllerContent)
}
private func traceDeceleratingScrollView(_ view: UIView, at point: CGPoint) -> Bool {
if view.bounds.contains(point), let view = view as? UIScrollView, view.isDecelerating {
return true
@@ -108,7 +115,7 @@ public final class PeekControllerGestureRecognizer: UIPanGestureRecognizer {
if let _ = self.tapLocation, let menuActivation = self.menuActivation, case .press = menuActivation {
if let presentedController = self.presentedController {
if presentedController.isNodeLoaded {
(presentedController.displayNode as? PeekControllerNode)?.activateMenu()
(presentedController.displayNode as? PeekControllerNodeProtocol)?.activateMenu(immediately: false)
}
self.menuActivation = nil
// self.presentedController = nil
@@ -148,7 +155,7 @@ public final class PeekControllerGestureRecognizer: UIPanGestureRecognizer {
self.state = .ended
} else {
if let presentedController = self.presentedController, presentedController.isNodeLoaded, let location = touches.first?.location(in: presentedController.view) {
(presentedController.displayNode as? PeekControllerNode)?.endDragging(location)
(presentedController.displayNode as? PeekControllerNodeProtocol)?.endDragging(location)
self.presentedController = nil
self.menuActivation = nil
}
@@ -184,7 +191,7 @@ public final class PeekControllerGestureRecognizer: UIPanGestureRecognizer {
if let presentedController = self.presentedController, self.menuActivation == nil {
if presentedController.isNodeLoaded {
let touchLocation = touch.location(in: presentedController.view)
(presentedController.displayNode as? PeekControllerNode)?.applyDraggingOffset(touchLocation)
(presentedController.displayNode as? PeekControllerNodeProtocol)?.applyDraggingOffset(touchLocation)
}
} else if let menuActivation = self.menuActivation, let presentedController = self.presentedController {
switch menuActivation {
@@ -201,7 +208,7 @@ public final class PeekControllerGestureRecognizer: UIPanGestureRecognizer {
if #available(iOSApplicationExtension 9.0, iOS 9.0, *) {
if touch.force >= 2.5 {
if presentedController.isNodeLoaded {
(presentedController.displayNode as? PeekControllerNode)?.activateMenu()
(presentedController.displayNode as? PeekControllerNodeProtocol)?.activateMenu(immediately: false)
self.menuActivation = nil
self.presentedController = nil
self.candidateContent = nil
@@ -268,7 +275,7 @@ public final class PeekControllerGestureRecognizer: UIPanGestureRecognizer {
presentedController.sourceView = {
return (sourceView, sourceRect)
}
(presentedController.displayNode as? PeekControllerNode)?.updateContent(content: content)
(presentedController.displayNode as? PeekControllerNodeProtocol)?.updateContent(content: content)
}
}
} else {
@@ -277,7 +284,7 @@ public final class PeekControllerGestureRecognizer: UIPanGestureRecognizer {
if forceActivate {
strongSelf.candidateContent = nil
if case .press = content.menuActivation() {
(presentedController.displayNode as? PeekControllerNode)?.activateMenu()
(presentedController.displayNode as? PeekControllerNodeProtocol)?.activateMenu(immediately: false)
}
} else {
strongSelf.candidateContent = (sourceView, sourceRect, content)
@@ -3,480 +3,25 @@ import UIKit
import AsyncDisplayKit
import Display
import TelegramPresentationData
import TextSelectionNode
import TelegramCore
import SwiftSignalKit
import UIKitRuntimeUtils
final class PinchSourceGesture: UIPinchGestureRecognizer {
private final class Target {
var updated: (() -> Void)?
@objc func onGesture(_ gesture: UIPinchGestureRecognizer) {
self.updated?()
}
}
private let target: Target
private(set) var currentTransform: (CGFloat, CGPoint, CGPoint)?
var began: (() -> Void)?
var updated: ((CGFloat, CGPoint, CGPoint) -> Void)?
var ended: (() -> Void)?
private var initialLocation: CGPoint?
private var pinchLocation = CGPoint()
private var currentOffset = CGPoint()
private var currentNumberOfTouches = 0
init() {
self.target = Target()
super.init(target: self.target, action: #selector(self.target.onGesture(_:)))
self.target.updated = { [weak self] in
self?.gestureUpdated()
}
}
override func reset() {
super.reset()
self.currentNumberOfTouches = 0
self.initialLocation = nil
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesBegan(touches, with: event)
//self.currentTouches.formUnion(touches)
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesEnded(touches, with: event)
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesCancelled(touches, with: event)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesMoved(touches, with: event)
}
private func gestureUpdated() {
switch self.state {
case .began:
self.currentOffset = CGPoint()
let pinchLocation = self.location(in: self.view)
self.pinchLocation = pinchLocation
self.initialLocation = pinchLocation
let scale = max(1.0, self.scale)
self.currentTransform = (scale, self.pinchLocation, self.currentOffset)
self.currentNumberOfTouches = self.numberOfTouches
self.began?()
case .changed:
let locationSum = self.location(in: self.view)
if self.numberOfTouches < 2 && self.currentNumberOfTouches >= 2 {
self.initialLocation = CGPoint(x: locationSum.x - self.currentOffset.x, y: locationSum.y - self.currentOffset.y)
}
self.currentNumberOfTouches = self.numberOfTouches
if let initialLocation = self.initialLocation {
self.currentOffset = CGPoint(x: locationSum.x - initialLocation.x, y: locationSum.y - initialLocation.y)
}
if let (scale, pinchLocation, _) = self.currentTransform {
self.currentTransform = (scale, pinchLocation, self.currentOffset)
self.updated?(scale, pinchLocation, self.currentOffset)
}
let scale = max(1.0, self.scale)
self.currentTransform = (scale, self.pinchLocation, self.currentOffset)
self.updated?(scale, self.pinchLocation, self.currentOffset)
case .ended, .cancelled:
self.ended?()
default:
break
}
}
public protocol PinchController: ViewController {
func addRelativeContentOffset(_ offset: CGPoint, transition: ContainedViewLayoutTransition)
}
private func cancelContextGestures(node: ASDisplayNode) {
if let node = node as? ContextControllerSourceNode {
node.cancelGesture()
}
public var makePinchControllerImpl: ((
_ sourceNode: PinchSourceContainerNode,
_ disableScreenshots: Bool,
_ getContentAreaInScreenSpace: @escaping () -> CGRect
) -> PinchController)?
if let supernode = node.supernode {
cancelContextGestures(node: supernode)
}
}
private func cancelContextGestures(view: UIView) {
if let gestureRecognizers = view.gestureRecognizers {
for recognizer in gestureRecognizers {
if let recognizer = recognizer as? InteractiveTransitionGestureRecognizer {
recognizer.cancel()
} else if let recognizer = recognizer as? WindowPanRecognizer {
recognizer.cancel()
}
}
}
if let superview = view.superview {
cancelContextGestures(view: superview)
}
}
public final class PinchSourceContainerNode: ASDisplayNode, ASGestureRecognizerDelegate {
public let contentNode: ASDisplayNode
public var contentRect: CGRect = CGRect()
private(set) var naturalContentFrame: CGRect?
fileprivate let gesture: PinchSourceGesture
public var isPinchGestureEnabled: Bool = true {
didSet {
if self.isPinchGestureEnabled != oldValue {
self.gesture.isEnabled = self.isPinchGestureEnabled
}
}
}
public var maxPinchScale: CGFloat = 10.0
private var isActive: Bool = false
public var activate: ((PinchSourceContainerNode) -> Void)?
public var scaleUpdated: ((CGFloat, ContainedViewLayoutTransition) -> Void)?
public var animatedOut: (() -> Void)?
var deactivate: (() -> Void)?
public var deactivated: (() -> Void)?
var updated: ((CGFloat, CGPoint, CGPoint) -> Void)?
override public init() {
self.gesture = PinchSourceGesture()
self.contentNode = ASDisplayNode()
super.init()
self.addSubnode(self.contentNode)
self.gesture.began = { [weak self] in
guard let strongSelf = self else {
return
}
cancelContextGestures(node: strongSelf)
cancelContextGestures(view: strongSelf.view)
strongSelf.isActive = true
strongSelf.activate?(strongSelf)
}
self.gesture.ended = { [weak self] in
guard let strongSelf = self else {
return
}
strongSelf.isActive = false
strongSelf.deactivate?()
strongSelf.deactivated?()
}
self.gesture.updated = { [weak self] scale, pinchLocation, offset in
guard let strongSelf = self else {
return
}
strongSelf.updated?(min(scale, strongSelf.maxPinchScale), pinchLocation, offset)
strongSelf.scaleUpdated?(min(scale, strongSelf.maxPinchScale), .immediate)
}
}
override public func didLoad() {
super.didLoad()
self.view.addGestureRecognizer(self.gesture)
self.view.disablesInteractiveTransitionGestureRecognizerNow = { [weak self] in
guard let strongSelf = self else {
return false
}
return strongSelf.isActive
}
}
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return false
}
public func update(size: CGSize, transition: ContainedViewLayoutTransition) {
let contentFrame = CGRect(origin: CGPoint(), size: size)
self.naturalContentFrame = contentFrame
if !self.isActive {
transition.updateFrame(node: self.contentNode, frame: contentFrame)
}
}
func restoreToNaturalSize() {
guard let naturalContentFrame = self.naturalContentFrame else {
return
}
self.contentNode.frame = naturalContentFrame
}
}
private final class PinchControllerNode: ViewControllerTracingNode {
private weak var controller: PinchController?
private var initialSourceFrame: CGRect?
private let clippingNode: ASDisplayNode
private let scrollingContainer: ASDisplayNode
private let sourceNode: PinchSourceContainerNode
private let disableScreenshots: Bool
private let getContentAreaInScreenSpace: () -> CGRect
private let dimNode: ASDisplayNode
private var validLayout: ContainerViewLayout?
private var isAnimatingOut: Bool = false
private var hapticFeedback: HapticFeedback?
init(controller: PinchController, sourceNode: PinchSourceContainerNode, disableScreenshots: Bool, getContentAreaInScreenSpace: @escaping () -> CGRect) {
self.controller = controller
self.sourceNode = sourceNode
self.disableScreenshots = disableScreenshots
self.getContentAreaInScreenSpace = getContentAreaInScreenSpace
self.dimNode = ASDisplayNode()
self.dimNode.backgroundColor = UIColor(white: 0.0, alpha: 0.5)
self.dimNode.alpha = 0.0
self.clippingNode = ASDisplayNode()
self.clippingNode.clipsToBounds = true
self.scrollingContainer = ASDisplayNode()
super.init()
self.addSubnode(self.dimNode)
self.addSubnode(self.clippingNode)
self.clippingNode.addSubnode(self.scrollingContainer)
self.sourceNode.deactivate = { [weak self] in
guard let strongSelf = self else {
return
}
strongSelf.controller?.dismiss()
}
self.sourceNode.updated = { [weak self] scale, pinchLocation, offset in
guard let strongSelf = self, let initialSourceFrame = strongSelf.initialSourceFrame else {
return
}
strongSelf.dimNode.alpha = max(0.0, min(1.0, scale - 1.0))
let pinchOffset = CGPoint(
x: pinchLocation.x - initialSourceFrame.width / 2.0,
y: pinchLocation.y - initialSourceFrame.height / 2.0
)
var transform = CATransform3DIdentity
transform = CATransform3DTranslate(transform, offset.x - pinchOffset.x * (scale - 1.0), offset.y - pinchOffset.y * (scale - 1.0), 0.0)
transform = CATransform3DScale(transform, scale, scale, 0.0)
strongSelf.sourceNode.contentNode.transform = transform
}
if self.disableScreenshots {
setLayerDisableScreenshots(self.layer, true)
}
}
deinit {
}
override func didLoad() {
super.didLoad()
}
func updateLayout(layout: ContainerViewLayout, transition: ContainedViewLayoutTransition, previousActionsContainerNode: ContextActionsContainerNode?) {
if self.isAnimatingOut {
return
}
self.validLayout = layout
transition.updateFrame(node: self.dimNode, frame: CGRect(origin: CGPoint(), size: layout.size))
transition.updateFrame(node: self.clippingNode, frame: CGRect(origin: CGPoint(), size: layout.size))
}
func animateIn() {
let convertedFrame = convertFrame(self.sourceNode.bounds, from: self.sourceNode.view, to: self.view)
self.sourceNode.contentNode.frame = convertedFrame
self.initialSourceFrame = convertedFrame
self.scrollingContainer.addSubnode(self.sourceNode.contentNode)
var updatedContentAreaInScreenSpace = self.getContentAreaInScreenSpace()
updatedContentAreaInScreenSpace.origin.x = 0.0
updatedContentAreaInScreenSpace.size.width = self.bounds.width
self.clippingNode.layer.animateFrame(from: updatedContentAreaInScreenSpace, to: self.clippingNode.frame, duration: 0.18 * 1.0, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue)
self.clippingNode.layer.animateBoundsOriginYAdditive(from: updatedContentAreaInScreenSpace.minY, to: 0.0, duration: 0.18 * 1.0, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue)
}
func animateOut(completion: @escaping () -> Void) {
self.isAnimatingOut = true
let performCompletion: () -> Void = { [weak self] in
guard let strongSelf = self else {
return
}
strongSelf.isAnimatingOut = false
strongSelf.sourceNode.restoreToNaturalSize()
strongSelf.sourceNode.addSubnode(strongSelf.sourceNode.contentNode)
strongSelf.sourceNode.animatedOut?()
completion()
}
let convertedFrame = convertFrame(self.sourceNode.bounds, from: self.sourceNode.view, to: self.view)
self.sourceNode.contentNode.frame = convertedFrame
self.initialSourceFrame = convertedFrame
if let (scale, pinchLocation, offset) = self.sourceNode.gesture.currentTransform, let initialSourceFrame = self.initialSourceFrame {
let duration = 0.3
let transitionCurve: ContainedViewLayoutTransitionCurve = .easeInOut
var updatedContentAreaInScreenSpace = self.getContentAreaInScreenSpace()
updatedContentAreaInScreenSpace.origin.x = 0.0
updatedContentAreaInScreenSpace.size.width = self.bounds.width
self.clippingNode.layer.animateFrame(from: self.clippingNode.frame, to: updatedContentAreaInScreenSpace, duration: duration * 1.0, timingFunction: transitionCurve.timingFunction, removeOnCompletion: false)
self.clippingNode.layer.animateBoundsOriginYAdditive(from: 0.0, to: updatedContentAreaInScreenSpace.minY, duration: duration * 1.0, timingFunction: transitionCurve.timingFunction, removeOnCompletion: false)
let transition: ContainedViewLayoutTransition = .animated(duration: duration, curve: .spring)
if self.hapticFeedback == nil {
self.hapticFeedback = HapticFeedback()
}
self.hapticFeedback?.prepareImpact(.light)
self.hapticFeedback?.impact(.light)
self.sourceNode.scaleUpdated?(1.0, transition)
let pinchOffset = CGPoint(
x: pinchLocation.x - initialSourceFrame.width / 2.0,
y: pinchLocation.y - initialSourceFrame.height / 2.0
)
var transform = CATransform3DIdentity
transform = CATransform3DScale(transform, scale, scale, 0.0)
self.sourceNode.contentNode.transform = CATransform3DIdentity
self.sourceNode.contentNode.position = CGPoint(x: initialSourceFrame.midX, y: initialSourceFrame.midY)
self.sourceNode.contentNode.layer.animateSpring(from: scale as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: duration * 1.2, damping: 110.0)
self.sourceNode.contentNode.layer.animatePosition(from: CGPoint(x: offset.x - pinchOffset.x * (scale - 1.0), y: offset.y - pinchOffset.y * (scale - 1.0)), to: CGPoint(), duration: duration, timingFunction: kCAMediaTimingFunctionSpring, additive: true, force: true, completion: { _ in
performCompletion()
})
let dimNodeTransition: ContainedViewLayoutTransition = .animated(duration: 0.3, curve: transitionCurve)
dimNodeTransition.updateAlpha(node: self.dimNode, alpha: 0.0)
} else {
performCompletion()
}
}
func addRelativeContentOffset(_ offset: CGPoint, transition: ContainedViewLayoutTransition) {
if self.isAnimatingOut {
self.scrollingContainer.bounds = self.scrollingContainer.bounds.offsetBy(dx: 0.0, dy: offset.y)
transition.animateOffsetAdditive(node: self.scrollingContainer, offset: -offset.y)
}
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
return nil
}
}
public final class PinchController: ViewController, StandalonePresentableController {
private let _ready = Promise<Bool>()
override public var ready: Promise<Bool> {
return self._ready
}
private let sourceNode: PinchSourceContainerNode
private let disableScreenshots: Bool
private let getContentAreaInScreenSpace: () -> CGRect
private var wasDismissed = false
private var controllerNode: PinchControllerNode {
return self.displayNode as! PinchControllerNode
}
public init(sourceNode: PinchSourceContainerNode, disableScreenshots: Bool = false, getContentAreaInScreenSpace: @escaping () -> CGRect) {
self.sourceNode = sourceNode
self.disableScreenshots = disableScreenshots
self.getContentAreaInScreenSpace = getContentAreaInScreenSpace
super.init(navigationBarPresentationData: nil)
self.statusBar.statusBarStyle = .Ignore
self.lockOrientation = true
self.blocksBackgroundWhenInOverlay = true
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
}
override public func loadDisplayNode() {
self.displayNode = PinchControllerNode(controller: self, sourceNode: self.sourceNode, disableScreenshots: self.disableScreenshots, getContentAreaInScreenSpace: self.getContentAreaInScreenSpace)
self.displayNodeDidLoad()
self._ready.set(.single(true))
}
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
super.containerLayoutUpdated(layout, transition: transition)
self.controllerNode.updateLayout(layout: layout, transition: transition, previousActionsContainerNode: nil)
}
override public func viewDidAppear(_ animated: Bool) {
if self.ignoreAppearanceMethodInvocations() {
return
}
super.viewDidAppear(animated)
self.controllerNode.animateIn()
}
override public func dismiss(completion: (() -> Void)? = nil) {
if !self.wasDismissed {
self.wasDismissed = true
self.controllerNode.animateOut(completion: { [weak self] in
self?.presentingViewController?.dismiss(animated: false, completion: nil)
completion?()
})
}
}
public func addRelativeContentOffset(_ offset: CGPoint, transition: ContainedViewLayoutTransition) {
self.controllerNode.addRelativeContentOffset(offset, transition: transition)
}
public func makePinchController(
sourceNode: PinchSourceContainerNode,
disableScreenshots: Bool = false,
getContentAreaInScreenSpace: @escaping () -> CGRect
) -> PinchController {
return makePinchControllerImpl!(
sourceNode,
disableScreenshots,
getContentAreaInScreenSpace
)
}
@@ -0,0 +1,226 @@
import Foundation
import UIKit
import AsyncDisplayKit
import Display
private func cancelContextGestures(node: ASDisplayNode) {
if let node = node as? ContextControllerSourceNode {
node.cancelGesture()
}
if let supernode = node.supernode {
cancelContextGestures(node: supernode)
}
}
private func cancelContextGestures(view: UIView) {
if let gestureRecognizers = view.gestureRecognizers {
for recognizer in gestureRecognizers {
if let recognizer = recognizer as? InteractiveTransitionGestureRecognizer {
recognizer.cancel()
} else if let recognizer = recognizer as? WindowPanRecognizer {
recognizer.cancel()
}
}
}
if let superview = view.superview {
cancelContextGestures(view: superview)
}
}
public final class PinchSourceGesture: UIPinchGestureRecognizer {
private final class Target {
var updated: (() -> Void)?
@objc func onGesture(_ gesture: UIPinchGestureRecognizer) {
self.updated?()
}
}
private let target: Target
public private(set) var currentTransform: (CGFloat, CGPoint, CGPoint)?
public var began: (() -> Void)?
public var updated: ((CGFloat, CGPoint, CGPoint) -> Void)?
public var ended: (() -> Void)?
private var initialLocation: CGPoint?
private var pinchLocation = CGPoint()
private var currentOffset = CGPoint()
private var currentNumberOfTouches = 0
public init() {
self.target = Target()
super.init(target: self.target, action: #selector(self.target.onGesture(_:)))
self.target.updated = { [weak self] in
self?.gestureUpdated()
}
}
override public func reset() {
super.reset()
self.currentNumberOfTouches = 0
self.initialLocation = nil
}
override public func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesBegan(touches, with: event)
//self.currentTouches.formUnion(touches)
}
override public func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesEnded(touches, with: event)
}
override public func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesCancelled(touches, with: event)
}
override public func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesMoved(touches, with: event)
}
private func gestureUpdated() {
switch self.state {
case .began:
self.currentOffset = CGPoint()
let pinchLocation = self.location(in: self.view)
self.pinchLocation = pinchLocation
self.initialLocation = pinchLocation
let scale = max(1.0, self.scale)
self.currentTransform = (scale, self.pinchLocation, self.currentOffset)
self.currentNumberOfTouches = self.numberOfTouches
self.began?()
case .changed:
let locationSum = self.location(in: self.view)
if self.numberOfTouches < 2 && self.currentNumberOfTouches >= 2 {
self.initialLocation = CGPoint(x: locationSum.x - self.currentOffset.x, y: locationSum.y - self.currentOffset.y)
}
self.currentNumberOfTouches = self.numberOfTouches
if let initialLocation = self.initialLocation {
self.currentOffset = CGPoint(x: locationSum.x - initialLocation.x, y: locationSum.y - initialLocation.y)
}
if let (scale, pinchLocation, _) = self.currentTransform {
self.currentTransform = (scale, pinchLocation, self.currentOffset)
self.updated?(scale, pinchLocation, self.currentOffset)
}
let scale = max(1.0, self.scale)
self.currentTransform = (scale, self.pinchLocation, self.currentOffset)
self.updated?(scale, self.pinchLocation, self.currentOffset)
case .ended, .cancelled:
self.ended?()
default:
break
}
}
}
public final class PinchSourceContainerNode: ASDisplayNode, ASGestureRecognizerDelegate {
public let contentNode: ASDisplayNode
public var contentRect: CGRect = CGRect()
private(set) var naturalContentFrame: CGRect?
public let gesture: PinchSourceGesture
public var isPinchGestureEnabled: Bool = true {
didSet {
if self.isPinchGestureEnabled != oldValue {
self.gesture.isEnabled = self.isPinchGestureEnabled
}
}
}
public var maxPinchScale: CGFloat = 10.0
private var isActive: Bool = false
public var activate: ((PinchSourceContainerNode) -> Void)?
public var scaleUpdated: ((CGFloat, ContainedViewLayoutTransition) -> Void)?
public var animatedOut: (() -> Void)?
public var deactivate: (() -> Void)?
public var deactivated: (() -> Void)?
public var updated: ((CGFloat, CGPoint, CGPoint) -> Void)?
override public init() {
self.gesture = PinchSourceGesture()
self.contentNode = ASDisplayNode()
super.init()
self.addSubnode(self.contentNode)
self.gesture.began = { [weak self] in
guard let strongSelf = self else {
return
}
cancelContextGestures(node: strongSelf)
cancelContextGestures(view: strongSelf.view)
strongSelf.isActive = true
strongSelf.activate?(strongSelf)
}
self.gesture.ended = { [weak self] in
guard let strongSelf = self else {
return
}
strongSelf.isActive = false
strongSelf.deactivate?()
strongSelf.deactivated?()
}
self.gesture.updated = { [weak self] scale, pinchLocation, offset in
guard let strongSelf = self else {
return
}
strongSelf.updated?(min(scale, strongSelf.maxPinchScale), pinchLocation, offset)
strongSelf.scaleUpdated?(min(scale, strongSelf.maxPinchScale), .immediate)
}
}
override public func didLoad() {
super.didLoad()
self.view.addGestureRecognizer(self.gesture)
self.view.disablesInteractiveTransitionGestureRecognizerNow = { [weak self] in
guard let strongSelf = self else {
return false
}
return strongSelf.isActive
}
}
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return false
}
public func update(size: CGSize, transition: ContainedViewLayoutTransition) {
let contentFrame = CGRect(origin: CGPoint(), size: size)
self.naturalContentFrame = contentFrame
if !self.isActive {
transition.updateFrame(node: self.contentNode, frame: contentFrame)
}
}
public func restoreToNaturalSize() {
guard let naturalContentFrame = self.naturalContentFrame else {
return
}
self.contentNode.frame = naturalContentFrame
}
}
@@ -16,7 +16,7 @@ public final class ContextContentContainerNode: ASDisplayNode {
switch contentNode {
case .reference:
break
case .extracted:
case .extracted, .extractedContainer:
break
case let .controller(controller):
transition.updatePosition(node: controller, position: CGPoint(x: scaledSize.width / 2.0, y: scaledSize.height / 2.0))
@@ -39,6 +39,24 @@ public final class ContextExtractedContentContainingNode: ASDisplayNode {
}
}
public enum ContextExtractableContainerState {
public enum ExtractionState {
case animatedOut
case animatedIn
}
case normal
case extracted(size: CGSize, cornerRadius: CGFloat, state: ExtractionState)
}
public protocol ContextExtractableContainer: UIView {
typealias State = ContextExtractableContainerState
var extractableContentView: UIView { get }
func updateState(state: State, transition: ContainedViewLayoutTransition)
}
public final class ContextExtractedContentContainingView: UIView {
public let contentView: ContextExtractedContentView
public var contentRect: CGRect = CGRect()
@@ -152,5 +170,6 @@ public final class ContextControllerContentNode: ASDisplayNode {
public enum ContextContentNode {
case reference(view: UIView)
case extracted(node: ContextExtractedContentContainingNode, keepInPlace: Bool)
case extractedContainer(container: ContextExtractableContainer)
case controller(ContextControllerContentNode)
}
@@ -192,6 +192,8 @@ public protocol NavigationBar: ASDisplayNode {
func updateLayout(size: CGSize, defaultHeight: CGFloat, additionalTopHeight: CGFloat, additionalContentHeight: CGFloat, additionalBackgroundHeight: CGFloat, additionalCutout: CGSize?, leftInset: CGFloat, rightInset: CGFloat, appearsHidden: Bool, isLandscape: Bool, transition: ContainedViewLayoutTransition)
func updateEdgeEffectExtension(value: CGFloat, transition: ContainedViewLayoutTransition)
func navigationButtonContextContainer(sourceView: UIView) -> ContextExtractableContainer?
}
public var defaultNavigationBarImpl: ((NavigationBarPresentationData) -> NavigationBar)?
@@ -989,7 +989,7 @@ private final class DrawingScreenComponent: CombinedComponent {
)
]
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }.withUpdated(theme: defaultDarkPresentationTheme)
let contextController = ContextController(presentationData: presentationData, source: .reference(ReferenceContentSource(sourceView: sourceView, contentArea: UIScreen.main.bounds, customPosition: CGPoint(x: 7.0, y: 3.0))), items: .single(ContextController.Items(content: .list(items))))
let contextController = makeContextController(presentationData: presentationData, source: .reference(ReferenceContentSource(sourceView: sourceView, contentArea: UIScreen.main.bounds, customPosition: CGPoint(x: 7.0, y: 3.0))), items: .single(ContextController.Items(content: .list(items))))
self.present(contextController)
}
@@ -3482,7 +3482,7 @@ public final class DrawingToolsInteraction {
}
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }.withUpdated(theme: defaultDarkPresentationTheme)
let contextController = ContextController(presentationData: presentationData, source: .reference(ReferenceContentSource(sourceView: sourceView, contentArea: CGRect(origin: .zero, size: CGSize(width: validLayout.size.width, height: validLayout.size.height - (validLayout.inputHeight ?? 0.0))), customPosition: CGPoint(x: 0.0, y: 1.0))), items: .single(ContextController.Items(content: .list(items))))
let contextController = makeContextController(presentationData: presentationData, source: .reference(ReferenceContentSource(sourceView: sourceView, contentArea: CGRect(origin: .zero, size: CGSize(width: validLayout.size.width, height: validLayout.size.height - (validLayout.inputHeight ?? 0.0))), customPosition: CGPoint(x: 0.0, y: 1.0))), items: .single(ContextController.Items(content: .list(items))))
self.present(contextController, .window(.root), nil)
self.currentFontPicker = contextController
contextController.view.disablesInteractiveKeyboardGestureRecognizer = true
@@ -707,7 +707,7 @@ private final class FeaturedStickersScreenNode: ViewControllerTracingNode {
return nil
}, present: { [weak self] content, sourceView, sourceRect in
if let strongSelf = self {
let controller = PeekController(presentationData: strongSelf.presentationData, content: content, sourceView: {
let controller = makePeekController(presentationData: strongSelf.presentationData, content: content, sourceView: {
return (sourceView, sourceRect)
})
strongSelf.peekController = controller
@@ -756,7 +756,7 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
guard let controller = self.baseNavigationController()?.topViewController as? ViewController else {
return
}
let contextController = ContextController(presentationData: self.presentationData.withUpdated(theme: defaultDarkColorPresentationTheme), source: .reference(HeaderContextReferenceContentSource(controller: controller, sourceNode: self.moreBarButton.referenceNode, actionsOnTop: false)), items: items |> map { ContextController.Items(content: .list($0)) }, gesture: gesture)
let contextController = makeContextController(presentationData: self.presentationData.withUpdated(theme: defaultDarkColorPresentationTheme), source: .reference(HeaderContextReferenceContentSource(controller: controller, sourceNode: self.moreBarButton.referenceNode, actionsOnTop: false)), items: items |> map { ContextController.Items(content: .list($0)) }, gesture: gesture)
controller.presentInGlobalOverlay(contextController)
}
@@ -3254,7 +3254,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
})
}
let contextController = ContextController(presentationData: self.presentationData.withUpdated(theme: defaultDarkColorPresentationTheme), source: .reference(HeaderContextReferenceContentSource(controller: controller, sourceNode: sourceNode, actionsOnTop: actionsOnTop)), items: items |> map { items in
let contextController = makeContextController(presentationData: self.presentationData.withUpdated(theme: defaultDarkColorPresentationTheme), source: .reference(HeaderContextReferenceContentSource(controller: controller, sourceNode: sourceNode, actionsOnTop: actionsOnTop)), items: items |> map { items in
if !items.topItems.isEmpty {
return ContextController.Items(id: AnyHashable(0), content: .twoLists(items.items, items.topItems))
} else {
@@ -265,7 +265,7 @@ final class ImportStickerPackControllerNode: ViewControllerTracingNode, ASScroll
return nil
}, present: { [weak self] content, sourceView, sourceRect in
if let strongSelf = self {
let controller = PeekController(presentationData: strongSelf.presentationData, content: content, sourceView: {
let controller = makePeekController(presentationData: strongSelf.presentationData, content: content, sourceView: {
return (sourceView, sourceRect)
})
controller.visibilityUpdated = { [weak self] visible in
@@ -628,7 +628,7 @@ final class InstantPageControllerNode: ASDisplayNode, ASScrollViewDelegate {
guard let strongSelf = self, let controller = strongSelf.controller else {
return
}
let pinchController = PinchController(sourceNode: sourceNode, getContentAreaInScreenSpace: {
let pinchController = makePinchController(sourceNode: sourceNode, getContentAreaInScreenSpace: {
guard let strongSelf = self else {
return CGRect()
}
@@ -501,7 +501,7 @@ public func folderInviteLinkListController(context: AccountContext, updatedPrese
})
})))
let contextController = ContextController(presentationData: presentationData, source: .reference(InviteLinkContextReferenceContentSource(controller: controller, sourceNode: node)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
let contextController = makeContextController(presentationData: presentationData, source: .reference(InviteLinkContextReferenceContentSource(controller: controller, sourceNode: node)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
presentInGlobalOverlayImpl?(contextController)
}, peerAction: { peer, isEnabled in
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
@@ -518,7 +518,7 @@ public final class InviteLinkInviteController: ViewController {
})))
}
let contextController = ContextController(presentationData: presentationData, source: .reference(InviteLinkContextReferenceContentSource(controller: controller, sourceNode: node)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
let contextController = makeContextController(presentationData: presentationData, source: .reference(InviteLinkContextReferenceContentSource(controller: controller, sourceNode: node)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
self.controller?.presentInGlobalOverlay(contextController)
}, copyLink: { [weak self] invite in
UIPasteboard.general.string = invite.link
@@ -602,7 +602,7 @@ public func inviteLinkListController(context: AccountContext, updatedPresentatio
})))
}
let contextController = ContextController(presentationData: presentationData, source: .reference(InviteLinkContextReferenceContentSource(controller: controller, sourceNode: node)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
let contextController = makeContextController(presentationData: presentationData, source: .reference(InviteLinkContextReferenceContentSource(controller: controller, sourceNode: node)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
presentInGlobalOverlayImpl?(contextController)
}, createLink: {
let controller = inviteLinkEditController(context: context, updatedPresentationData: updatedPresentationData, peerId: peerId, invite: nil, completion: { invite in
@@ -820,7 +820,7 @@ public func inviteLinkListController(context: AccountContext, updatedPresentatio
})))
}
let contextController = ContextController(presentationData: presentationData, source: .extracted(InviteLinkContextExtractedContentSource(controller: controller, sourceNode: node, keepInPlace: false, blurBackground: true)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
let contextController = makeContextController(presentationData: presentationData, source: .extracted(InviteLinkContextExtractedContentSource(controller: controller, sourceNode: node, keepInPlace: false, blurBackground: true)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
presentInGlobalOverlayImpl?(contextController)
}, openAdmin: { admin in
let controller = inviteLinkListController(context: context, peerId: peerId, admin: admin)
@@ -808,7 +808,7 @@ public final class InviteLinkViewController: ViewController {
}
}
let contextController = ContextController(presentationData: presentationData, source: .reference(InviteLinkContextReferenceContentSource(controller: controller, sourceNode: node)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
let contextController = makeContextController(presentationData: presentationData, source: .reference(InviteLinkContextReferenceContentSource(controller: controller, sourceNode: node)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
self?.controller?.presentInGlobalOverlay(contextController)
})
})
@@ -268,7 +268,7 @@ public func inviteRequestsController(context: AccountContext, updatedPresentatio
// dismissPromise.set(true)
// }
let contextController = ContextController(presentationData: presentationData, source: .extracted(source), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
let contextController = makeContextController(presentationData: presentationData, source: .extracted(source), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
presentInGlobalOverlayImpl?(contextController)
})
})
@@ -457,7 +457,7 @@ public final class InviteRequestsSearchContainerNode: SearchDisplayControllerCon
// dismissPromise.set(true)
// }
let contextController = ContextController(presentationData: presentationData, source: .extracted(source), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
let contextController = makeContextController(presentationData: presentationData, source: .extracted(source), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
presentInGlobalOverlay(contextController)
})
})
@@ -339,7 +339,7 @@ public class ItemListInviteRequestItemNode: ListViewItemNode, ItemListItemNode {
return
}
strongSelf.avatarListNode?.controlsContainerNode.alpha = 0.0
let pinchController = PinchController(sourceNode: sourceNode, getContentAreaInScreenSpace: {
let pinchController = makePinchController(sourceNode: sourceNode, getContentAreaInScreenSpace: {
return UIScreen.main.bounds
})
item.context.sharedContext.mainWindow?.presentInGlobalOverlay(pinchController)
@@ -2388,7 +2388,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
)
self.titleView.isHighlighted = true
let contextController = ContextController(
let contextController = makeContextController(
presentationData: self.presentationData,
source: .reference(MediaPickerContextReferenceContentSource(controller: self, sourceView: self.titleView)),
items: .single(ContextController.Items(content: .custom(content))),
@@ -2923,7 +2923,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
self?.presentFilePicker()
})))
let contextController = ContextController(presentationData: self.presentationData, source: .reference(MediaPickerContextReferenceContentSource(controller: self, sourceView: view)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
let contextController = makeContextController(presentationData: self.presentationData, source: .reference(MediaPickerContextReferenceContentSource(controller: self, sourceView: view)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
self.presentInGlobalOverlay(contextController)
return
@@ -3084,7 +3084,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
return ContextController.Items(content: .list(items))
}
let contextController = ContextController(presentationData: self.presentationData, source: .reference(MediaPickerContextReferenceContentSource(controller: self, sourceView: view)), items: items, gesture: gesture)
let contextController = makeContextController(presentationData: self.presentationData, source: .reference(MediaPickerContextReferenceContentSource(controller: self, sourceView: view)), items: items, gesture: gesture)
self.presentInGlobalOverlay(contextController)
}
}
@@ -1668,7 +1668,7 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta
})
})))
let contextController = ContextController(presentationData: presentationData, source: .reference(InviteLinkContextReferenceContentSource(controller: controller, sourceNode: node)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
let contextController = makeContextController(presentationData: presentationData, source: .reference(InviteLinkContextReferenceContentSource(controller: controller, sourceNode: node)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
presentInGlobalOverlayImpl?(contextController)
}, manageInviteLinks: {
let controller = inviteLinkListController(context: context, updatedPresentationData: updatedPresentationData, peerId: peerId, admin: nil)
@@ -488,7 +488,7 @@ public func peersNearbyController(context: AccountContext) -> ViewController {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let chatController = context.sharedContext.makeChatController(context: context, chatLocation: .peer(id: peer.id), subject: nil, botStart: nil, mode: .standard(.previewing), params: nil)
chatController.canReadHistory.set(false)
let contextController = ContextController(presentationData: presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node)), items: peerNearbyContextMenuItems(context: context, peerId: peer.id, present: { c in
let contextController = makeContextController(presentationData: presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node)), items: peerNearbyContextMenuItems(context: context, peerId: peer.id, present: { c in
presentControllerImpl?(c, nil)
}) |> map { ContextController.Items(content: .list($0), animationCache: nil) }, gesture: gesture)
presentInGlobalOverlayImpl?(contextController)
@@ -422,7 +422,7 @@ public func storageUsageExceptionsScreen(
let items: Signal<ContextController.Items, NoError> = .single(ContextController.Items(content: .list(subItems)))
let source: ContextContentSource = .reference(StorageUsageExceptionsContextReferenceContentSource(sourceView: sourceNode.labelNode.view))
let contextController = ContextController(
let contextController = makeContextController(
presentationData: presentationData,
source: source,
items: items,
@@ -689,7 +689,7 @@ public func themePickerController(context: AccountContext, focusOnItemTag: Theme
})))
}
let contextController = ContextController(presentationData: presentationData, source: .controller(ContextControllerContentSourceImpl(controller: themeController, sourceNode: node)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
let contextController = makeContextController(presentationData: presentationData, source: .controller(ContextControllerContentSourceImpl(controller: themeController, sourceNode: node)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
presentInGlobalOverlayImpl?(contextController, nil)
})
}, colorContextAction: { isCurrent, reference, accentColor, node, gesture in
@@ -938,7 +938,7 @@ public func themePickerController(context: AccountContext, focusOnItemTag: Theme
}
}
}
let contextController = ContextController(presentationData: presentationData, source: .controller(ContextControllerContentSourceImpl(controller: themeController, sourceNode: node)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
let contextController = makeContextController(presentationData: presentationData, source: .controller(ContextControllerContentSourceImpl(controller: themeController, sourceNode: node)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
presentInGlobalOverlayImpl?(contextController, nil)
})
})
@@ -779,7 +779,7 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
})))
}
let contextController = ContextController(presentationData: presentationData, source: .controller(ContextControllerContentSourceImpl(controller: themeController, sourceNode: node)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
let contextController = makeContextController(presentationData: presentationData, source: .controller(ContextControllerContentSourceImpl(controller: themeController, sourceNode: node)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
presentInGlobalOverlayImpl?(contextController, nil)
})
}, colorContextAction: { isCurrent, reference, accentColor, node, gesture in
@@ -1028,7 +1028,7 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
}
}
}
let contextController = ContextController(presentationData: presentationData, source: .controller(ContextControllerContentSourceImpl(controller: themeController, sourceNode: node)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
let contextController = makeContextController(presentationData: presentationData, source: .controller(ContextControllerContentSourceImpl(controller: themeController, sourceNode: node)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
presentInGlobalOverlayImpl?(contextController, nil)
})
})
@@ -592,7 +592,7 @@ final class ShareControllerNode: ViewControllerTracingNode, ASScrollViewDelegate
])
return ContextController.Items(content: .list(items), animationCache: nil)
}
let contextController = ContextController(presentationData: presentationData, source: .reference(ShareContextReferenceContentSource(sourceNode: node, customPosition: CGPoint(x: 0.0, y: fromForeignApp ? -116.0 : 0.0))), items: items, gesture: gesture)
let contextController = makeContextController(presentationData: presentationData, source: .reference(ShareContextReferenceContentSource(sourceNode: node, customPosition: CGPoint(x: 0.0, y: fromForeignApp ? -116.0 : 0.0))), items: items, gesture: gesture)
contextController.immediateItemsTransitionAnimation = true
strongSelf.present?(contextController)
}
@@ -2440,7 +2440,7 @@ public func channelStatsController(
})
})))
let contextController = ContextController(presentationData: presentationData, source: .extracted(ChannelStatsContextExtractedContentSource(controller: controller, sourceNode: sourceNode, keepInPlace: false)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
let contextController = makeContextController(presentationData: presentationData, source: .extracted(ChannelStatsContextExtractedContentSource(controller: controller, sourceNode: sourceNode, keepInPlace: false)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
controller.presentInGlobalOverlay(contextController)
}
dismissAllTooltipsImpl = { [weak controller] in
@@ -556,7 +556,7 @@ public func messageStatsController(context: AccountContext, updatedPresentationD
})
})))
let contextController = ContextController(presentationData: presentationData, source: .extracted(ChannelStatsContextExtractedContentSource(controller: controller, sourceNode: sourceNode, keepInPlace: false)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
let contextController = makeContextController(presentationData: presentationData, source: .extracted(ChannelStatsContextExtractedContentSource(controller: controller, sourceNode: sourceNode, keepInPlace: false)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
controller.presentInGlobalOverlay(contextController)
}
return controller
@@ -638,7 +638,7 @@ private final class StickerPackContainer: ASDisplayNode {
if let strongSelf = self {
strongSelf.hideMainPreviewIcon()
let controller = PeekController(presentationData: strongSelf.presentationData, content: content, sourceView: {
let controller = makePeekController(presentationData: strongSelf.presentationData, content: content, sourceView: {
return (sourceView, sourceRect)
})
controller.visibilityUpdated = { [weak self] visible in
@@ -1228,7 +1228,7 @@ private final class StickerPackContainer: ASDisplayNode {
})))
}
let contextController = ContextController(presentationData: self.presentationData, source: .reference(StickerPackContextReferenceContentSource(controller: controller, sourceNode: node)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
let contextController = makeContextController(presentationData: self.presentationData, source: .reference(StickerPackContextReferenceContentSource(controller: controller, sourceNode: node)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
self.presentInGlobalOverlay(contextController, nil)
}
@@ -596,7 +596,7 @@ public final class MediaNavigationAccessoryHeaderNode: ASDisplayNode, ASScrollVi
let items = self.contextMenuSpeedItems(scheduleTooltip: { change in
scheduledTooltip = change
})
let contextController = ContextController(presentationData: self.context.sharedContext.currentPresentationData.with { $0 }, source: .reference(HeaderContextReferenceContentSource(controller: controller, sourceNode: self.rateButton.referenceNode, shouldBeDismissed: self.dismissedPromise.get())), items: items, gesture: gesture)
let contextController = makeContextController(presentationData: self.context.sharedContext.currentPresentationData.with { $0 }, source: .reference(HeaderContextReferenceContentSource(controller: controller, sourceNode: self.rateButton.referenceNode, shouldBeDismissed: self.dismissedPromise.get())), items: items, gesture: gesture)
contextController.dismissed = { [weak self] in
if let scheduledTooltip, let self, let rate = self.playbackBaseRate {
self.setRate?(rate, scheduledTooltip)
@@ -655,7 +655,7 @@ public final class MediaStreamComponent: CombinedComponent {
}
}
let contextController = ContextController(presentationData: presentationData.withUpdated(theme: defaultDarkPresentationTheme), source: .reference(ReferenceContentSource(sourceView: anchorView)), items: .single(ContextController.Items(content: .list(items))), gesture: nil)
let contextController = makeContextController(presentationData: presentationData.withUpdated(theme: defaultDarkPresentationTheme), source: .reference(ReferenceContentSource(sourceView: anchorView)), items: .single(ContextController.Items(content: .list(items))), gesture: nil)
controller.presentInGlobalOverlay(contextController)
}
).minSize(CGSize(width: 44.0, height: 44.0)).tagged(moreButtonTag))
@@ -1112,7 +1112,7 @@ final class VideoChatParticipantsComponent: Component {
}
self.isPinchToZoomActive = true
self.state?.updated(transition: .immediate, isLocal: true)
let pinchController = PinchController(sourceNode: sourceNode, getContentAreaInScreenSpace: {
let pinchController = makePinchController(sourceNode: sourceNode, getContentAreaInScreenSpace: {
return UIScreen.main.bounds
})
component.call.accountContext.sharedContext.mainWindow?.presentInGlobalOverlay(pinchController)
@@ -596,7 +596,7 @@ extension VideoChatScreenComponent.View {
}
let presentationData = currentCall.accountContext.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: environment.theme)
let contextController = ContextController(presentationData: presentationData, source: .reference(VoiceChatContextReferenceContentSource(controller: controller, sourceView: sourceView)), items: .single(ContextController.Items(content: .list(items))), gesture: nil)
let contextController = makeContextController(presentationData: presentationData, source: .reference(VoiceChatContextReferenceContentSource(controller: controller, sourceView: sourceView)), items: .single(ContextController.Items(content: .list(items))), gesture: nil)
controller.presentInGlobalOverlay(contextController)
}
@@ -336,7 +336,7 @@ extension VideoChatScreenComponent.View {
}
let presentationData = currentCall.accountContext.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: environment.theme)
let contextController = ContextController(
let contextController = makeContextController(
presentationData: presentationData,
source: .extracted(ParticipantExtractedContentSource(contentView: sourceView)),
items: items |> map { items in
@@ -394,7 +394,7 @@ extension VideoChatScreenComponent.View {
let items = itemsForEntry()
let presentationData = currentCall.accountContext.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: environment.theme)
let contextController = ContextController(
let contextController = makeContextController(
presentationData: presentationData,
source: .extracted(ParticipantExtractedContentSource(contentView: sourceView)),
items: .single(ContextController.Items(content: .list(items))),
@@ -904,7 +904,7 @@ final class VoiceChatMainStageNode: ASDisplayNode {
}
strongSelf.setControlsHidden(true, animated: false)
strongSelf.controlsHidden?(true)
let pinchController = PinchController(sourceNode: sourceNode, getContentAreaInScreenSpace: {
let pinchController = makePinchController(sourceNode: sourceNode, getContentAreaInScreenSpace: {
return UIScreen.main.bounds
})
strongSelf.context.sharedContext.mainWindow?.presentInGlobalOverlay(pinchController)
@@ -462,7 +462,7 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
return
}
strongSelf.avatarListNode?.controlsContainerNode.alpha = 0.0
let pinchController = PinchController(sourceNode: sourceNode, getContentAreaInScreenSpace: {
let pinchController = makePinchController(sourceNode: sourceNode, getContentAreaInScreenSpace: {
return UIScreen.main.bounds
})
item.context.sharedContext.mainWindow?.presentInGlobalOverlay(pinchController)
@@ -99,7 +99,7 @@ final class VoiceChatPeerProfileNode: ASDisplayNode {
return
}
strongSelf.avatarListNode.controlsContainerNode.alpha = 0.0
let pinchController = PinchController(sourceNode: sourceNode, getContentAreaInScreenSpace: {
let pinchController = makePinchController(sourceNode: sourceNode, getContentAreaInScreenSpace: {
return UIScreen.main.bounds
})
context.sharedContext.mainWindow?.presentInGlobalOverlay(pinchController)
@@ -121,6 +121,9 @@ extension TelegramUser {
if (flags2 & (1 << 16)) != 0 {
botFlags.insert(.hasForum)
}
if (flags2 & (1 << 17)) != 0 {
botFlags.insert(.forumManagedByUser)
}
botInfo = BotUserInfo(flags: botFlags, inlinePlaceholder: botInlinePlaceholder)
}
@@ -43,6 +43,7 @@ public struct BotUserInfoFlags: OptionSet {
public static let isBusiness = BotUserInfoFlags(rawValue: (1 << 6))
public static let hasWebApp = BotUserInfoFlags(rawValue: (1 << 7))
public static let hasForum = BotUserInfoFlags(rawValue: (1 << 8))
public static let forumManagedByUser = BotUserInfoFlags(rawValue: (1 << 9))
}
public struct BotUserInfo: PostboxCoding, Equatable {
+1
View File
@@ -513,6 +513,7 @@ swift_library(
"//submodules/TelegramUI/Components/AlertComponent",
"//submodules/TelegramUI/Components/Chat/ChatAgeRestrictionAlertController",
"//submodules/TelegramUI/Components/CocoonInfoScreen",
"//submodules/TelegramUI/Components/ContextControllerImpl",
] + select({
"@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets,
"//build-system:ios_sim_arm64": [],
@@ -1344,7 +1344,7 @@ public class AdsInfoScreen: ViewController {
})))
}
let contextController = ContextController(presentationData: presentationData, source: .reference(AdsInfoContextReferenceContentSource(controller: controller, sourceView: referenceView, insets: .zero, contentInsets: .zero)), items: .single(ContextController.Items(content: .list(actions))), gesture: nil)
let contextController = makeContextController(presentationData: presentationData, source: .reference(AdsInfoContextReferenceContentSource(controller: controller, sourceView: referenceView, insets: .zero, contentInsets: .zero)), items: .single(ContextController.Items(content: .list(actions))), gesture: nil)
controller.presentInGlobalOverlay(contextController)
}
@@ -1227,7 +1227,7 @@ final class CameraCollageView: UIView, UIGestureRecognizerDelegate {
}
let items = ContextController.Items(content: .list(itemList), tip: .collageReordering)
let controller = ContextController(
let controller = makeContextController(
presentationData: presentationData.withUpdated(theme: defaultDarkColorPresentationTheme),
source: .extracted(CollageContextExtractedContentSource(contentView: sourceView)),
items: .single(items),
@@ -1163,7 +1163,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
return
}
let contextController = ContextController(presentationData: self.presentationData, source: source, items: .single(ContextController.Items(content: .list(actions))), recognizer: recognizer, gesture: gesture)
let contextController = makeContextController(presentationData: self.presentationData, source: source, items: .single(ContextController.Items(content: .list(actions))), recognizer: recognizer, gesture: gesture)
controller.window?.presentInGlobalOverlay(contextController)
}
@@ -22,6 +22,7 @@ swift_library(
"//submodules/AvatarNode",
"//submodules/AccountContext",
"//submodules/UndoUI",
"//submodules/TelegramUI/Components/ContextControllerImpl",
],
visibility = [
"//visibility:public",
@@ -12,6 +12,7 @@ import TelegramStringFormatting
import AvatarNode
import AccountContext
import UndoUI
import ContextControllerImpl
public final class ChatSendAsPeerListContextItem: ContextMenuCustomItem {
let context: AccountContext
@@ -1378,7 +1378,7 @@ private final class ChatSendStarsScreenComponent: Component {
})))
}
let contextController = ContextController(presentationData: presentationData, source: .reference(HeaderContextReferenceContentSource(controller: controller, sourceView: sourceView, actionsOnTop: false)), items: .single(ContextController.Items(id: AnyHashable(0), content: .list(items))), gesture: nil)
let contextController = makeContextController(presentationData: presentationData, source: .reference(HeaderContextReferenceContentSource(controller: controller, sourceView: sourceView, actionsOnTop: false)), items: .single(ContextController.Items(id: AnyHashable(0), content: .list(items))), gesture: nil)
controller.presentInGlobalOverlay(contextController)
}
@@ -46,10 +46,10 @@ public final class ChatSideTopicsPanel: Component {
case top
}
public enum Kind {
public enum Kind: Equatable {
case forum
case monoforum
case botForum
case botForum(forumManagedByUser: Bool)
}
let context: AccountContext
@@ -1249,8 +1249,13 @@ public final class ChatSideTopicsPanel: Component {
)
let titleText: String
if case .botForum = component.kind {
titleText = component.strings.Chat_InlineTopicMenu_NewForumThreadTab
if case let .botForum(forumManagedByUser) = component.kind {
if forumManagedByUser {
titleText = component.strings.Chat_InlineTopicMenu_NewForumThreadTab
} else {
//TODO:localize
titleText = "All"
}
} else {
titleText = component.strings.Chat_InlineTopicMenu_AllTab
}
@@ -1386,8 +1391,13 @@ public final class ChatSideTopicsPanel: Component {
let rightInset: CGFloat = 12.0
let titleText: String
if case .botForum = component.kind {
titleText = component.strings.Chat_InlineTopicMenu_NewForumThreadTab
if case let .botForum(forumManagedByUser) = component.kind {
if forumManagedByUser {
titleText = component.strings.Chat_InlineTopicMenu_NewForumThreadTab
} else {
//TODO:localize
titleText = "All"
}
} else {
titleText = component.strings.Chat_InlineTopicMenu_AllTab
}
@@ -2099,7 +2109,7 @@ public final class ChatSideTopicsPanel: Component {
})
})))
let contextController = ContextController(
let contextController = makeContextController(
presentationData: presentationData,
source: .extracted(ItemExtractedContentSource(
sourceNode: sourceNode,
@@ -2223,7 +2233,7 @@ public final class ChatSideTopicsPanel: Component {
return
}
let contextController = ContextController(
let contextController = makeContextController(
presentationData: presentationData,
source: .extracted(ItemExtractedContentSource(
sourceNode: sourceNode,
@@ -4046,7 +4046,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
}
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let controller = PeekController(presentationData: presentationData, content: content, sourceView: {
let controller = makePeekController(presentationData: presentationData, content: content, sourceView: {
return (sourceView, sourceRect)
})
//strongSelf.peekController = controller
@@ -2336,7 +2336,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
})))
}
let contextController = ContextController(presentationData: presentationData, source: .controller(ContextControllerContentSourceImpl(controller: gallery, sourceView: sourceView, sourceRect: sourceRect)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
let contextController = makeContextController(presentationData: presentationData, source: .controller(ContextControllerContentSourceImpl(controller: gallery, sourceView: sourceView, sourceRect: sourceRect)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
strongSelf.interaction?.presentGlobalOverlayController(contextController, nil)
})
}
@@ -3044,7 +3044,7 @@ public final class EmojiContentPeekBehaviorImpl: EmojiContentPeekBehavior {
if let forceTheme = strongSelf.forceTheme {
presentationData = presentationData.withUpdated(theme: forceTheme)
}
let controller = PeekController(presentationData: presentationData, content: content, sourceView: {
let controller = makePeekController(presentationData: presentationData, content: content, sourceView: {
return (sourceView, sourceRect)
})
controller.visibilityUpdated = { [weak self] visible in
@@ -656,7 +656,7 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
let items = ContextController.Items(content: .list(itemList))
let controller = ContextController(
let controller = makeContextController(
presentationData: presentationData,
source: .extracted(LinkListContextExtractedContentSource(contentView: sourceView)),
items: .single(items),
@@ -725,8 +725,8 @@ public final class ChatListHeaderComponent: Component {
private let leftButtonsContainer: UIView
private let rightButtonsContainer: UIView
private var leftButtonsBackgroundContainer: GlassBackgroundView?
private var rightButtonsBackgroundContainer: GlassBackgroundView?
private var leftButtonsBackgroundContainer: GlassContextExtractableContainer?
private var rightButtonsBackgroundContainer: GlassContextExtractableContainer?
private let storyPeerListExternalState = StoryPeerListComponent.ExternalState()
private var storyPeerList: ComponentView<Empty>?
@@ -774,6 +774,16 @@ public final class ChatListHeaderComponent: Component {
return self.storyPeerList?.view as? StoryPeerListComponent.View
}
public func navigationButtonContextContainer(sourceView: UIView) -> ContextExtractableContainer? {
if let leftButtonsBackgroundContainer = self.leftButtonsBackgroundContainer, sourceView.isDescendant(of: leftButtonsBackgroundContainer) {
return leftButtonsBackgroundContainer
}
if let rightButtonsBackgroundContainer = self.rightButtonsBackgroundContainer, sourceView.isDescendant(of: rightButtonsBackgroundContainer) {
return rightButtonsBackgroundContainer
}
return nil
}
override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if let storyPeerListView = self.storyPeerList?.view {
if let result = storyPeerListView.hitTest(self.convert(point, to: storyPeerListView), with: event) {
@@ -1094,13 +1104,13 @@ public final class ChatListHeaderComponent: Component {
}
if leftButtonsEffectiveWidth != 0.0 {
let leftButtonsBackgroundContainer: GlassBackgroundView
let leftButtonsBackgroundContainer: GlassContextExtractableContainer
var leftButtonsBackgroundContainerTransition = transition
if let current = self.leftButtonsBackgroundContainer {
leftButtonsBackgroundContainer = current
} else {
leftButtonsBackgroundContainerTransition = leftButtonsBackgroundContainerTransition.withAnimation(.none)
leftButtonsBackgroundContainer = GlassBackgroundView()
leftButtonsBackgroundContainer = GlassContextExtractableContainer()
self.leftButtonsBackgroundContainer = leftButtonsBackgroundContainer
self.addSubview(leftButtonsBackgroundContainer)
leftButtonsBackgroundContainer.contentView.addSubview(self.leftButtonsContainer)
@@ -1119,7 +1129,7 @@ public final class ChatListHeaderComponent: Component {
}
if rightButtonsEffectiveWidth != 0.0 {
let rightButtonsBackgroundContainer: GlassBackgroundView
let rightButtonsBackgroundContainer: GlassContextExtractableContainer
var rightButtonsBackgroundContainerTransition = transition
let rightButtonsContainerFrame = CGRect(origin: CGPoint(x: availableSize.width - component.sideInset - max(44.0, rightButtonsEffectiveWidth), y: 0.0), size: CGSize(width: max(44.0, rightButtonsEffectiveWidth), height: 44.0))
@@ -1128,7 +1138,7 @@ public final class ChatListHeaderComponent: Component {
rightButtonsBackgroundContainer = current
} else {
rightButtonsBackgroundContainerTransition = rightButtonsBackgroundContainerTransition.withAnimation(.none)
rightButtonsBackgroundContainer = GlassBackgroundView()
rightButtonsBackgroundContainer = GlassContextExtractableContainer()
self.rightButtonsBackgroundContainer = rightButtonsBackgroundContainer
self.addSubview(rightButtonsBackgroundContainer)
rightButtonsBackgroundContainer.contentView.addSubview(self.rightButtonsContainer)
@@ -115,17 +115,7 @@ public final class ChatNavigationBarTitleView: UIView, NavigationBarTitleView {
let transition = ComponentTransition(transition)
if let contentData = self.contentData {
let displayBackground: Bool
if let singleColor = contentData.wallpaper.singleColor {
let brightness = singleColor.brightness
if brightness <= 0.2 || brightness >= 0.8 {
displayBackground = false
} else {
displayBackground = true
}
} else {
displayBackground = true
}
let displayBackground: Bool = true
let titleSize = self.title.update(
transition: transition,
@@ -1039,6 +1029,7 @@ public final class ChatTitleComponent: Component {
if let minSubtitleWidth {
contentSize.width = max(contentSize.width, minSubtitleWidth)
}
contentSize.width = max(min(150.0, availableSize.width - containerSideInset * 2.0), contentSize.width)
contentSize.height += subtitleSize.height
let containerSize = CGSize(width: contentSize.width + containerSideInset * 2.0, height: 44.0)
@@ -0,0 +1,41 @@
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
swift_library(
name = "ContextControllerImpl",
module_name = "ContextControllerImpl",
srcs = glob([
"Sources/**/*.swift",
]),
copts = [
"-warnings-as-errors",
],
deps = [
"//submodules/AccountContext",
"//submodules/TelegramUI/Components/AnimationCache",
"//submodules/AnimationUI",
"//submodules/AppBundle",
"//submodules/AsyncDisplayKit",
"//submodules/Components/ComponentDisplayAdapters",
"//submodules/ComponentFlow",
"//submodules/ContextUI",
"//submodules/Display",
"//submodules/TelegramUI/Components/EmojiStatusComponent",
"//submodules/TelegramUI/Components/EntityKeyboard",
"//submodules/TelegramUI/Components/GlassBackgroundComponent",
"//submodules/TelegramUI/Components/LottieComponent",
"//submodules/Markdown",
"//submodules/TelegramUI/Components/MultiAnimationRenderer",
"//submodules/ReactionSelectionNode",
"//submodules/SSignalKit/SwiftSignalKit",
"//submodules/TelegramCore",
"//submodules/TelegramPresentationData",
"//submodules/TextFormat",
"//submodules/TelegramUI/Components/TextNodeWithEntities",
"//submodules/TextSelectionNode",
"//submodules/UIKitRuntimeUtils",
"//submodules/UndoUI",
],
visibility = [
"//visibility:public",
],
)
@@ -5,19 +5,7 @@ import Display
import TelegramPresentationData
import SwiftSignalKit
import Markdown
public enum ContextActionSibling {
case none
case item
case separator
}
public protocol ContextActionNodeProtocol: ASDisplayNode {
func setIsHighlighted(_ value: Bool)
func performAction()
func actionNode(at point: CGPoint) -> ContextActionNodeProtocol
var isActionEnabled: Bool { get }
}
import ContextUI
public final class ContextActionNode: ASDisplayNode, ContextActionNodeProtocol {
private var presentationData: PresentationData
@@ -10,6 +10,7 @@ import AppBundle
import TextFormat
import TextNodeWithEntities
import SwiftSignalKit
import ContextUI
private final class ContextActionsSelectionGestureRecognizer: UIPanGestureRecognizer {
var updateLocation: ((CGPoint, Bool) -> Void)?
@@ -18,76 +18,7 @@ import ComponentDisplayAdapters
import GlassBackgroundComponent
import LottieComponent
import TextNodeWithEntities
public protocol ContextControllerActionsStackItemNode: ASDisplayNode {
var wantsFullWidth: Bool { get }
func update(
presentationData: PresentationData,
constrainedSize: CGSize,
standardMinWidth: CGFloat,
standardMaxWidth: CGFloat,
additionalBottomInset: CGFloat,
transition: ContainedViewLayoutTransition
) -> (size: CGSize, apparentHeight: CGFloat)
func highlightGestureMoved(location: CGPoint)
func highlightGestureFinished(performAction: Bool)
func decreaseHighlightedIndex()
func increaseHighlightedIndex()
}
public struct ContextControllerReactionItems {
public var context: AccountContext
public var reactionItems: [ReactionContextItem]
public var selectedReactionItems: Set<MessageReaction.Reaction>
public var reactionsTitle: String?
public var reactionsLocked: Bool
public var animationCache: AnimationCache
public var alwaysAllowPremiumReactions: Bool
public var allPresetReactionsAreAvailable: Bool
public var getEmojiContent: ((AnimationCache, MultiAnimationRenderer) -> Signal<EmojiPagerContentComponent, NoError>)?
public init(context: AccountContext, reactionItems: [ReactionContextItem], selectedReactionItems: Set<MessageReaction.Reaction>, reactionsTitle: String?, reactionsLocked: Bool, animationCache: AnimationCache, alwaysAllowPremiumReactions: Bool, allPresetReactionsAreAvailable: Bool, getEmojiContent: ((AnimationCache, MultiAnimationRenderer) -> Signal<EmojiPagerContentComponent, NoError>)?) {
self.context = context
self.reactionItems = reactionItems
self.selectedReactionItems = selectedReactionItems
self.reactionsTitle = reactionsTitle
self.reactionsLocked = reactionsLocked
self.animationCache = animationCache
self.alwaysAllowPremiumReactions = alwaysAllowPremiumReactions
self.allPresetReactionsAreAvailable = allPresetReactionsAreAvailable
self.getEmojiContent = getEmojiContent
}
}
public final class ContextControllerPreviewReaction {
public let context: AccountContext
public let file: TelegramMediaFile
public init(context: AccountContext, file: TelegramMediaFile) {
self.context = context
self.file = file
}
}
public protocol ContextControllerActionsStackItem: AnyObject {
func node(
context: AccountContext?,
getController: @escaping () -> ContextControllerProtocol?,
requestDismiss: @escaping (ContextMenuActionResult) -> Void,
requestUpdate: @escaping (ContainedViewLayoutTransition) -> Void,
requestUpdateApparentHeight: @escaping (ContainedViewLayoutTransition) -> Void
) -> ContextControllerActionsStackItemNode
var id: AnyHashable? { get }
var tip: ContextController.Tip? { get }
var tipSignal: Signal<ContextController.Tip?, NoError>? { get }
var reactionItems: ContextControllerReactionItems? { get }
var previewReaction: ContextControllerPreviewReaction? { get }
var dismissed: (() -> Void)? { get }
}
import ContextUI
public protocol ContextControllerActionsListItemNode: ASDisplayNode {
func update(presentationData: PresentationData, constrainedSize: CGSize) -> (minSize: CGSize, apply: (_ size: CGSize, _ transition: ContainedViewLayoutTransition) -> Void)
@@ -1326,17 +1257,11 @@ private final class ItemSelectionRecognizer: UIGestureRecognizer {
}
}
public final class ContextControllerActionsStackNode: ASDisplayNode {
public enum Presentation {
case modal
case inline
case additional
}
public final class ContextControllerActionsStackNodeImpl: ASDisplayNode, ContextControllerActionsStackNode {
final class NavigationContainer: ASDisplayNode, ASGestureRecognizerDelegate {
let backgroundContainer: GlassBackgroundContainerView
let backgroundView: GlassBackgroundView
var sourceExtractableContainer: ContextExtractableContainer?
let contentContainer: UIView
var requestUpdate: ((ContainedViewLayoutTransition) -> Void)?
@@ -1417,6 +1342,55 @@ public final class ContextControllerActionsStackNode: ASDisplayNode {
}
}
func animateIn(fromExtractableContainer extractableContainer: ContextExtractableContainer) {
let transition: ComponentTransition = .spring(duration: 0.42)
let normalSize = extractableContainer.extractableContentView.bounds.size
let normalCornerRadius: CGFloat = min(normalSize.width, normalSize.height) * 0.5
self.sourceExtractableContainer = extractableContainer
self.backgroundView.isHidden = true
self.backgroundContainer.contentView.addSubview(extractableContainer.extractableContentView)
for subview in extractableContainer.extractableContentView.subviews {
if let subview = subview as? GlassBackgroundView {
//TODO:release
subview.contentView.addSubview(self.contentContainer)
break
}
}
self.sourceExtractableContainer = nil
self.contentContainer.frame = CGRect(origin: CGPoint(), size: normalSize)
self.contentContainer.layer.cornerRadius = normalCornerRadius
transition.setFrame(view: self.contentContainer, frame: CGRect(origin: CGPoint(), size: self.backgroundContainer.bounds.size))
transition.setCornerRadius(layer: self.contentContainer.layer, cornerRadius: 30.0)
self.contentContainer.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
extractableContainer.updateState(state: .extracted(size: normalSize, cornerRadius: normalCornerRadius, state: .animatedOut), transition: .immediate)
extractableContainer.updateState(state: .extracted(size: self.backgroundContainer.bounds.size, cornerRadius: 30.0, state: .animatedIn), transition: transition.containedViewLayoutTransition)
}
func animateOut(toExtractableContainer extractableContainer: ContextExtractableContainer, transition: ContainedViewLayoutTransition) {
let transition = ComponentTransition(transition)
let normalSize = extractableContainer.extractableContentView.bounds.size
let normalCornerRadius: CGFloat = min(normalSize.width, normalSize.height) * 0.5
transition.setFrame(view: self.contentContainer, frame: CGRect(origin: CGPoint(), size: normalSize))
transition.attachAnimation(view: self.contentContainer, id: "animateOut", completion: { [weak extractableContainer] _ in
guard let extractableContainer else {
return
}
extractableContainer.addSubview(extractableContainer.extractableContentView)
})
transition.setCornerRadius(layer: self.contentContainer.layer, cornerRadius: normalCornerRadius)
transition.setAlpha(view: self.contentContainer, alpha: 0.0)
extractableContainer.updateState(state: .normal, transition: transition.containedViewLayoutTransition)
}
func update(presentationData: PresentationData, presentation: Presentation, size: CGSize, transition: ContainedViewLayoutTransition) {
let transition = ComponentTransition(transition)
@@ -1443,7 +1417,12 @@ public final class ContextControllerActionsStackNode: ASDisplayNode {
}
transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(), size: size))
self.backgroundView.update(size: size, cornerRadius: 30.0, isDark: presentationData.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: UIColor(white: presentationData.theme.overallDarkAppearance ? 0.0 : 1.0, alpha: 0.6)), isInteractive: false, transition: transition)
self.backgroundView.update(size: size, cornerRadius: 30.0, isDark: presentationData.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: UIColor(white: presentationData.theme.overallDarkAppearance ? 0.0 : 1.0, alpha: 0.6)), isInteractive: true, transition: transition)
if let sourceExtractableContainer = self.sourceExtractableContainer {
transition.setFrame(view: sourceExtractableContainer.extractableContentView, frame: CGRect(origin: CGPoint(), size: size))
sourceExtractableContainer.updateState(state: .extracted(size: size, cornerRadius: 30.0, state: .animatedIn), transition: transition.containedViewLayoutTransition)
}
}
}
@@ -2080,4 +2059,12 @@ public final class ContextControllerActionsStackNode: ASDisplayNode {
}
}
}
func animateIn(fromExtractableContainer extractableContainer: ContextExtractableContainer) {
self.navigationContainer.animateIn(fromExtractableContainer: extractableContainer)
}
func animateOut(toExtractableContainer extractableContainer: ContextExtractableContainer, transition: ContainedViewLayoutTransition) {
self.navigationContainer.animateOut(toExtractableContainer: extractableContainer, transition: transition)
}
}
@@ -9,6 +9,9 @@ import SwiftSignalKit
import ReactionSelectionNode
import UndoUI
import AccountContext
import ContextUI
import ComponentFlow
import ComponentDisplayAdapters
private extension ContextControllerTakeViewInfo.ContainingItem {
var contentRect: CGRect {
@@ -264,7 +267,7 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
private let contentRectDebugNode: ASDisplayNode
private var actionsContainerNode: ASDisplayNode
private let actionsStackNode: ContextControllerActionsStackNode
private let actionsStackNode: ContextControllerActionsStackNodeImpl
private let additionalActionsStackNode: ContextControllerActionsStackNode
private var validLayout: ContainerViewLayout?
@@ -324,7 +327,7 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
self.contentRectDebugNode.backgroundColor = UIColor.red.withAlphaComponent(0.2)
self.actionsContainerNode = ASDisplayNode()
self.actionsStackNode = ContextControllerActionsStackNode(
self.actionsStackNode = ContextControllerActionsStackNodeImpl(
context: self.context,
getController: getController,
requestDismiss: { result in
@@ -333,7 +336,7 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
requestUpdate: requestUpdate
)
self.additionalActionsStackNode = ContextControllerActionsStackNode(
self.additionalActionsStackNode = ContextControllerActionsStackNodeImpl(
context: self.context,
getController: getController,
requestDismiss: { result in
@@ -662,7 +665,7 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
var contentTopInset: CGFloat = topInset
var removedReactionContextNode: ReactionContextNode?
if let reactionItems = self.actionsStackNode.topReactionItems, !reactionItems.reactionItems.isEmpty, let controller = self.getController() as? ContextController {
if let reactionItems = self.actionsStackNode.topReactionItems, !reactionItems.reactionItems.isEmpty, let controller = self.getController() as? ContextControllerImpl {
let reactionContextNode: ReactionContextNode
if let current = self.reactionContextNode {
reactionContextNode = current
@@ -707,14 +710,14 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
}
reactionContextNode.reactionSelected = { [weak self] reaction, isLarge in
guard let strongSelf = self, let controller = strongSelf.getController() as? ContextController else {
guard let strongSelf = self, let controller = strongSelf.getController() as? ContextControllerImpl else {
return
}
controller.reactionSelected?(reaction, isLarge)
}
let context = reactionItems.context
reactionContextNode.premiumReactionsSelected = { [weak self] file in
guard let strongSelf = self, let validLayout = strongSelf.validLayout, let controller = strongSelf.getController() as? ContextController else {
guard let strongSelf = self, let validLayout = strongSelf.validLayout, let controller = strongSelf.getController() as? ContextControllerImpl else {
return
}
@@ -799,6 +802,7 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
var contentRect: CGRect
var isContentResizeableVertically: Bool = false
let _ = isContentResizeableVertically
var contextExtractableContainer: (container: ContextExtractableContainer, sourceRect: CGRect)?
switch self.source {
case let .location(location):
@@ -810,6 +814,10 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
}
case let .reference(reference):
if let transitionInfo = reference.transitionInfo() {
if let referenceView = transitionInfo.referenceView as? ContextExtractableContainer {
contextExtractableContainer = (referenceView, convertFrame(transitionInfo.referenceView.bounds.inset(by: transitionInfo.insets), from: transitionInfo.referenceView, to: self.view))
}
contentRect = convertFrame(transitionInfo.referenceView.bounds.inset(by: transitionInfo.insets), from: transitionInfo.referenceView, to: self.view).insetBy(dx: -2.0, dy: 0.0)
contentRect.size.width += 5.0
contentParentGlobalFrame = CGRect(origin: CGPoint(x: 0.0, y: contentRect.minY), size: CGSize(width: layout.size.width, height: contentRect.height))
@@ -1051,14 +1059,17 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
transition.updateFrame(node: self.contentRectDebugNode, frame: contentRect, beginWithCurrentState: true)
var actionsFrame: CGRect
if case let .reference(source) = self.source, let actionsPosition = source.transitionInfo()?.actionsPosition, case .top = actionsPosition {
if let contextExtractableContainer {
let _ = contextExtractableContainer
actionsFrame = CGRect(origin: CGPoint(x: actionsSideInset, y: contentRect.minY), size: actionsSize)
} else if case let .reference(source) = self.source, let actionsPosition = source.transitionInfo()?.actionsPosition, case .top = actionsPosition {
actionsFrame = CGRect(origin: CGPoint(x: actionsSideInset, y: contentRect.minY - contentActionsSpacing - actionsSize.height), size: actionsSize)
} else {
actionsFrame = CGRect(origin: CGPoint(x: actionsSideInset, y: contentRect.maxY + contentActionsSpacing), size: actionsSize)
}
var contentVerticalOffset: CGFloat = 0.0
if keepInPlace, case .extracted = self.source {
if contextExtractableContainer == nil, keepInPlace, case .extracted = self.source {
actionsFrame.origin.y = contentRect.minY - contentActionsSpacing - actionsFrame.height
let statusBarHeight = (layout.statusBarHeight ?? 0.0)
if actionsFrame.origin.y < statusBarHeight {
@@ -1333,52 +1344,65 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
currentContentScreenFrame = contentRect
}
self.actionsContainerNode.layer.animateAlpha(from: 0.0, to: self.actionsContainerNode.alpha, duration: 0.05)
self.actionsContainerNode.layer.animateSpring(
from: 0.01 as NSNumber,
to: 1.0 as NSNumber,
keyPath: "transform.scale",
duration: duration,
delay: 0.0,
initialVelocity: 0.0,
damping: springDamping,
additive: false
)
var actionsPositionDeltaXDistance: CGFloat = 0.0
if case .center = actionsHorizontalAlignment {
actionsPositionDeltaXDistance = currentContentScreenFrame.midX - self.actionsContainerNode.frame.midX
}
if case .reference = self.source {
actionsPositionDeltaXDistance = currentContentScreenFrame.midX - self.actionsContainerNode.frame.midX
}
let actionsVerticalTransitionDirection: CGFloat
if let contentNode = itemContentNode {
if contentNode.frame.minY < self.actionsContainerNode.frame.minY {
actionsVerticalTransitionDirection = -1.0
} else {
actionsVerticalTransitionDirection = 1.0
}
if let contextExtractableContainer {
let transition: ContainedViewLayoutTransition = .animated(duration: duration, curve: .spring)
transition.animatePositionAdditive(layer: self.actionsContainerNode.layer, offset: CGPoint(
x: contextExtractableContainer.sourceRect.minX - self.actionsContainerNode.frame.minX,
y: contextExtractableContainer.sourceRect.minY - self.actionsContainerNode.frame.minY
))
self.actionsStackNode.animateIn(fromExtractableContainer: contextExtractableContainer.container)
} else {
if contentRect.minY < self.actionsContainerNode.frame.minY {
actionsVerticalTransitionDirection = -1.0
} else {
actionsVerticalTransitionDirection = 1.0
self.actionsContainerNode.layer.animateAlpha(from: 0.0, to: self.actionsContainerNode.alpha, duration: 0.05)
self.actionsContainerNode.layer.animateSpring(
from: 0.01 as NSNumber,
to: 1.0 as NSNumber,
keyPath: "transform.scale",
duration: duration,
delay: 0.0,
initialVelocity: 0.0,
damping: springDamping,
additive: false
)
var actionsPositionDeltaXDistance: CGFloat = 0.0
if case .center = actionsHorizontalAlignment {
actionsPositionDeltaXDistance = currentContentScreenFrame.midX - self.actionsContainerNode.frame.midX
}
if case .reference = self.source {
actionsPositionDeltaXDistance = currentContentScreenFrame.midX - self.actionsContainerNode.frame.midX
}
let actionsVerticalTransitionDirection: CGFloat
if let contentNode = itemContentNode {
if contentNode.frame.minY < self.actionsContainerNode.frame.minY {
actionsVerticalTransitionDirection = -1.0
} else {
actionsVerticalTransitionDirection = 1.0
}
} else {
if contentRect.minY < self.actionsContainerNode.frame.minY {
actionsVerticalTransitionDirection = -1.0
} else {
actionsVerticalTransitionDirection = 1.0
}
}
let actionsPositionDeltaYDistance = -animationInContentYDistance + actionsVerticalTransitionDirection * actionsSize.height / 2.0 - contentActionsSpacing
self.actionsContainerNode.layer.animateSpring(
from: NSValue(cgPoint: CGPoint(x: actionsPositionDeltaXDistance, y: actionsPositionDeltaYDistance)),
to: NSValue(cgPoint: CGPoint()),
keyPath: "position",
duration: duration,
delay: 0.0,
initialVelocity: 0.0,
damping: springDamping,
additive: true
)
self.actionsStackNode.animateIn()
}
let actionsPositionDeltaYDistance = -animationInContentYDistance + actionsVerticalTransitionDirection * actionsSize.height / 2.0 - contentActionsSpacing
self.actionsContainerNode.layer.animateSpring(
from: NSValue(cgPoint: CGPoint(x: actionsPositionDeltaXDistance, y: actionsPositionDeltaYDistance)),
to: NSValue(cgPoint: CGPoint()),
keyPath: "position",
duration: duration,
delay: 0.0,
initialVelocity: 0.0,
damping: springDamping,
additive: true
)
if let reactionContextNode = self.reactionContextNode {
let reactionsPositionDeltaYDistance = -animationInContentYDistance
@@ -1395,8 +1419,6 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
reactionContextNode.animateIn(from: currentContentScreenFrame)
}
self.actionsStackNode.animateIn()
if let contentNode = itemContentNode {
contentNode.containingItem.isExtractedToContextPreview = true
contentNode.containingItem.isExtractedToContextPreviewUpdated?(true)
@@ -1678,41 +1700,55 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
)
}
self.actionsContainerNode.layer.animateAlpha(from: self.actionsContainerNode.alpha, to: 0.0, duration: duration, removeOnCompletion: false)
self.actionsContainerNode.layer.animate(
from: 1.0 as NSNumber,
to: 0.01 as NSNumber,
keyPath: "transform.scale",
timingFunction: timingFunction,
duration: duration,
delay: 0.0,
removeOnCompletion: false,
completion: { _ in
if let contextExtractableContainer {
let transition: ContainedViewLayoutTransition = .animated(duration: duration, curve: .easeInOut)
transition.updateFrame(node: self.actionsContainerNode, frame: CGRect(origin: CGPoint(x: contextExtractableContainer.sourceRect.minX, y: contextExtractableContainer.sourceRect.minY), size: self.actionsContainerNode.bounds.size))
ComponentTransition(transition).attachAnimation(view: self.actionsContainerNode.view, id: "animateOut", completion: { _ in
if completeWithActionStack {
restoreOverlayViews.forEach({ $0() })
completion()
}
})
self.actionsStackNode.animateOut(toExtractableContainer: contextExtractableContainer.container, transition: transition)
} else {
self.actionsContainerNode.layer.animateAlpha(from: self.actionsContainerNode.alpha, to: 0.0, duration: duration, removeOnCompletion: false)
self.actionsContainerNode.layer.animate(
from: 1.0 as NSNumber,
to: 0.01 as NSNumber,
keyPath: "transform.scale",
timingFunction: timingFunction,
duration: duration,
delay: 0.0,
removeOnCompletion: false,
completion: { _ in
if completeWithActionStack {
restoreOverlayViews.forEach({ $0() })
completion()
}
}
)
var actionsPositionDeltaXDistance: CGFloat = 0.0
if case .center = actionsHorizontalAlignment {
actionsPositionDeltaXDistance = currentContentScreenFrame.midX - self.actionsContainerNode.frame.midX
}
)
var actionsPositionDeltaXDistance: CGFloat = 0.0
if case .center = actionsHorizontalAlignment {
actionsPositionDeltaXDistance = currentContentScreenFrame.midX - self.actionsContainerNode.frame.midX
if case .reference = self.source {
actionsPositionDeltaXDistance = currentContentScreenFrame.midX - self.actionsContainerNode.frame.midX
}
let actionsPositionDeltaYDistance = -animationInContentYDistance + actionsVerticalTransitionDirection * actionsSize.height / 2.0 - contentActionsSpacing
self.actionsContainerNode.layer.animate(
from: NSValue(cgPoint: CGPoint()),
to: NSValue(cgPoint: CGPoint(x: actionsPositionDeltaXDistance, y: actionsPositionDeltaYDistance)),
keyPath: "position",
timingFunction: timingFunction,
duration: duration,
delay: 0.0,
removeOnCompletion: false,
additive: true
)
}
if case .reference = self.source {
actionsPositionDeltaXDistance = currentContentScreenFrame.midX - self.actionsContainerNode.frame.midX
}
let actionsPositionDeltaYDistance = -animationInContentYDistance + actionsVerticalTransitionDirection * actionsSize.height / 2.0 - contentActionsSpacing
self.actionsContainerNode.layer.animate(
from: NSValue(cgPoint: CGPoint()),
to: NSValue(cgPoint: CGPoint(x: actionsPositionDeltaXDistance, y: actionsPositionDeltaYDistance)),
keyPath: "position",
timingFunction: timingFunction,
duration: duration,
delay: 0.0,
removeOnCompletion: false,
additive: true
)
if let reactionContextNode = self.reactionContextNode {
reactionContextNode.animateOut(to: currentContentScreenFrame, animatingOutToReaction: self.reactionContextNodeIsAnimatingOut)
@@ -7,6 +7,7 @@ import TextSelectionNode
import TelegramCore
import SwiftSignalKit
import ReactionSelectionNode
import ContextUI
enum ContextControllerPresentationNodeStateTransition {
case animateIn
@@ -12,10 +12,11 @@ import PlainButtonComponent
import MultilineTextComponent
import ComponentDisplayAdapters
import AccountContext
import ContextUI
final class ContextSourceContainer: ASDisplayNode {
final class Source {
weak var controller: ContextController?
weak var controller: ContextControllerImpl?
let id: AnyHashable
let title: String
@@ -44,7 +45,7 @@ final class ContextSourceContainer: ASDisplayNode {
private let actionsReady = Promise<Bool>()
init(
controller: ContextController,
controller: ContextControllerImpl,
id: AnyHashable,
title: String,
footer: String?,
@@ -359,7 +360,7 @@ final class ContextSourceContainer: ASDisplayNode {
}
}
private weak var controller: ContextController?
private weak var controller: ContextControllerImpl?
private let backgroundNode: NavigationBackgroundNode
@@ -387,7 +388,7 @@ final class ContextSourceContainer: ASDisplayNode {
return self.activeSource?.presentationNode.wantsDisplayBelowKeyboard() ?? false
}
init(controller: ContextController, configuration: ContextController.Configuration, context: AccountContext?) {
init(controller: ContextControllerImpl, configuration: ContextController.Configuration, context: AccountContext?) {
self.controller = controller
self.backgroundNode = NavigationBackgroundNode(color: .clear, enableBlur: false)
@@ -0,0 +1,120 @@
import Foundation
import UIKit
import AsyncDisplayKit
import Display
import SwiftSignalKit
import TelegramPresentationData
import ContextUI
public final class PeekControllerImpl: ViewController, PeekController, ContextControllerProtocol {
public var useComplexItemsTransitionAnimation: Bool = false
public var immediateItemsTransitionAnimation = false
public func getActionsMinHeight() -> ContextController.ActionsHeight? {
return nil
}
public func setItems(_ items: Signal<ContextController.Items, NoError>, minHeight: ContextController.ActionsHeight?, animated: Bool) {
}
public func setItems(_ items: Signal<ContextController.Items, NoError>, minHeight: ContextController.ActionsHeight?, previousActionsTransition: ContextController.PreviousActionsTransition) {
}
public func pushItems(items: Signal<ContextController.Items, NoError>) {
self.controllerNode.pushItems(items: items)
}
public func popItems() {
self.controllerNode.popItems()
}
private var controllerNode: PeekControllerNode {
return self.displayNode as! PeekControllerNode
}
public var contentNode: PeekControllerContentNode & ASDisplayNode {
return self.controllerNode.contentNode
}
private let presentationData: PresentationData
private let content: PeekControllerContent
public var sourceView: () -> (UIView, CGRect)?
private let activateImmediately: Bool
public var visibilityUpdated: ((Bool) -> Void)?
public var getOverlayViews: (() -> [UIView])?
public var appeared: (() -> Void)?
public var disappeared: (() -> Void)?
private var animatedIn = false
private let _ready = Promise<Bool>()
override public var ready: Promise<Bool> {
return self._ready
}
public init(presentationData: PresentationData, content: PeekControllerContent, sourceView: @escaping () -> (UIView, CGRect)?, activateImmediately: Bool = false) {
self.presentationData = presentationData
self.content = content
self.sourceView = sourceView
self.activateImmediately = activateImmediately
super.init(navigationBarPresentationData: nil)
self.statusBar.statusBarStyle = .Ignore
}
required public init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override public func loadDisplayNode() {
self.displayNode = PeekControllerNode(presentationData: self.presentationData, controller: self, content: self.content, requestDismiss: { [weak self] in
self?.dismiss()
})
self.displayNodeDidLoad()
}
private func getSourceRect() -> CGRect {
if let (sourceView, sourceRect) = self.sourceView() {
return sourceView.convert(sourceRect, to: self.view)
} else {
let size = self.displayNode.bounds.size
return CGRect(origin: CGPoint(x: floor((size.width - 10.0) / 2.0), y: floor((size.height - 10.0) / 2.0)), size: CGSize(width: 10.0, height: 10.0))
}
}
override public func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if !self.animatedIn {
self.animatedIn = true
self.controllerNode.animateIn(from: self.getSourceRect())
self.visibilityUpdated?(true)
if self.activateImmediately {
self.controllerNode.activateMenu(immediately: true)
}
}
}
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
super.containerLayoutUpdated(layout, transition: transition)
self.controllerNode.containerLayoutUpdated(layout, transition: transition)
}
override public func dismiss(completion: (() -> Void)? = nil) {
self.visibilityUpdated?(false)
self.controllerNode.animateOut(to: self.getSourceRect(), completion: { [weak self] in
self?.presentingViewController?.dismiss(animated: false, completion: nil)
})
}
public func dismiss(result: ContextMenuActionResult, completion: (() -> Void)?) {
self.dismiss(completion: completion)
}
}
@@ -4,10 +4,11 @@ import AsyncDisplayKit
import Display
import SwiftSignalKit
import TelegramPresentationData
import ContextUI
private let animationDurationFactor: Double = 1.0
final class PeekControllerNode: ViewControllerTracingNode {
final class PeekControllerNode: ViewControllerTracingNode, PeekControllerNodeProtocol {
private let requestDismiss: () -> Void
private let presentationData: PresentationData
@@ -79,7 +80,7 @@ final class PeekControllerNode: ViewControllerTracingNode {
var activatedActionImpl: (() -> Void)?
var requestLayoutImpl: ((ContainedViewLayoutTransition) -> Void)?
self.actionsStackNode = ContextControllerActionsStackNode(
self.actionsStackNode = ContextControllerActionsStackNodeImpl(
context: nil,
getController: { [weak controller] in
return controller
@@ -405,7 +406,7 @@ final class PeekControllerNode: ViewControllerTracingNode {
}
}
func activateMenu(immediately: Bool = false) {
func activateMenu(immediately: Bool) {
if self.content.menuItems().isEmpty {
if let fullScreenAccessoryNode = self.fullScreenAccessoryNode {
fullScreenAccessoryNode.alpha = 1.0
@@ -0,0 +1,262 @@
import Foundation
import UIKit
import AsyncDisplayKit
import Display
import TelegramPresentationData
import TextSelectionNode
import TelegramCore
import SwiftSignalKit
import UIKitRuntimeUtils
import ContextUI
private final class PinchControllerNode: ViewControllerTracingNode {
private weak var controller: PinchController?
private var initialSourceFrame: CGRect?
private let clippingNode: ASDisplayNode
private let scrollingContainer: ASDisplayNode
private let sourceNode: PinchSourceContainerNode
private let disableScreenshots: Bool
private let getContentAreaInScreenSpace: () -> CGRect
private let dimNode: ASDisplayNode
private var validLayout: ContainerViewLayout?
private var isAnimatingOut: Bool = false
private var hapticFeedback: HapticFeedback?
init(controller: PinchController, sourceNode: PinchSourceContainerNode, disableScreenshots: Bool, getContentAreaInScreenSpace: @escaping () -> CGRect) {
self.controller = controller
self.sourceNode = sourceNode
self.disableScreenshots = disableScreenshots
self.getContentAreaInScreenSpace = getContentAreaInScreenSpace
self.dimNode = ASDisplayNode()
self.dimNode.backgroundColor = UIColor(white: 0.0, alpha: 0.5)
self.dimNode.alpha = 0.0
self.clippingNode = ASDisplayNode()
self.clippingNode.clipsToBounds = true
self.scrollingContainer = ASDisplayNode()
super.init()
self.addSubnode(self.dimNode)
self.addSubnode(self.clippingNode)
self.clippingNode.addSubnode(self.scrollingContainer)
self.sourceNode.deactivate = { [weak self] in
guard let strongSelf = self else {
return
}
strongSelf.controller?.dismiss()
}
self.sourceNode.updated = { [weak self] scale, pinchLocation, offset in
guard let strongSelf = self, let initialSourceFrame = strongSelf.initialSourceFrame else {
return
}
strongSelf.dimNode.alpha = max(0.0, min(1.0, scale - 1.0))
let pinchOffset = CGPoint(
x: pinchLocation.x - initialSourceFrame.width / 2.0,
y: pinchLocation.y - initialSourceFrame.height / 2.0
)
var transform = CATransform3DIdentity
transform = CATransform3DTranslate(transform, offset.x - pinchOffset.x * (scale - 1.0), offset.y - pinchOffset.y * (scale - 1.0), 0.0)
transform = CATransform3DScale(transform, scale, scale, 0.0)
strongSelf.sourceNode.contentNode.transform = transform
}
if self.disableScreenshots {
setLayerDisableScreenshots(self.layer, true)
}
}
deinit {
}
override func didLoad() {
super.didLoad()
}
func updateLayout(layout: ContainerViewLayout, transition: ContainedViewLayoutTransition, previousActionsContainerNode: ContextActionsContainerNode?) {
if self.isAnimatingOut {
return
}
self.validLayout = layout
transition.updateFrame(node: self.dimNode, frame: CGRect(origin: CGPoint(), size: layout.size))
transition.updateFrame(node: self.clippingNode, frame: CGRect(origin: CGPoint(), size: layout.size))
}
func animateIn() {
let convertedFrame = convertFrame(self.sourceNode.bounds, from: self.sourceNode.view, to: self.view)
self.sourceNode.contentNode.frame = convertedFrame
self.initialSourceFrame = convertedFrame
self.scrollingContainer.addSubnode(self.sourceNode.contentNode)
var updatedContentAreaInScreenSpace = self.getContentAreaInScreenSpace()
updatedContentAreaInScreenSpace.origin.x = 0.0
updatedContentAreaInScreenSpace.size.width = self.bounds.width
self.clippingNode.layer.animateFrame(from: updatedContentAreaInScreenSpace, to: self.clippingNode.frame, duration: 0.18 * 1.0, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue)
self.clippingNode.layer.animateBoundsOriginYAdditive(from: updatedContentAreaInScreenSpace.minY, to: 0.0, duration: 0.18 * 1.0, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue)
}
func animateOut(completion: @escaping () -> Void) {
self.isAnimatingOut = true
let performCompletion: () -> Void = { [weak self] in
guard let strongSelf = self else {
return
}
strongSelf.isAnimatingOut = false
strongSelf.sourceNode.restoreToNaturalSize()
strongSelf.sourceNode.addSubnode(strongSelf.sourceNode.contentNode)
strongSelf.sourceNode.animatedOut?()
completion()
}
let convertedFrame = convertFrame(self.sourceNode.bounds, from: self.sourceNode.view, to: self.view)
self.sourceNode.contentNode.frame = convertedFrame
self.initialSourceFrame = convertedFrame
if let (scale, pinchLocation, offset) = self.sourceNode.gesture.currentTransform, let initialSourceFrame = self.initialSourceFrame {
let duration = 0.3
let transitionCurve: ContainedViewLayoutTransitionCurve = .easeInOut
var updatedContentAreaInScreenSpace = self.getContentAreaInScreenSpace()
updatedContentAreaInScreenSpace.origin.x = 0.0
updatedContentAreaInScreenSpace.size.width = self.bounds.width
self.clippingNode.layer.animateFrame(from: self.clippingNode.frame, to: updatedContentAreaInScreenSpace, duration: duration * 1.0, timingFunction: transitionCurve.timingFunction, removeOnCompletion: false)
self.clippingNode.layer.animateBoundsOriginYAdditive(from: 0.0, to: updatedContentAreaInScreenSpace.minY, duration: duration * 1.0, timingFunction: transitionCurve.timingFunction, removeOnCompletion: false)
let transition: ContainedViewLayoutTransition = .animated(duration: duration, curve: .spring)
if self.hapticFeedback == nil {
self.hapticFeedback = HapticFeedback()
}
self.hapticFeedback?.prepareImpact(.light)
self.hapticFeedback?.impact(.light)
self.sourceNode.scaleUpdated?(1.0, transition)
let pinchOffset = CGPoint(
x: pinchLocation.x - initialSourceFrame.width / 2.0,
y: pinchLocation.y - initialSourceFrame.height / 2.0
)
var transform = CATransform3DIdentity
transform = CATransform3DScale(transform, scale, scale, 0.0)
self.sourceNode.contentNode.transform = CATransform3DIdentity
self.sourceNode.contentNode.position = CGPoint(x: initialSourceFrame.midX, y: initialSourceFrame.midY)
self.sourceNode.contentNode.layer.animateSpring(from: scale as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: duration * 1.2, damping: 110.0)
self.sourceNode.contentNode.layer.animatePosition(from: CGPoint(x: offset.x - pinchOffset.x * (scale - 1.0), y: offset.y - pinchOffset.y * (scale - 1.0)), to: CGPoint(), duration: duration, timingFunction: kCAMediaTimingFunctionSpring, additive: true, force: true, completion: { _ in
performCompletion()
})
let dimNodeTransition: ContainedViewLayoutTransition = .animated(duration: 0.3, curve: transitionCurve)
dimNodeTransition.updateAlpha(node: self.dimNode, alpha: 0.0)
} else {
performCompletion()
}
}
func addRelativeContentOffset(_ offset: CGPoint, transition: ContainedViewLayoutTransition) {
if self.isAnimatingOut {
self.scrollingContainer.bounds = self.scrollingContainer.bounds.offsetBy(dx: 0.0, dy: offset.y)
transition.animateOffsetAdditive(node: self.scrollingContainer, offset: -offset.y)
}
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
return nil
}
}
public final class PinchControllerImpl: ViewController, PinchController, StandalonePresentableController {
private let _ready = Promise<Bool>()
override public var ready: Promise<Bool> {
return self._ready
}
private let sourceNode: PinchSourceContainerNode
private let disableScreenshots: Bool
private let getContentAreaInScreenSpace: () -> CGRect
private var wasDismissed = false
private var controllerNode: PinchControllerNode {
return self.displayNode as! PinchControllerNode
}
public init(sourceNode: PinchSourceContainerNode, disableScreenshots: Bool = false, getContentAreaInScreenSpace: @escaping () -> CGRect) {
self.sourceNode = sourceNode
self.disableScreenshots = disableScreenshots
self.getContentAreaInScreenSpace = getContentAreaInScreenSpace
super.init(navigationBarPresentationData: nil)
self.statusBar.statusBarStyle = .Ignore
self.lockOrientation = true
self.blocksBackgroundWhenInOverlay = true
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
}
override public func loadDisplayNode() {
self.displayNode = PinchControllerNode(controller: self, sourceNode: self.sourceNode, disableScreenshots: self.disableScreenshots, getContentAreaInScreenSpace: self.getContentAreaInScreenSpace)
self.displayNodeDidLoad()
self._ready.set(.single(true))
}
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
super.containerLayoutUpdated(layout, transition: transition)
self.controllerNode.updateLayout(layout: layout, transition: transition, previousActionsContainerNode: nil)
}
override public func viewDidAppear(_ animated: Bool) {
if self.ignoreAppearanceMethodInvocations() {
return
}
super.viewDidAppear(animated)
self.controllerNode.animateIn()
}
override public func dismiss(completion: (() -> Void)? = nil) {
if !self.wasDismissed {
self.wasDismissed = true
self.controllerNode.animateOut(completion: { [weak self] in
self?.presentingViewController?.dismiss(animated: false, completion: nil)
completion?()
})
}
}
public func addRelativeContentOffset(_ offset: CGPoint, transition: ContainedViewLayoutTransition) {
self.controllerNode.addRelativeContentOffset(offset, transition: transition)
}
}
@@ -598,6 +598,84 @@ public final class VariableBlurView: UIView {
variableBlur.setValue("mask_source", forKey: "inputSourceSublayerName")
variableBlur.setValue(true, forKey: "inputNormalizeEdges")
mainEffectLayer.filters = [variableBlur]
/*#if DEBUG
if let classValue = NSClassFromString("CAFilter") as AnyObject as? NSObjectProtocol {
let makeSelector = NSSelectorFromString("filterWithName:")
let filter = classValue.perform(makeSelector, with: "colorMatrix").takeUnretainedValue() as? NSObject
if let filter {
/// Builds a 4x5 (RGBA) color matrix (20 floats, row-major).
/// Applies: Desaturate(s) then TintToward(t, k) in linear RGB.
/// - s: saturation in [0,1] (1 = unchanged, 0 = grayscale)
/// - k: tint strength in [0,1] (0 = none, 1 = fully "brightness->tint" mapping)
/// - t: tint color (linear RGB). Doesn't have to be normalized, but should be non-negative.
/// - bias: optional constant "cast" added in the bias column, scaled by tint color.
let makeDesatTintMatrix: (Float32, Float32, (Float32, Float32, Float32), Float32) -> [Float32] = { s, k, t, bias in
// Rec.709 luma weights (good default for linear RGB)
let wr: Float32 = 0.2126
let wg: Float32 = 0.7152
let wb: Float32 = 0.0722
let (tr, tg, tb) = t
// --- D = desaturation matrix ---
let a = 1 - s
let D00 = wr * a + s, D01 = wg * a, D02 = wb * a
let D10 = wr * a, D11 = wg * a + s, D12 = wb * a
let D20 = wr * a, D21 = wg * a, D22 = wb * a + s
// --- T = outer(t, w): maps luma to tinted RGB (preserves luma per weights) ---
let T00 = tr * wr, T01 = tr * wg, T02 = tr * wb
let T10 = tg * wr, T11 = tg * wg, T12 = tg * wb
let T20 = tb * wr, T21 = tb * wg, T22 = tb * wb
// --- A = (1-k)I + kT ---
let ik = 1 - k
let A00 = ik + k * T00, A01 = k * T01, A02 = k * T02
let A10 = k * T10, A11 = ik + k * T11, A12 = k * T12
let A20 = k * T20, A21 = k * T21, A22 = ik + k * T22
// --- M = A * D (3x3 multiply) ---
let M00 = A00 * D00 + A01 * D10 + A02 * D20
let M01 = A00 * D01 + A01 * D11 + A02 * D21
let M02 = A00 * D02 + A01 * D12 + A02 * D22
let M10 = A10 * D00 + A11 * D10 + A12 * D20
let M11 = A10 * D01 + A11 * D11 + A12 * D21
let M12 = A10 * D02 + A11 * D12 + A12 * D22
let M20 = A20 * D00 + A21 * D10 + A22 * D20
let M21 = A20 * D01 + A21 * D11 + A22 * D21
let M22 = A20 * D02 + A21 * D12 + A22 * D22
// Optional constant cast in bias column
let bR = bias * tr
let bG = bias * tg
let bB = bias * tb
// 4x5 row-major, alpha passthrough:
return [
M00, M01, M02, 0, bR,
M10, M11, M12, 0, bG,
M20, M21, M22, 0, bB,
0, 0, 0, 1, 0
]
}
var matrix: [Float32] = makeDesatTintMatrix(
0.1, // more desaturated
0.1, // moderate tinting
(0.0, 1.0, 0.0), // warm tint target
0.0 // set e.g. 0.03 for a gentle warm cast
)
filter.setValue(NSValue(bytes: &matrix, objCType: "{CAColorMatrix=ffffffffffffffffffff}"), forKey: "inputColorMatrix")
mainEffectLayer.filters = [variableBlur, filter]
}
}
#endif*/
}
} else {
self.additionalEffectLayer = createBackdropLayer()
@@ -48,6 +48,7 @@ swift_library(
"//submodules/TelegramUI/Components/Gifts/GiftLoadingShimmerView",
"//submodules/TelegramUI/Components/EdgeEffect",
"//submodules/TelegramUI/Components/GlassBackgroundComponent",
"//submodules/TelegramUI/Components/ContextControllerImpl",
],
visibility = [
"//visibility:public",
@@ -8,6 +8,7 @@ import TelegramCore
import AccountContext
import TelegramPresentationData
import ContextUI
import ContextControllerImpl
final class GiftAttributeListContextItem: ContextMenuCustomItem {
let context: AccountContext
@@ -511,7 +511,7 @@ final class GiftStoreScreenComponent: Component {
self.scrollToTop()
})))
let contextController = ContextController(presentationData: presentationData, source: .reference(GiftStoreReferenceContentSource(controller: controller, sourceView: sourceView)), items: .single(ContextController.Items(content: .list(items))), gesture: nil)
let contextController = makeContextController(presentationData: presentationData, source: .reference(GiftStoreReferenceContentSource(controller: controller, sourceView: sourceView)), items: .single(ContextController.Items(content: .list(items))), gesture: nil)
contextController.dismissed = { [weak self] in
guard let self else {
return
@@ -613,7 +613,7 @@ final class GiftStoreScreenComponent: Component {
}
), false))
let contextController = ContextController(
let contextController = makeContextController(
context: component.context,
presentationData: presentationData,
source: .reference(GiftStoreReferenceContentSource(controller: controller, sourceView: sourceView)),
@@ -721,7 +721,7 @@ final class GiftStoreScreenComponent: Component {
}
), false))
let contextController = ContextController(
let contextController = makeContextController(
context: component.context,
presentationData: presentationData,
source: .reference(GiftStoreReferenceContentSource(controller: controller, sourceView: sourceView)),
@@ -829,7 +829,7 @@ final class GiftStoreScreenComponent: Component {
}
), false))
let contextController = ContextController(
let contextController = makeContextController(
context: component.context,
presentationData: presentationData,
source: .reference(GiftStoreReferenceContentSource(controller: controller, sourceView: sourceView)),
@@ -1831,7 +1831,7 @@ private final class GiftAuctionBidScreenComponent: Component {
self?.share()
})))
let contextController = ContextController(presentationData: presentationData, source: .reference(GiftViewContextReferenceContentSource(controller: controller, sourceView: view)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
let contextController = makeContextController(presentationData: presentationData, source: .reference(GiftViewContextReferenceContentSource(controller: controller, sourceView: view)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
controller.presentInGlobalOverlay(contextController)
}
@@ -459,7 +459,7 @@ private final class GiftAuctionViewSheetContent: CombinedComponent {
self?.share()
})))
let contextController = ContextController(presentationData: presentationData, source: .reference(GiftViewContextReferenceContentSource(controller: controller, sourceView: view)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
let contextController = makeContextController(presentationData: presentationData, source: .reference(GiftViewContextReferenceContentSource(controller: controller, sourceView: view)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
controller.presentInGlobalOverlay(contextController)
}
@@ -1530,7 +1530,7 @@ private final class GiftViewSheetContent: CombinedComponent {
})))
}
let contextController = ContextController(presentationData: presentationData, source: .reference(GiftViewContextReferenceContentSource(controller: controller, sourceNode: node)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
let contextController = makeContextController(presentationData: presentationData, source: .reference(GiftViewContextReferenceContentSource(controller: controller, sourceNode: node)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
controller.presentInGlobalOverlay(contextController)
})
}
@@ -424,7 +424,9 @@ public class GlassBackgroundView: UIView {
nativeView.frame = CGRect(origin: CGPoint(), size: size)
} else {
let nativeFrame = CGRect(origin: CGPoint(), size: size)
transition.setFrame(view: nativeView, frame: nativeFrame)
transition.animateView {
nativeView.frame = nativeFrame
}
}
nativeView.overrideUserInterfaceStyle = isDark ? .dark : .light
}
@@ -544,7 +546,7 @@ public class GlassBackgroundView: UIView {
if transition.animation.isImmediate {
nativeView.effect = nil
} else {
UIView.animate {
transition.animateView {
nativeView.effect = nil
}
}
@@ -555,7 +557,7 @@ public class GlassBackgroundView: UIView {
} else {
if let glassEffect, let currentEffect = nativeView.effect as? UIGlassEffect, currentEffect.tintColor == glassEffect.tintColor, currentEffect.isInteractive == glassEffect.isInteractive {
} else {
UIView.animate {
transition.animateView {
nativeView.effect = glassEffect
}
}
@@ -680,7 +682,9 @@ public final class GlassBackgroundContainerView: UIView {
nativeParamsView.lumaMax = 0.801
}
transition.setFrame(view: nativeView, frame: CGRect(origin: CGPoint(), size: size))
transition.animateView {
nativeView.frame = CGRect(origin: CGPoint(), size: size)
}
} else if let legacyView = self.legacyView {
transition.setFrame(view: legacyView, frame: CGRect(origin: CGPoint(), size: size))
}
@@ -1082,3 +1086,136 @@ public final class GlassBackgroundComponent: Component {
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
}
}
public final class GlassContextExtractableContainer: UIView, ContextExtractableContainer {
private struct NormalParams {
let size: CGSize
let cornerRadius: CGFloat
let isDark: Bool
let tintColor: GlassBackgroundView.TintColor
let isInteractive: Bool
let isVisible: Bool
init(size: CGSize, cornerRadius: CGFloat, isDark: Bool, tintColor: GlassBackgroundView.TintColor, isInteractive: Bool, isVisible: Bool) {
self.size = size
self.cornerRadius = cornerRadius
self.isDark = isDark
self.tintColor = tintColor
self.isInteractive = isInteractive
self.isVisible = isVisible
}
}
public let extractableContentView: UIView
public let normalContentView: UIView
public var contentView: UIView {
return self.normalContentView
}
private let glassView: GlassBackgroundView
private var state: State = .normal
private var normalParams: NormalParams?
override public init(frame: CGRect) {
self.extractableContentView = UIView()
self.glassView = GlassBackgroundView()
self.normalContentView = SparseContainerView()
super.init(frame: frame)
self.glassView.contentView.addSubview(self.normalContentView)
self.extractableContentView.addSubview(self.glassView)
self.addSubview(self.extractableContentView)
}
required public init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if !self.isUserInteractionEnabled {
return nil
}
if self.isHidden {
return nil
}
if self.alpha == 0.0 {
return nil
}
switch self.state {
case .normal:
if let result = self.normalContentView.hitTest(self.convert(point, to: self.normalContentView), with: event) {
return result
}
case .extracted:
break
}
return nil
}
public func update(size: CGSize, cornerRadius: CGFloat, isDark: Bool, tintColor: GlassBackgroundView.TintColor, isInteractive: Bool = false, isVisible: Bool = true, transition: ComponentTransition) {
let normalParams = NormalParams(size: size, cornerRadius: cornerRadius, isDark: isDark, tintColor: tintColor, isInteractive: isInteractive, isVisible: isVisible)
self.normalParams = normalParams
if case .normal = self.state {
self.applyState(transition: transition)
}
}
public func updateState(state: State, transition: ContainedViewLayoutTransition) {
self.state = state
self.applyState(transition: ComponentTransition(transition))
}
private func applyState(transition: ComponentTransition) {
guard let normalParams = self.normalParams else {
return
}
switch self.state {
case .normal:
transition.setAlpha(view: self.normalContentView, alpha: 1.0)
transition.setFrame(view: self.extractableContentView, frame: CGRect(origin: CGPoint(), size: normalParams.size))
transition.setFrame(view: self.normalContentView, frame: CGRect(origin: CGPoint(), size: normalParams.size))
self.glassView.update(
size: normalParams.size,
cornerRadius: normalParams.cornerRadius,
isDark: normalParams.isDark,
tintColor: normalParams.tintColor,
isInteractive: normalParams.isInteractive,
isVisible: normalParams.isVisible,
transition: transition
)
case let .extracted(size, cornerRadius, extractionState):
switch extractionState {
case .animatedOut:
transition.setAlpha(view: self.normalContentView, alpha: 1.0)
self.glassView.update(
size: normalParams.size,
cornerRadius: normalParams.cornerRadius,
isDark: normalParams.isDark,
tintColor: normalParams.tintColor,
isInteractive: normalParams.isInteractive,
isVisible: normalParams.isVisible,
transition: transition
)
case .animatedIn:
transition.setAlpha(view: self.normalContentView, alpha: 0.0)
self.glassView.update(
size: size,
cornerRadius: cornerRadius,
isDark: normalParams.isDark,
tintColor: normalParams.tintColor,
isInteractive: normalParams.isInteractive,
isVisible: normalParams.isVisible,
transition: transition
)
}
}
}
}
@@ -435,7 +435,7 @@ public class LegacyMessageInputPanelNode: ASDisplayNode, TGCaptionPanelView {
updateTimeout(nil)
})))
let contextController = ContextController(presentationData: presentationData, source: .reference(HeaderContextReferenceContentSource(sourceView: sourceView, position: self.currentIsCaptionAbove ? .bottom : .top)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
let contextController = makeContextController(presentationData: presentationData, source: .reference(HeaderContextReferenceContentSource(sourceView: sourceView, position: self.currentIsCaptionAbove ? .bottom : .top)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
self.present(contextController)
}
@@ -70,6 +70,7 @@ swift_library(
"//submodules/TelegramNotices",
"//submodules/TelegramUI/Components/AttachmentFileController",
"//submodules/SaveToCameraRoll",
"//submodules/TelegramUI/Components/ContextControllerImpl",
],
visibility = [
"//visibility:public",
@@ -25,7 +25,7 @@ func presentLinkOptionsController(context: AccountContext, selfController: Creat
return
}
let contextController = ContextController(
let contextController = makeContextController(
presentationData: context.sharedContext.currentPresentationData.with { $0 }.withUpdated(theme: defaultDarkColorPresentationTheme),
configuration: ContextController.Configuration(
sources: sources,
@@ -5406,7 +5406,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
}
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }.withUpdated(theme: defaultDarkPresentationTheme)
let contextController = ContextController(presentationData: presentationData, source: .reference(ReferenceContentSource(sourceView: sourceView, contentArea: UIScreen.main.bounds, customPosition: CGPoint(x: 0.0, y: -3.0))), items: .single(ContextController.Items(content: .list(items))))
let contextController = makeContextController(presentationData: presentationData, source: .reference(ReferenceContentSource(sourceView: sourceView, contentArea: UIScreen.main.bounds, customPosition: CGPoint(x: 0.0, y: -3.0))), items: .single(ContextController.Items(content: .list(items))))
self.controller?.present(contextController, in: .window(.root))
}
@@ -7224,7 +7224,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
self?.node.presentAudioPicker()
})))
let contextController = ContextController(presentationData: presentationData, source: .reference(HeaderContextReferenceContentSource(controller: self, sourceView: sourceView)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
let contextController = makeContextController(presentationData: presentationData, source: .reference(HeaderContextReferenceContentSource(controller: self, sourceView: sourceView)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
self.present(contextController, in: .window(.root))
}
@@ -7300,7 +7300,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
)))
}
let contextController = ContextController(presentationData: presentationData, source: .reference(HeaderContextReferenceContentSource(controller: self, sourceView: sourceView)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
let contextController = makeContextController(presentationData: presentationData, source: .reference(HeaderContextReferenceContentSource(controller: self, sourceView: sourceView)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
self.present(contextController, in: .window(.root))
}
@@ -7916,7 +7916,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
portalView.view.layer.rasterizationScale = UIScreenScale
self.node.previewContentContainerView.addPortal(view: portalView)
let stickerResultController = PeekController(
let stickerResultController = makePeekController(
presentationData: presentationData,
content: StickerPreviewPeekContent(
context: self.context,
@@ -9,6 +9,7 @@ import AccountContext
import TelegramPresentationData
import StickerResources
import ContextUI
import ContextControllerImpl
final class StickerPackListContextItem: ContextMenuCustomItem {
let context: AccountContext
@@ -586,7 +586,7 @@ public final class MediaNavigationAccessoryHeaderNode: ASDisplayNode, ASScrollVi
let items = self.contextMenuSpeedItems(scheduleTooltip: { change in
scheduledTooltip = change
})
let contextController = ContextController(presentationData: self.context.sharedContext.currentPresentationData.with { $0 }, source: .reference(HeaderContextReferenceContentSource(controller: controller, sourceNode: self.rateButton.referenceNode, shouldBeDismissed: self.dismissedPromise.get())), items: items, gesture: gesture)
let contextController = makeContextController(presentationData: self.context.sharedContext.currentPresentationData.with { $0 }, source: .reference(HeaderContextReferenceContentSource(controller: controller, sourceNode: self.rateButton.referenceNode, shouldBeDismissed: self.dismissedPromise.get())), items: items, gesture: gesture)
contextController.dismissed = { [weak self] in
if let scheduledTooltip, let self, let rate = self.playbackBaseRate {
self.setRate?(rate, scheduledTooltip)
@@ -290,7 +290,7 @@ final class StickersResultPanelComponent: Component {
}, present: { [weak self] content, sourceView, sourceRect in
if let self, let component = self.component {
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }.withUpdated(theme: component.theme)
let controller = PeekController(presentationData: presentationData, content: content, sourceView: {
let controller = makePeekController(presentationData: presentationData, content: content, sourceView: {
return (sourceView, sourceRect)
})
component.presentInGlobalOverlay(controller)
@@ -550,8 +550,8 @@ public final class NavigationBarImpl: ASDisplayNode, NavigationBar {
public let backgroundNode: NavigationBackgroundNode
private var leftButtonsBackgroundView: (background: GlassBackgroundView, container: UIView)?
private var rightButtonsBackgroundView: (background: GlassBackgroundView, container: UIView)?
private var leftButtonsBackgroundView: (background: GlassContextExtractableContainer, container: UIView)?
private var rightButtonsBackgroundView: (background: GlassContextExtractableContainer, container: UIView)?
private let backButtonNodeImpl: NavigationButtonNodeImpl
public var backButtonNode: NavigationButtonNode {
@@ -662,12 +662,12 @@ public final class NavigationBarImpl: ASDisplayNode, NavigationBar {
backgroundContainer.contentView.addSubview(self.customOverBackgroundContentView)
let leftButtonsBackgroundView: (background: GlassBackgroundView, container: UIView) = (GlassBackgroundView(), UIView())
let leftButtonsBackgroundView: (background: GlassContextExtractableContainer, container: UIView) = (GlassContextExtractableContainer(), UIView())
leftButtonsBackgroundView.background.contentView.addSubview(leftButtonsBackgroundView.container)
self.leftButtonsBackgroundView = leftButtonsBackgroundView
backgroundContainer.contentView.addSubview(leftButtonsBackgroundView.background)
let rightButtonsBackgroundView: (background: GlassBackgroundView, container: UIView) = (GlassBackgroundView(), UIView())
let rightButtonsBackgroundView: (background: GlassContextExtractableContainer, container: UIView) = (GlassContextExtractableContainer(), UIView())
rightButtonsBackgroundView.background.contentView.addSubview(rightButtonsBackgroundView.container)
self.rightButtonsBackgroundView = rightButtonsBackgroundView
backgroundContainer.contentView.addSubview(rightButtonsBackgroundView.background)
@@ -1062,6 +1062,16 @@ public final class NavigationBarImpl: ASDisplayNode, NavigationBar {
}
}
public func navigationButtonContextContainer(sourceView: UIView) -> ContextExtractableContainer? {
if let leftButtonsBackgroundView = self.leftButtonsBackgroundView, sourceView.isDescendant(of: leftButtonsBackgroundView.background) {
return leftButtonsBackgroundView.background
}
if let rightButtonsBackgroundView = self.rightButtonsBackgroundView, sourceView.isDescendant(of: rightButtonsBackgroundView.background) {
return rightButtonsBackgroundView.background
}
return nil
}
public var intrinsicCanTransitionInline: Bool = true
public var passthroughTouches = true
@@ -505,7 +505,7 @@ final class AffiliateProgramSetupScreenComponent: Component {
}
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 })
let contextController = ContextController(presentationData: presentationData, source: .reference(HeaderContextReferenceContentSource(controller: controller, sourceView: sourceView, actionsOnTop: false)), items: .single(ContextController.Items(id: AnyHashable(0), content: .list(items))), gesture: nil)
let contextController = makeContextController(presentationData: presentationData, source: .reference(HeaderContextReferenceContentSource(controller: controller, sourceView: sourceView, actionsOnTop: false)), items: .single(ContextController.Items(id: AnyHashable(0), content: .list(items))), gesture: nil)
controller.presentInGlobalOverlay(contextController)
}
@@ -1343,7 +1343,7 @@ final class AffiliateProgramSetupScreenComponent: Component {
let items = ContextController.Items(content: .list(itemList))
let controller = ContextController(
let controller = makeContextController(
presentationData: presentationData,
source: .extracted(ListContextExtractedContentSource(contentView: sourceView)),
items: .single(items),
@@ -450,7 +450,7 @@ private final class JoinAffiliateProgramScreenComponent: Component {
})))
}
let contextController = ContextController(presentationData: presentationData, source: .reference(HeaderContextReferenceContentSource(controller: controller, sourceView: sourceView, actionsOnTop: true)), items: .single(ContextController.Items(id: AnyHashable(0), content: .list(items))), gesture: nil)
let contextController = makeContextController(presentationData: presentationData, source: .reference(HeaderContextReferenceContentSource(controller: controller, sourceView: sourceView, actionsOnTop: true)), items: .single(ContextController.Items(id: AnyHashable(0), content: .list(items))), gesture: nil)
controller.presentInGlobalOverlay(contextController)
}

Some files were not shown because too many files have changed in this diff Show More