Shared media update

This commit is contained in:
Ali 2021-10-16 02:20:46 +04:00
parent 074df6febf
commit c0fb52283d
14 changed files with 1957 additions and 382 deletions

View File

@ -11,3 +11,18 @@ final class EscapeGuard {
self.status.isDeallocated = true
}
}
public final class EscapeNotification: NSObject {
let deallocated: () -> Void
public init(_ deallocated: @escaping () -> Void) {
self.deallocated = deallocated
}
deinit {
self.deallocated()
}
public func keep() {
}
}

View File

@ -17,9 +17,11 @@ public protocol ContextActionNodeProtocol: ASDisplayNode {
}
final class ContextActionNode: ASDisplayNode, ContextActionNodeProtocol {
private let action: ContextMenuActionItem
private var presentationData: PresentationData
private var action: ContextMenuActionItem
private let getController: () -> ContextControllerProtocol?
private let actionSelected: (ContextMenuActionResult) -> Void
private let requestLayout: () -> Void
private let backgroundNode: ASDisplayNode
private let highlightedBackgroundNode: ASDisplayNode
@ -38,10 +40,12 @@ final class ContextActionNode: ASDisplayNode, ContextActionNodeProtocol {
return true
}
init(presentationData: PresentationData, action: ContextMenuActionItem, getController: @escaping () -> ContextControllerProtocol?, actionSelected: @escaping (ContextMenuActionResult) -> Void) {
init(presentationData: PresentationData, action: ContextMenuActionItem, getController: @escaping () -> ContextControllerProtocol?, actionSelected: @escaping (ContextMenuActionResult) -> Void, requestLayout: @escaping () -> Void) {
self.presentationData = presentationData
self.action = action
self.getController = getController
self.actionSelected = actionSelected
self.requestLayout = requestLayout
let textFont = Font.regular(presentationData.listsFontSize.baseDisplaySize)
@ -267,6 +271,8 @@ final class ContextActionNode: ASDisplayNode, ContextActionNodeProtocol {
}
func updateTheme(presentationData: PresentationData) {
self.presentationData = presentationData
self.backgroundNode.backgroundColor = presentationData.theme.contextMenu.itemBackgroundColor
self.highlightedBackgroundNode.backgroundColor = presentationData.theme.contextMenu.itemHighlightedBackgroundColor
@ -315,9 +321,45 @@ final class ContextActionNode: ASDisplayNode, ContextActionNodeProtocol {
guard let controller = self.getController() else {
return
}
self.action.action?(controller, { [weak self] result in
self?.actionSelected(result)
})
self.action.action?(ContextMenuActionItem.Action(
controller: controller,
dismissWithResult: { [weak self] result in
self?.actionSelected(result)
},
updateAction: { [weak self] updatedAction in
guard let strongSelf = self else {
return
}
strongSelf.action = updatedAction
let textColor: UIColor
switch strongSelf.action.textColor {
case .primary:
textColor = strongSelf.presentationData.theme.contextMenu.primaryColor
case .destructive:
textColor = strongSelf.presentationData.theme.contextMenu.destructiveColor
case .disabled:
textColor = strongSelf.presentationData.theme.contextMenu.primaryColor.withMultipliedAlpha(0.4)
}
let textFont = Font.regular(strongSelf.presentationData.listsFontSize.baseDisplaySize)
let titleFont: UIFont
switch strongSelf.action.textFont {
case .regular:
titleFont = textFont
case let .custom(customFont):
titleFont = customFont
}
strongSelf.textNode.attributedText = NSAttributedString(string: strongSelf.action.text, font: titleFont, textColor: textColor)
if strongSelf.action.iconSource == nil {
strongSelf.iconNode.image = strongSelf.action.icon(strongSelf.presentationData.theme)
}
strongSelf.requestLayout()
}
))
}
func setIsHighlighted(_ value: Bool) {

View File

@ -69,7 +69,7 @@ private final class InnerActionsContainerNode: ASDisplayNode {
}
}
init(presentationData: PresentationData, items: [ContextMenuItem], getController: @escaping () -> ContextControllerProtocol?, actionSelected: @escaping (ContextMenuActionResult) -> Void, feedbackTap: @escaping () -> Void, blurBackground: Bool) {
init(presentationData: PresentationData, items: [ContextMenuItem], getController: @escaping () -> ContextControllerProtocol?, actionSelected: @escaping (ContextMenuActionResult) -> Void, requestLayout: @escaping () -> Void, feedbackTap: @escaping () -> Void, blurBackground: Bool) {
self.presentationData = presentationData
self.feedbackTap = feedbackTap
self.blurBackground = blurBackground
@ -83,7 +83,7 @@ private final class InnerActionsContainerNode: ASDisplayNode {
for i in 0 ..< items.count {
switch items[i] {
case let .action(action):
itemNodes.append(.action(ContextActionNode(presentationData: presentationData, action: action, getController: getController, actionSelected: actionSelected)))
itemNodes.append(.action(ContextActionNode(presentationData: presentationData, action: action, getController: getController, actionSelected: actionSelected, requestLayout: requestLayout)))
if i != items.count - 1 {
switch items[i + 1] {
case .action, .custom:
@ -469,7 +469,7 @@ final class ContextActionsContainerNode: ASDisplayNode {
return self.additionalActionsNode != nil
}
init(presentationData: PresentationData, items: ContextController.Items, getController: @escaping () -> ContextControllerProtocol?, actionSelected: @escaping (ContextMenuActionResult) -> Void, feedbackTap: @escaping () -> Void, blurBackground: Bool) {
init(presentationData: PresentationData, items: ContextController.Items, getController: @escaping () -> ContextControllerProtocol?, actionSelected: @escaping (ContextMenuActionResult) -> Void, requestLayout: @escaping () -> Void, feedbackTap: @escaping () -> Void, blurBackground: Bool) {
self.blurBackground = blurBackground
self.shadowNode = ASImageNode()
self.shadowNode.displaysAsynchronously = false
@ -488,14 +488,14 @@ final class ContextActionsContainerNode: ASDisplayNode {
additionalShadowNode.isHidden = true
self.additionalShadowNode = additionalShadowNode
self.additionalActionsNode = InnerActionsContainerNode(presentationData: presentationData, items: [firstItem], getController: getController, actionSelected: actionSelected, feedbackTap: feedbackTap, blurBackground: blurBackground)
self.additionalActionsNode = InnerActionsContainerNode(presentationData: presentationData, items: [firstItem], getController: getController, actionSelected: actionSelected, requestLayout: requestLayout, feedbackTap: feedbackTap, blurBackground: blurBackground)
items.items.removeFirst()
} else {
self.additionalShadowNode = nil
self.additionalActionsNode = nil
}
self.actionsNode = InnerActionsContainerNode(presentationData: presentationData, items: items.items, getController: getController, actionSelected: actionSelected, feedbackTap: feedbackTap, blurBackground: blurBackground)
self.actionsNode = InnerActionsContainerNode(presentationData: presentationData, items: items.items, getController: getController, actionSelected: actionSelected, requestLayout: requestLayout, feedbackTap: feedbackTap, blurBackground: blurBackground)
if let tip = items.tip {
let textSelectionTipNode = InnerTextSelectionTipContainerNode(presentationData: presentationData, tip: tip)
textSelectionTipNode.isUserInteractionEnabled = false

View File

@ -68,6 +68,18 @@ public struct ContextMenuActionBadge {
}
public final class ContextMenuActionItem {
public final class Action {
public let controller: ContextControllerProtocol
public let dismissWithResult: (ContextMenuActionResult) -> Void
public let updateAction: (ContextMenuActionItem) -> Void
init(controller: ContextControllerProtocol, dismissWithResult: @escaping (ContextMenuActionResult) -> Void, updateAction: @escaping (ContextMenuActionItem) -> Void) {
self.controller = controller
self.dismissWithResult = dismissWithResult
self.updateAction = updateAction
}
}
public let text: String
public let textColor: ContextMenuActionItemTextColor
public let textFont: ContextMenuActionItemFont
@ -75,9 +87,44 @@ public final class ContextMenuActionItem {
public let badge: ContextMenuActionBadge?
public let icon: (PresentationTheme) -> UIImage?
public let iconSource: ContextMenuActionItemIconSource?
public let action: ((ContextControllerProtocol, @escaping (ContextMenuActionResult) -> Void) -> Void)?
public let action: ((Action) -> Void)?
public init(text: String, textColor: ContextMenuActionItemTextColor = .primary, textLayout: ContextMenuActionItemTextLayout = .twoLinesMax, textFont: ContextMenuActionItemFont = .regular, badge: ContextMenuActionBadge? = nil, icon: @escaping (PresentationTheme) -> UIImage?, iconSource: ContextMenuActionItemIconSource? = nil, action: ((ContextControllerProtocol, @escaping (ContextMenuActionResult) -> Void) -> Void)?) {
convenience public init(
text: String,
textColor: ContextMenuActionItemTextColor = .primary,
textLayout: ContextMenuActionItemTextLayout = .twoLinesMax,
textFont: ContextMenuActionItemFont = .regular,
badge: ContextMenuActionBadge? = nil,
icon: @escaping (PresentationTheme) -> UIImage?,
iconSource: ContextMenuActionItemIconSource? = nil,
action: ((ContextControllerProtocol, @escaping (ContextMenuActionResult) -> Void) -> Void)?
) {
self.init(
text: text,
textColor: textColor,
textLayout: textLayout,
textFont: textFont,
badge: badge,
icon: icon,
iconSource: iconSource,
action: action.flatMap { action in
return { impl in
action(impl.controller, impl.dismissWithResult)
}
}
)
}
public init(
text: String,
textColor: ContextMenuActionItemTextColor = .primary,
textLayout: ContextMenuActionItemTextLayout = .twoLinesMax,
textFont: ContextMenuActionItemFont = .regular,
badge: ContextMenuActionBadge? = nil,
icon: @escaping (PresentationTheme) -> UIImage?,
iconSource: ContextMenuActionItemIconSource? = nil,
action: ((Action) -> Void)?
) {
self.text = text
self.textColor = textColor
self.textFont = textFont
@ -215,6 +262,7 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
self.contentContainerNode = ContextContentContainerNode()
var feedbackTap: (() -> Void)?
var updateLayout: (() -> Void)?
var blurBackground = true
if case .reference = source {
@ -228,6 +276,8 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
return controller
}, actionSelected: { result in
beginDismiss(result)
}, requestLayout: {
updateLayout?()
}, feedbackTap: {
feedbackTap?()
}, blurBackground: blurBackground)
@ -237,6 +287,10 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
feedbackTap = { [weak self] in
self?.hapticFeedback.tap()
}
updateLayout = { [weak self] in
self?.updateLayout()
}
self.scrollNode.view.delegate = self
@ -1059,6 +1113,8 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
return self?.getController()
}, actionSelected: { [weak self] result in
self?.beginDismiss(result)
}, requestLayout: { [weak self] in
self?.updateLayout()
}, feedbackTap: { [weak self] in
self?.hapticFeedback.tap()
}, blurBackground: self.blurBackground)
@ -1086,6 +1142,12 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
self.updateLayout(layout: validLayout, transition: .immediate, previousActionsContainerNode: nil, previousActionsContainerFrame: nil)
}
}
func updateLayout() {
if let layout = self.validLayout {
self.updateLayout(layout: layout, transition: .immediate, previousActionsContainerNode: nil)
}
}
func updateLayout(layout: ContainerViewLayout, transition: ContainedViewLayoutTransition, previousActionsContainerNode: ContextActionsContainerNode?, previousActionsContainerFrame: CGRect? = nil, previousActionsTransition: ContextController.PreviousActionsTransition = .scale) {
if self.isAnimatingOut {
@ -1573,10 +1635,13 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
return nil
}
let mappedPoint = self.view.convert(point, to: self.scrollNode.view)
var maybePassthrough = false
if let maybeContentNode = self.contentContainerNode.contentNode {
switch maybeContentNode {
case .reference:
break
if let controller = self.getController() as? ContextController {
maybePassthrough = controller.passthroughTouchEvents
}
case let .extracted(contentParentNode, _):
if case let .extracted(source) = self.source {
if !source.ignoreContentTouches {
@ -1615,6 +1680,11 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
if self.actionsContainerNode.frame.contains(mappedPoint) {
return self.actionsContainerNode.hitTest(self.view.convert(point, to: self.actionsContainerNode.view), with: event)
}
if maybePassthrough {
self.getController()?.dismiss(completion: nil)
return nil
}
return self.dismissNode.view
}
@ -1776,6 +1846,8 @@ public final class ContextController: ViewController, StandalonePresentableContr
public var useComplexItemsTransitionAnimation = false
public var immediateItemsTransitionAnimation = false
public var passthroughTouchEvents = false
private var shouldBeDismissedDisposable: Disposable?

View File

@ -72,10 +72,13 @@ final class PeekControllerNode: ViewControllerTracingNode {
var feedbackTapImpl: (() -> Void)?
var activatedActionImpl: (() -> Void)?
var requestLayoutImpl: (() -> Void)?
self.actionsContainerNode = ContextActionsContainerNode(presentationData: presentationData, items: ContextController.Items(items: content.menuItems()), getController: { [weak controller] in
return controller
}, actionSelected: { result in
activatedActionImpl?()
}, requestLayout: {
requestLayoutImpl?()
}, feedbackTap: {
feedbackTapImpl?()
}, blurBackground: true)
@ -86,6 +89,10 @@ final class PeekControllerNode: ViewControllerTracingNode {
feedbackTapImpl = { [weak self] in
self?.hapticFeedback.tap()
}
requestLayoutImpl = { [weak self] in
self?.updateLayout()
}
if content.presentation() == .freeform {
self.containerNode.isUserInteractionEnabled = false
@ -118,6 +125,12 @@ final class PeekControllerNode: ViewControllerTracingNode {
self.dimNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimNodeTap(_:))))
self.view.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(self.panGesture(_:))))
}
func updateLayout() {
if let layout = self.validLayout {
self.containerLayoutUpdated(layout, transition: .immediate)
}
}
func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
self.validLayout = layout
@ -332,6 +345,8 @@ final class PeekControllerNode: ViewControllerTracingNode {
return self?.controller
}, actionSelected: { [weak self] result in
self?.requestDismiss()
}, requestLayout: { [weak self] in
self?.updateLayout()
}, feedbackTap: { [weak self] in
self?.hapticFeedback.tap()
}, blurBackground: true)

View File

@ -0,0 +1,23 @@
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
swift_library(
name = "DirectMediaImageCache",
module_name = "DirectMediaImageCache",
srcs = glob([
"Sources/**/*.swift",
]),
copts = [
"-warnings-as-errors",
],
deps = [
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
"//submodules/Postbox:Postbox",
"//submodules/TelegramCore:TelegramCore",
"//submodules/TinyThumbnail:TinyThumbnail",
"//submodules/Display:Display",
"//submodules/FastBlur:FastBlur",
],
visibility = [
"//visibility:public",
],
)

View File

@ -0,0 +1,134 @@
import Foundation
import SwiftSignalKit
import Postbox
import TelegramCore
import UIKit
import TinyThumbnail
import Display
import FastBlur
private func generateBlurredThumbnail(image: UIImage) -> UIImage? {
let thumbnailContextSize = CGSize(width: 32.0, height: 32.0)
let thumbnailContext = DrawingContext(size: thumbnailContextSize, scale: 1.0)
let filledSize = image.size.aspectFilled(thumbnailContextSize)
let imageRect = CGRect(origin: CGPoint(x: (thumbnailContextSize.width - filledSize.width) / 2.0, y: (thumbnailContextSize.height - filledSize.height) / 2.0), size: filledSize)
thumbnailContext.withFlippedContext { c in
c.draw(image.cgImage!, in: imageRect)
}
telegramFastBlurMore(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes)
return thumbnailContext.generateImage()
}
public final class DirectMediaImageCache {
public final class GetMediaResult {
public let image: UIImage?
public let loadSignal: Signal<UIImage?, NoError>?
init(image: UIImage?, loadSignal: Signal<UIImage?, NoError>?) {
self.image = image
self.loadSignal = loadSignal
}
}
private enum ImageType {
case blurredThumbnail
case square(width: Int)
}
private let account: Account
public init(account: Account) {
self.account = account
}
private func getCachePath(resourceId: MediaResourceId, imageType: ImageType) -> String {
let representationId: String
switch imageType {
case .blurredThumbnail:
representationId = "blurred32"
case let .square(width):
representationId = "shm\(width)"
}
return self.account.postbox.mediaBox.cachedRepresentationPathForId(resourceId.stringRepresentation, representationId: representationId, keepDuration: .general)
}
private func getLoadSignal(resource: MediaResourceReference, width: Int) -> Signal<UIImage?, NoError>? {
let cachePath = self.getCachePath(resourceId: resource.resource.id, imageType: .square(width: width))
return Signal { subscriber in
let fetch = fetchedMediaResource(mediaBox: self.account.postbox.mediaBox, reference: resource).start()
let data = (self.account.postbox.mediaBox.resourceData(resource.resource)
|> filter { data in
return data.complete
}
|> take(1)).start(next: { data in
if let dataValue = try? Data(contentsOf: URL(fileURLWithPath: data.path)), let image = UIImage(data: dataValue) {
if let scaledImage = generateImage(CGSize(width: CGFloat(width), height: CGFloat(width)), contextGenerator: { size, context in
let filledSize = image.size.aspectFilled(size)
let imageRect = CGRect(origin: CGPoint(x: (size.width - filledSize.width) / 2.0, y: (size.height - filledSize.height) / 2.0), size: filledSize)
context.draw(image.cgImage!, in: imageRect)
}, scale: 1.0) {
if let resultData = scaledImage.jpegData(compressionQuality: 0.7) {
let _ = try? resultData.write(to: URL(fileURLWithPath: cachePath))
subscriber.putNext(scaledImage)
subscriber.putCompletion()
}
}
}
})
return ActionDisposable {
fetch.dispose()
data.dispose()
}
}
}
private func getResource(message: Message, image: TelegramMediaImage) -> MediaResourceReference? {
guard let representation = image.representations.last else {
return nil
}
return MediaReference.message(message: MessageReference(message), media: image).resourceReference(representation.resource)
}
private func getResource(message: Message, file: TelegramMediaFile) -> MediaResourceReference? {
if let representation = file.previewRepresentations.last {
return MediaReference.message(message: MessageReference(message), media: file).resourceReference(representation.resource)
} else {
return nil
}
}
public func getImage(message: Message, media: Media, width: Int) -> GetMediaResult? {
var immediateThumbnailData: Data?
var resource: MediaResourceReference?
if let image = media as? TelegramMediaImage {
immediateThumbnailData = image.immediateThumbnailData
resource = self.getResource(message: message, image: image)
} else if let file = media as? TelegramMediaFile {
immediateThumbnailData = file.immediateThumbnailData
resource = self.getResource(message: message, file: file)
}
if let resource = resource {
if let data = try? Data(contentsOf: URL(fileURLWithPath: self.getCachePath(resourceId: resource.resource.id, imageType: .square(width: width)))), let image = UIImage(data: data) {
return GetMediaResult(image: image, loadSignal: nil)
}
var blurredImage: UIImage?
if let data = try? Data(contentsOf: URL(fileURLWithPath: self.getCachePath(resourceId: resource.resource.id, imageType: .blurredThumbnail))), let image = UIImage(data: data) {
blurredImage = image
} else if let data = immediateThumbnailData.flatMap(decodeTinyThumbnail), let image = UIImage(data: data) {
if let blurredImageValue = generateBlurredThumbnail(image: image) {
blurredImage = blurredImageValue
}
}
return GetMediaResult(image: blurredImage, loadSignal: self.getLoadSignal(resource: resource, width: width))
} else {
return nil
}
}
}

View File

@ -865,6 +865,36 @@ public extension ContainedViewLayoutTransition {
})
}
}
func updateTransform(node: ASDisplayNode, transform: CGAffineTransform, beginWithCurrentState: Bool = false, delay: Double = 0.0, completion: ((Bool) -> Void)? = nil) {
let transform = CATransform3DMakeAffineTransform(transform)
if CATransform3DEqualToTransform(node.layer.transform, transform) {
if let completion = completion {
completion(true)
}
return
}
switch self {
case .immediate:
node.layer.transform = transform
if let completion = completion {
completion(true)
}
case let .animated(duration, curve):
let previousTransform: CATransform3D
if beginWithCurrentState, let presentation = node.layer.presentation() {
previousTransform = presentation.transform
} else {
previousTransform = node.layer.transform
}
node.layer.transform = transform
node.layer.animate(from: NSValue(caTransform3D: previousTransform), to: NSValue(caTransform3D: transform), keyPath: "transform", timingFunction: curve.timingFunction, duration: duration, mediaTimingFunction: curve.mediaTimingFunction, completion: { value in
completion?(value)
})
}
}
func updateTransformScale(node: ASDisplayNode, scale: CGFloat, beginWithCurrentState: Bool = false, delay: Double = 0.0, completion: ((Bool) -> Void)? = nil) {
let t = node.layer.transform

File diff suppressed because it is too large Load Diff

View File

@ -83,7 +83,6 @@ public final class SparseMessageList {
}
private let loadHoleDisposable = MetaDisposable()
private var loadingHole: LoadingHole?
private var scheduledLoadingHole: LoadingHole?
private var loadingPlaceholders: [MessageId: Disposable] = [:]
private var loadedPlaceholders: [MessageId: Message] = [:]
@ -169,7 +168,13 @@ public final class SparseMessageList {
}
private func resetTopSection() {
self.topItemsDisposable.set((self.account.postbox.aroundMessageHistoryViewForLocation(.peer(peerId), anchor: .upperBound, count: 200, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: Set(), tagMask: self.messageTag, appendMessagesFromTheSameGroup: false, namespaces: .not(Set(Namespaces.Message.allScheduled)), orderStatistics: [])
let count: Int
#if DEBUG
count = 20
#else
count = 200
#endif
self.topItemsDisposable.set((self.account.postbox.aroundMessageHistoryViewForLocation(.peer(peerId), anchor: .upperBound, count: count, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: Set(), tagMask: self.messageTag, appendMessagesFromTheSameGroup: false, namespaces: .not(Set(Namespaces.Message.allScheduled)), orderStatistics: [])
|> deliverOn(self.queue)).start(next: { [weak self] view, updateType, _ in
guard let strongSelf = self else {
return
@ -337,14 +342,15 @@ public final class SparseMessageList {
})
}
func loadHole(anchor: MessageId, direction: LoadHoleDirection) {
func loadHole(anchor: MessageId, direction: LoadHoleDirection, completion: @escaping () -> Void) {
let loadingHole = LoadingHole(anchor: anchor, direction: direction)
if self.loadingHole == loadingHole {
completion()
return
}
if self.loadingHole != nil {
self.scheduledLoadingHole = loadingHole
completion()
return
}
@ -370,6 +376,7 @@ public final class SparseMessageList {
}
|> deliverOn(self.queue)).start(next: { [weak self] messages in
guard let strongSelf = self else {
completion()
return
}
@ -490,12 +497,9 @@ public final class SparseMessageList {
if strongSelf.loadingHole == loadingHole {
strongSelf.loadingHole = nil
if let scheduledLoadingHole = strongSelf.scheduledLoadingHole {
strongSelf.scheduledLoadingHole = nil
strongSelf.loadHole(anchor: scheduledLoadingHole.anchor, direction: scheduledLoadingHole.direction)
}
}
completion()
}))
}
@ -634,9 +638,9 @@ public final class SparseMessageList {
}
}*/
public func loadHole(anchor: MessageId, direction: LoadHoleDirection) {
public func loadHole(anchor: MessageId, direction: LoadHoleDirection, completion: @escaping () -> Void) {
self.impl.with { impl in
impl.loadHole(anchor: anchor, direction: direction)
impl.loadHole(anchor: anchor, direction: direction, completion: completion)
}
}
}

View File

@ -245,6 +245,7 @@ swift_library(
"//submodules/CalendarMessageScreen:CalendarMessageScreen",
"//submodules/LottieMeshSwift:LottieMeshSwift",
"//submodules/MeshAnimationCache:MeshAnimationCache",
"//submodules/DirectMediaImageCache:DirectMediaImageCache",
] + select({
"@build_bazel_rules_apple//apple:ios_armv7": [],
"@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets,

View File

@ -386,7 +386,8 @@ private final class PeerInfoPendingPane {
key: PeerInfoPaneKey,
hasBecomeReady: @escaping (PeerInfoPaneKey) -> Void,
parentController: ViewController?,
openMediaCalendar: @escaping () -> Void
openMediaCalendar: @escaping () -> Void,
paneDidScroll: @escaping () -> Void
) {
let paneNode: PeerInfoPaneNode
switch key {
@ -396,6 +397,9 @@ private final class PeerInfoPendingPane {
visualPaneNode.openCurrentDate = {
openMediaCalendar()
}
visualPaneNode.paneDidScroll = {
paneDidScroll()
}
case .files:
paneNode = PeerInfoListPaneNode(context: context, updatedPresentationData: updatedPresentationData, chatControllerInteraction: chatControllerInteraction, peerId: peerId, tagMask: .file)
case .links:
@ -410,6 +414,9 @@ private final class PeerInfoPendingPane {
visualPaneNode.openCurrentDate = {
openMediaCalendar()
}
visualPaneNode.paneDidScroll = {
paneDidScroll()
}
case .groupsInCommon:
paneNode = PeerInfoGroupsInCommonPaneNode(context: context, peerId: peerId, chatControllerInteraction: chatControllerInteraction, openPeerContextAction: openPeerContextAction, groupsInCommonContext: data.groupsInCommon!)
case .members:
@ -478,6 +485,7 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, UIGestureRecognizerDelegat
var requestExpandTabs: (() -> Bool)?
var openMediaCalendar: (() -> Void)?
var paneDidScroll: (() -> Void)?
private var currentAvailablePanes: [PeerInfoPaneKey]?
private let updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?
@ -779,6 +787,9 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, UIGestureRecognizerDelegat
parentController: self.parentController,
openMediaCalendar: { [weak self] in
self?.openMediaCalendar?()
},
paneDidScroll: { [weak self] in
self?.paneDidScroll?()
}
)
self.pendingPanes[key] = pane

View File

@ -2310,6 +2310,16 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
}
strongSelf.openMediaCalendar()
}
self.paneContainerNode.paneDidScroll = { [weak self] in
guard let strongSelf = self else {
return
}
if let mediaGalleryContextMenu = strongSelf.mediaGalleryContextMenu {
strongSelf.mediaGalleryContextMenu = nil
mediaGalleryContextMenu.dismiss()
}
}
self.paneContainerNode.requestPerformPeerMemberAction = { [weak self] member, action in
guard let strongSelf = self else {
@ -5983,6 +5993,9 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
}
}
private weak var mediaGalleryContextMenu: ContextController?
private func displayMediaGalleryContextMenu(source: ContextReferenceContentNode) {
guard let controller = self.controller else {
return
@ -5994,29 +6007,36 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
var items: [ContextMenuItem] = []
//TODO:localize
let canZoomIn = pane.zoomLevel.decremented() != pane.zoomLevel
let canZoomOut = pane.zoomLevel.incremented() != pane.zoomLevel
items.append(.action(ContextMenuActionItem(text: "Zoom In", textColor: canZoomIn ? .primary : .disabled, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ZoomIn"), color: canZoomIn ? theme.contextMenu.primaryColor : theme.contextMenu.primaryColor.withMultipliedAlpha(0.4))
}, action: canZoomIn ? { [weak pane] _, a in
a(.default)
guard let pane = pane else {
return
var recurseGenerateAction: ((Bool) -> ContextMenuActionItem)?
let generateAction: (Bool) -> ContextMenuActionItem = { [weak pane] isZoomIn in
var canZoom: Bool = true
if !"".isEmpty {
canZoom = false
}
pane.updateZoomLevel(level: pane.zoomLevel.decremented())
} : nil)))
items.append(.action(ContextMenuActionItem(text: "Zoom Out", textColor : canZoomOut ? .primary : .disabled, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ZoomOut"), color: canZoomOut ? theme.contextMenu.primaryColor : theme.contextMenu.primaryColor.withMultipliedAlpha(0.4))
}, action: canZoomOut ? { [weak pane] _, a in
a(.default)
/*if isZoomIn {
canZoom = pane?.availableZoomLevels().increment != nil
} else {
canZoom = pane?.availableZoomLevels().decrement != nil
}*/
return ContextMenuActionItem(text: isZoomIn ? "Zoom In" : "ZoomOut", textColor: canZoom ? .primary : .disabled, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: isZoomIn ? "Chat/Context Menu/ZoomIn" : "Chat/Context Menu/ZoomOut"), color: canZoom ? theme.contextMenu.primaryColor : theme.contextMenu.primaryColor.withMultipliedAlpha(0.4))
}, action: canZoom ? { action in
guard let pane = pane, let zoomLevel = isZoomIn ? pane.availableZoomLevels().increment : pane.availableZoomLevels().decrement else {
return
}
pane.updateZoomLevel(level: zoomLevel)
if let recurseGenerateAction = recurseGenerateAction {
action.updateAction(recurseGenerateAction(isZoomIn))
}
} : nil)
}
recurseGenerateAction = { isZoomIn in
return generateAction(isZoomIn)
}
items.append(.action(generateAction(true)))
items.append(.action(generateAction(false)))
guard let pane = pane else {
return
}
pane.updateZoomLevel(level: pane.zoomLevel.incremented())
} : nil)))
items.append(.action(ContextMenuActionItem(text: "Show Calendar", icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Calendar"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] _, a in
@ -6090,6 +6110,8 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
pane.updateContentType(contentType: updatedContentType)
})))
let contextController = ContextController(account: self.context.account, presentationData: self.presentationData, source: .reference(PeerInfoContextReferenceContentSource(controller: controller, sourceNode: source)), items: .single(ContextController.Items(items: items)), gesture: nil)
contextController.passthroughTouchEvents = true
self.mediaGalleryContextMenu = contextController
controller.presentInGlobalOverlay(contextController)
}