mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Shared media update
This commit is contained in:
parent
074df6febf
commit
c0fb52283d
@ -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() {
|
||||
}
|
||||
}
|
||||
|
@ -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.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) {
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
@ -238,6 +288,10 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
||||
self?.hapticFeedback.tap()
|
||||
}
|
||||
|
||||
updateLayout = { [weak self] in
|
||||
self?.updateLayout()
|
||||
}
|
||||
|
||||
self.scrollNode.view.delegate = self
|
||||
|
||||
if blurBackground {
|
||||
@ -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)
|
||||
@ -1087,6 +1143,12 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
return
|
||||
@ -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 {
|
||||
@ -1616,6 +1681,11 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
||||
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
|
||||
}
|
||||
}
|
||||
@ -1777,6 +1847,8 @@ public final class ContextController: ViewController, StandalonePresentableContr
|
||||
public var useComplexItemsTransitionAnimation = false
|
||||
public var immediateItemsTransitionAnimation = false
|
||||
|
||||
public var passthroughTouchEvents = false
|
||||
|
||||
private var shouldBeDismissedDisposable: Disposable?
|
||||
|
||||
public init(account: Account, presentationData: PresentationData, source: ContextContentSource, items: Signal<ContextController.Items, NoError>, recognizer: TapLongTapOrDoubleTapGestureRecognizer? = nil, gesture: ContextGesture? = nil) {
|
||||
|
@ -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)
|
||||
@ -87,6 +90,10 @@ final class PeekControllerNode: ViewControllerTracingNode {
|
||||
self?.hapticFeedback.tap()
|
||||
}
|
||||
|
||||
requestLayoutImpl = { [weak self] in
|
||||
self?.updateLayout()
|
||||
}
|
||||
|
||||
if content.presentation() == .freeform {
|
||||
self.containerNode.isUserInteractionEnabled = false
|
||||
} else {
|
||||
@ -119,6 +126,12 @@ final class PeekControllerNode: ViewControllerTracingNode {
|
||||
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)
|
||||
|
23
submodules/DirectMediaImageCache/BUILD
Normal file
23
submodules/DirectMediaImageCache/BUILD
Normal 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",
|
||||
],
|
||||
)
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
@ -866,6 +866,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
|
||||
let currentScale = sqrt((t.m11 * t.m11) + (t.m12 * t.m12) + (t.m13 * t.m13))
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -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
|
||||
|
@ -2311,6 +2311,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 {
|
||||
return
|
||||
@ -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 {
|
||||
var recurseGenerateAction: ((Bool) -> ContextMenuActionItem)?
|
||||
let generateAction: (Bool) -> ContextMenuActionItem = { [weak pane] isZoomIn in
|
||||
var canZoom: Bool = true
|
||||
if !"".isEmpty {
|
||||
canZoom = false
|
||||
}
|
||||
/*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: 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)
|
||||
|
||||
guard let pane = pane else {
|
||||
return
|
||||
pane.updateZoomLevel(level: zoomLevel)
|
||||
if let recurseGenerateAction = recurseGenerateAction {
|
||||
action.updateAction(recurseGenerateAction(isZoomIn))
|
||||
}
|
||||
pane.updateZoomLevel(level: pane.zoomLevel.incremented())
|
||||
} : nil)))
|
||||
} : nil)
|
||||
}
|
||||
recurseGenerateAction = { isZoomIn in
|
||||
return generateAction(isZoomIn)
|
||||
}
|
||||
|
||||
items.append(.action(generateAction(true)))
|
||||
items.append(.action(generateAction(false)))
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user