mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2026-02-17 16:27:30 +00:00
962 lines
33 KiB
Swift
962 lines
33 KiB
Swift
import Foundation
|
|
import UIKit
|
|
import AsyncDisplayKit
|
|
import Display
|
|
import TelegramPresentationData
|
|
import TextSelectionNode
|
|
import ReactionSelectionNode
|
|
import TelegramCore
|
|
import SwiftSignalKit
|
|
import AccountContext
|
|
import TextNodeWithEntities
|
|
import EntityKeyboard
|
|
import AnimationCache
|
|
import MultiAnimationRenderer
|
|
import UndoUI
|
|
import UIKitRuntimeUtils
|
|
|
|
public protocol ContextControllerProtocol: ViewController {
|
|
var useComplexItemsTransitionAnimation: Bool { get set }
|
|
var immediateItemsTransitionAnimation: Bool { get set }
|
|
var getOverlayViews: (() -> [UIView])? { get set }
|
|
|
|
func dismiss(completion: (() -> Void)?)
|
|
func dismiss(result: ContextMenuActionResult, completion: (() -> Void)?)
|
|
|
|
func getActionsMinHeight() -> ContextController.ActionsHeight?
|
|
func setItems(_ items: Signal<ContextController.Items, NoError>, minHeight: ContextController.ActionsHeight?, animated: Bool)
|
|
func setItems(_ items: Signal<ContextController.Items, NoError>, minHeight: ContextController.ActionsHeight?, previousActionsTransition: ContextController.PreviousActionsTransition)
|
|
func pushItems(items: Signal<ContextController.Items, NoError>)
|
|
func popItems()
|
|
}
|
|
|
|
public enum ContextMenuActionItemTextLayout {
|
|
case singleLine
|
|
case twoLinesMax
|
|
case secondLineWithValue(String)
|
|
case secondLineWithAttributedValue(NSAttributedString)
|
|
case multiline
|
|
}
|
|
|
|
public enum ContextMenuActionItemTextColor {
|
|
case primary
|
|
case destructive
|
|
case disabled
|
|
}
|
|
|
|
public enum ContextMenuActionResult {
|
|
case `default`
|
|
case dismissWithoutContent
|
|
|
|
case custom(ContainedViewLayoutTransition)
|
|
}
|
|
|
|
public enum ContextMenuActionItemFont {
|
|
case regular
|
|
case small
|
|
case custom(font: UIFont, height: CGFloat?, verticalOffset: CGFloat?)
|
|
}
|
|
|
|
public struct ContextMenuActionItemIconSource {
|
|
public let size: CGSize
|
|
public let contentMode: UIView.ContentMode
|
|
public let cornerRadius: CGFloat
|
|
public let signal: Signal<UIImage?, NoError>
|
|
|
|
public init(size: CGSize, contentMode: UIView.ContentMode = .scaleToFill, cornerRadius: CGFloat = 0.0, signal: Signal<UIImage?, NoError>) {
|
|
self.size = size
|
|
self.contentMode = contentMode
|
|
self.cornerRadius = cornerRadius
|
|
self.signal = signal
|
|
}
|
|
}
|
|
|
|
public enum ContextMenuActionItemIconPosition {
|
|
case left
|
|
case right
|
|
}
|
|
|
|
public enum ContextMenuActionBadgeColor {
|
|
case accent
|
|
case inactive
|
|
}
|
|
|
|
public struct ContextMenuActionBadge: Equatable {
|
|
public enum Style {
|
|
case badge
|
|
case label
|
|
}
|
|
|
|
public var value: String
|
|
public var color: ContextMenuActionBadgeColor
|
|
public var style: Style
|
|
|
|
public init(value: String, color: ContextMenuActionBadgeColor, style: Style = .badge) {
|
|
self.value = value
|
|
self.color = color
|
|
self.style = style
|
|
}
|
|
}
|
|
|
|
public final class ContextMenuActionItem {
|
|
public final class Action {
|
|
public let controller: ContextControllerProtocol?
|
|
public let dismissWithResult: (ContextMenuActionResult) -> Void
|
|
public let updateAction: (AnyHashable, ContextMenuActionItem) -> Void
|
|
|
|
public init(controller: ContextControllerProtocol?, dismissWithResult: @escaping (ContextMenuActionResult) -> Void, updateAction: @escaping (AnyHashable, ContextMenuActionItem) -> Void) {
|
|
self.controller = controller
|
|
self.dismissWithResult = dismissWithResult
|
|
self.updateAction = updateAction
|
|
}
|
|
}
|
|
|
|
public struct IconAnimation: Equatable {
|
|
public var name: String
|
|
public var loop: Bool
|
|
|
|
public init(name: String, loop: Bool = false) {
|
|
self.name = name
|
|
self.loop = loop
|
|
}
|
|
}
|
|
|
|
public let id: AnyHashable?
|
|
public let text: String
|
|
public let entities: [MessageTextEntity]
|
|
public let entityFiles: [Int64: TelegramMediaFile]
|
|
public let enableEntityAnimations: Bool
|
|
public let textColor: ContextMenuActionItemTextColor
|
|
public let textFont: ContextMenuActionItemFont
|
|
public let textLayout: ContextMenuActionItemTextLayout
|
|
public let parseMarkdown: Bool
|
|
public let badge: ContextMenuActionBadge?
|
|
public let icon: (PresentationTheme) -> UIImage?
|
|
public let additionalLeftIcon: ((PresentationTheme) -> UIImage?)?
|
|
public let iconSource: ContextMenuActionItemIconSource?
|
|
public let iconPosition: ContextMenuActionItemIconPosition
|
|
public let animationName: String?
|
|
public let iconAnimation: IconAnimation?
|
|
public let textIcon: (PresentationTheme) -> UIImage?
|
|
public let textLinkAction: () -> Void
|
|
public let action: ((Action) -> Void)?
|
|
public let longPressAction: ((Action) -> Void)?
|
|
|
|
convenience public init(
|
|
id: AnyHashable? = nil,
|
|
text: String,
|
|
entities: [MessageTextEntity] = [],
|
|
entityFiles: [Int64: TelegramMediaFile] = [:],
|
|
enableEntityAnimations: Bool = true,
|
|
textColor: ContextMenuActionItemTextColor = .primary,
|
|
textLayout: ContextMenuActionItemTextLayout = .twoLinesMax,
|
|
textFont: ContextMenuActionItemFont = .regular,
|
|
parseMarkdown: Bool = false,
|
|
badge: ContextMenuActionBadge? = nil,
|
|
icon: @escaping (PresentationTheme) -> UIImage?,
|
|
additionalLeftIcon: ((PresentationTheme) -> UIImage?)? = nil,
|
|
iconSource: ContextMenuActionItemIconSource? = nil,
|
|
iconPosition: ContextMenuActionItemIconPosition = .right,
|
|
animationName: String? = nil,
|
|
iconAnimation: IconAnimation? = nil,
|
|
textIcon: @escaping (PresentationTheme) -> UIImage? = { _ in return nil },
|
|
textLinkAction: @escaping () -> Void = {},
|
|
action: ((ContextControllerProtocol?, @escaping (ContextMenuActionResult) -> Void) -> Void)?,
|
|
longPressAction: ((ContextControllerProtocol?, @escaping (ContextMenuActionResult) -> Void) -> Void)? = nil
|
|
) {
|
|
self.init(
|
|
id: id,
|
|
text: text,
|
|
entities: entities,
|
|
entityFiles: entityFiles,
|
|
enableEntityAnimations: enableEntityAnimations,
|
|
textColor: textColor,
|
|
textLayout: textLayout,
|
|
textFont: textFont,
|
|
parseMarkdown: parseMarkdown,
|
|
badge: badge,
|
|
icon: icon,
|
|
additionalLeftIcon: additionalLeftIcon,
|
|
iconSource: iconSource,
|
|
iconPosition: iconPosition,
|
|
animationName: animationName,
|
|
iconAnimation: iconAnimation,
|
|
textIcon: textIcon,
|
|
textLinkAction: textLinkAction,
|
|
action: action.flatMap { action in
|
|
return { impl in
|
|
action(impl.controller, impl.dismissWithResult)
|
|
}
|
|
},
|
|
longPressAction: longPressAction.flatMap { longPressAction in
|
|
return { impl in
|
|
longPressAction(impl.controller, impl.dismissWithResult)
|
|
}
|
|
}
|
|
)
|
|
}
|
|
|
|
public init(
|
|
id: AnyHashable? = nil,
|
|
text: String,
|
|
entities: [MessageTextEntity] = [],
|
|
entityFiles: [Int64: TelegramMediaFile] = [:],
|
|
enableEntityAnimations: Bool = true,
|
|
textColor: ContextMenuActionItemTextColor = .primary,
|
|
textLayout: ContextMenuActionItemTextLayout = .twoLinesMax,
|
|
textFont: ContextMenuActionItemFont = .regular,
|
|
parseMarkdown: Bool = false,
|
|
badge: ContextMenuActionBadge? = nil,
|
|
icon: @escaping (PresentationTheme) -> UIImage?,
|
|
additionalLeftIcon: ((PresentationTheme) -> UIImage?)? = nil,
|
|
iconSource: ContextMenuActionItemIconSource? = nil,
|
|
iconPosition: ContextMenuActionItemIconPosition = .right,
|
|
animationName: String? = nil,
|
|
iconAnimation: IconAnimation? = nil,
|
|
textIcon: @escaping (PresentationTheme) -> UIImage? = { _ in return nil },
|
|
textLinkAction: @escaping () -> Void = {},
|
|
action: ((Action) -> Void)?,
|
|
longPressAction: ((Action) -> Void)? = nil
|
|
) {
|
|
self.id = id
|
|
self.text = text
|
|
self.entities = entities
|
|
self.entityFiles = entityFiles
|
|
self.enableEntityAnimations = enableEntityAnimations
|
|
self.textColor = textColor
|
|
self.textFont = textFont
|
|
self.textLayout = textLayout
|
|
self.parseMarkdown = parseMarkdown
|
|
self.badge = badge
|
|
self.icon = icon
|
|
self.additionalLeftIcon = additionalLeftIcon
|
|
self.iconSource = iconSource
|
|
self.iconPosition = iconPosition
|
|
self.animationName = animationName
|
|
self.iconAnimation = iconAnimation
|
|
self.textIcon = textIcon
|
|
self.textLinkAction = textLinkAction
|
|
self.action = action
|
|
self.longPressAction = longPressAction
|
|
}
|
|
}
|
|
|
|
public protocol ContextMenuCustomNode: ASDisplayNode {
|
|
func updateLayout(constrainedWidth: CGFloat, constrainedHeight: CGFloat) -> (CGSize, (CGSize, ContainedViewLayoutTransition) -> Void)
|
|
func updateTheme(presentationData: PresentationData)
|
|
|
|
func canBeHighlighted() -> Bool
|
|
func updateIsHighlighted(isHighlighted: Bool)
|
|
func performAction()
|
|
|
|
var needsSeparator: Bool { get }
|
|
}
|
|
|
|
public extension ContextMenuCustomNode {
|
|
var needsSeparator: Bool {
|
|
return true
|
|
}
|
|
}
|
|
|
|
public protocol ContextMenuCustomItem {
|
|
func node(presentationData: PresentationData, getController: @escaping () -> ContextControllerProtocol?, actionSelected: @escaping (ContextMenuActionResult) -> Void) -> ContextMenuCustomNode
|
|
}
|
|
|
|
public enum ContextMenuItem {
|
|
case action(ContextMenuActionItem)
|
|
case custom(ContextMenuCustomItem, Bool)
|
|
case separator
|
|
}
|
|
|
|
public final class ContextControllerLocationViewInfo {
|
|
public let location: CGPoint
|
|
public let contentAreaInScreenSpace: CGRect
|
|
public let insets: UIEdgeInsets
|
|
|
|
public init(location: CGPoint, contentAreaInScreenSpace: CGRect, insets: UIEdgeInsets = UIEdgeInsets()) {
|
|
self.location = location
|
|
self.contentAreaInScreenSpace = contentAreaInScreenSpace
|
|
self.insets = insets
|
|
}
|
|
}
|
|
|
|
public protocol ContextLocationContentSource: AnyObject {
|
|
var shouldBeDismissed: Signal<Bool, NoError> { get }
|
|
|
|
func transitionInfo() -> ContextControllerLocationViewInfo?
|
|
}
|
|
|
|
public extension ContextLocationContentSource {
|
|
var shouldBeDismissed: Signal<Bool, NoError> {
|
|
return .single(false)
|
|
}
|
|
}
|
|
|
|
public final class ContextControllerReferenceViewInfo {
|
|
public enum ActionsPosition {
|
|
case bottom
|
|
case top
|
|
}
|
|
public let referenceView: UIView
|
|
public let contentAreaInScreenSpace: CGRect
|
|
public let insets: UIEdgeInsets
|
|
public let customPosition: CGPoint?
|
|
public let actionsPosition: ActionsPosition
|
|
|
|
public init(referenceView: UIView, contentAreaInScreenSpace: CGRect, insets: UIEdgeInsets = UIEdgeInsets(), customPosition: CGPoint? = nil, actionsPosition: ActionsPosition = .bottom) {
|
|
self.referenceView = referenceView
|
|
self.contentAreaInScreenSpace = contentAreaInScreenSpace
|
|
self.insets = insets
|
|
self.customPosition = customPosition
|
|
self.actionsPosition = actionsPosition
|
|
}
|
|
}
|
|
|
|
public protocol ContextReferenceContentSource: AnyObject {
|
|
var keepInPlace: Bool { get }
|
|
var shouldBeDismissed: Signal<Bool, NoError> { get }
|
|
var forceDisplayBelowKeyboard: Bool { get }
|
|
|
|
func transitionInfo() -> ContextControllerReferenceViewInfo?
|
|
}
|
|
|
|
public extension ContextReferenceContentSource {
|
|
var keepInPlace: Bool {
|
|
return false
|
|
}
|
|
|
|
var forceDisplayBelowKeyboard: Bool {
|
|
return false
|
|
}
|
|
|
|
var shouldBeDismissed: Signal<Bool, NoError> {
|
|
return .single(false)
|
|
}
|
|
}
|
|
|
|
public final class ContextControllerTakeViewInfo {
|
|
public enum ContainingItem {
|
|
case node(ContextExtractedContentContainingNode)
|
|
case view(ContextExtractedContentContainingView)
|
|
}
|
|
|
|
public let containingItem: ContainingItem
|
|
public let contentAreaInScreenSpace: CGRect
|
|
public let maskView: UIView?
|
|
|
|
public init(containingItem: ContainingItem, contentAreaInScreenSpace: CGRect, maskView: UIView? = nil) {
|
|
self.containingItem = containingItem
|
|
self.contentAreaInScreenSpace = contentAreaInScreenSpace
|
|
self.maskView = maskView
|
|
}
|
|
}
|
|
|
|
public final class ContextControllerPutBackViewInfo {
|
|
public let contentAreaInScreenSpace: CGRect
|
|
public let maskView: UIView?
|
|
|
|
public init(contentAreaInScreenSpace: CGRect, maskView: UIView? = nil) {
|
|
self.contentAreaInScreenSpace = contentAreaInScreenSpace
|
|
self.maskView = maskView
|
|
}
|
|
}
|
|
|
|
public enum ContextActionsHorizontalAlignment {
|
|
case `default`
|
|
case left
|
|
case center
|
|
case right
|
|
}
|
|
|
|
public protocol ContextExtractedContentSource: AnyObject {
|
|
var initialAppearanceOffset: CGPoint { get }
|
|
var centerVertically: Bool { get }
|
|
var keepInPlace: Bool { get }
|
|
var adjustContentHorizontally: Bool { get }
|
|
var adjustContentForSideInset: Bool { get }
|
|
var ignoreContentTouches: Bool { get }
|
|
var keepDefaultContentTouches: Bool { get }
|
|
var blurBackground: Bool { get }
|
|
var shouldBeDismissed: Signal<Bool, NoError> { get }
|
|
var additionalInsets: UIEdgeInsets { get }
|
|
|
|
var actionsHorizontalAlignment: ContextActionsHorizontalAlignment { get }
|
|
|
|
func takeView() -> ContextControllerTakeViewInfo?
|
|
func putBack() -> ContextControllerPutBackViewInfo?
|
|
}
|
|
|
|
public extension ContextExtractedContentSource {
|
|
var initialAppearanceOffset: CGPoint {
|
|
return .zero
|
|
}
|
|
|
|
var centerVertically: Bool {
|
|
return false
|
|
}
|
|
|
|
var adjustContentHorizontally: Bool {
|
|
return false
|
|
}
|
|
|
|
var adjustContentForSideInset: Bool {
|
|
return false
|
|
}
|
|
|
|
var additionalInsets: UIEdgeInsets {
|
|
return .zero
|
|
}
|
|
|
|
var actionsHorizontalAlignment: ContextActionsHorizontalAlignment {
|
|
return .default
|
|
}
|
|
|
|
var shouldBeDismissed: Signal<Bool, NoError> {
|
|
return .single(false)
|
|
}
|
|
|
|
var keepDefaultContentTouches: Bool {
|
|
return false
|
|
}
|
|
}
|
|
|
|
public final class ContextControllerTakeControllerInfo {
|
|
public let contentAreaInScreenSpace: CGRect
|
|
public let sourceNode: () -> (UIView, CGRect)?
|
|
|
|
public init(contentAreaInScreenSpace: CGRect, sourceNode: @escaping () -> (UIView, CGRect)?) {
|
|
self.contentAreaInScreenSpace = contentAreaInScreenSpace
|
|
self.sourceNode = sourceNode
|
|
}
|
|
}
|
|
|
|
public protocol ContextControllerContentSource: AnyObject {
|
|
var controller: ViewController { get }
|
|
var navigationController: NavigationController? { get }
|
|
var passthroughTouches: Bool { get }
|
|
|
|
func transitionInfo() -> ContextControllerTakeControllerInfo?
|
|
|
|
func animatedIn()
|
|
}
|
|
|
|
public enum ContextContentSource {
|
|
case location(ContextLocationContentSource)
|
|
case reference(ContextReferenceContentSource)
|
|
case extracted(ContextExtractedContentSource)
|
|
case controller(ContextControllerContentSource)
|
|
}
|
|
|
|
public protocol ContextControllerItemsNode: ASDisplayNode {
|
|
func update(presentationData: PresentationData, constrainedWidth: CGFloat, maxHeight: CGFloat, bottomInset: CGFloat, transition: ContainedViewLayoutTransition) -> (cleanSize: CGSize, apparentHeight: CGFloat)
|
|
|
|
var apparentHeight: CGFloat { get }
|
|
}
|
|
|
|
public protocol ContextControllerItemsContent: AnyObject {
|
|
func node(
|
|
requestUpdate: @escaping (ContainedViewLayoutTransition) -> Void,
|
|
requestUpdateApparentHeight: @escaping (ContainedViewLayoutTransition) -> Void
|
|
) -> ContextControllerItemsNode
|
|
}
|
|
|
|
public final class ContextControllerSource {
|
|
public let id: AnyHashable
|
|
public let title: String
|
|
public let footer: String?
|
|
public let source: ContextContentSource
|
|
public let items: Signal<ContextController.Items, NoError>
|
|
public let closeActionTitle: String?
|
|
public let closeAction: (() -> Void)?
|
|
|
|
public init(
|
|
id: AnyHashable,
|
|
title: String,
|
|
footer: String? = nil,
|
|
source: ContextContentSource,
|
|
items: Signal<ContextController.Items, NoError>,
|
|
closeActionTitle: String? = nil,
|
|
closeAction: (() -> Void)? = nil
|
|
) {
|
|
self.id = id
|
|
self.title = title
|
|
self.footer = footer
|
|
self.source = source
|
|
self.items = items
|
|
self.closeActionTitle = closeActionTitle
|
|
self.closeAction = closeAction
|
|
}
|
|
}
|
|
|
|
public final class ContextControllerConfiguration {
|
|
public let sources: [ContextControllerSource]
|
|
public let initialId: AnyHashable
|
|
|
|
public init(sources: [ContextControllerSource], initialId: AnyHashable) {
|
|
self.sources = sources
|
|
self.initialId = initialId
|
|
}
|
|
}
|
|
|
|
public struct ContextControllerItems {
|
|
public enum Content {
|
|
case list([ContextMenuItem])
|
|
case twoLists([ContextMenuItem], [ContextMenuItem])
|
|
case custom(ContextControllerItemsContent)
|
|
}
|
|
|
|
public var id: AnyHashable?
|
|
public var content: Content
|
|
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 var disablePositionLock: Bool
|
|
public var previewReaction: TelegramMediaFile?
|
|
public var tip: ContextControllerTip?
|
|
public var tipSignal: Signal<ContextControllerTip?, NoError>?
|
|
public var dismissed: (() -> Void)?
|
|
|
|
public init(
|
|
id: AnyHashable? = nil,
|
|
content: Content,
|
|
context: AccountContext? = nil,
|
|
reactionItems: [ReactionContextItem] = [],
|
|
selectedReactionItems: Set<MessageReaction.Reaction> = Set(),
|
|
reactionsTitle: String? = nil,
|
|
reactionsLocked: Bool = false,
|
|
animationCache: AnimationCache? = nil,
|
|
alwaysAllowPremiumReactions: Bool = false,
|
|
allPresetReactionsAreAvailable: Bool = false,
|
|
getEmojiContent: ((AnimationCache, MultiAnimationRenderer) -> Signal<EmojiPagerContentComponent, NoError>)? = nil,
|
|
disablePositionLock: Bool = false,
|
|
previewReaction: TelegramMediaFile? = nil,
|
|
tip: ContextControllerTip? = nil,
|
|
tipSignal: Signal<ContextControllerTip?, NoError>? = nil,
|
|
dismissed: (() -> Void)? = nil
|
|
) {
|
|
self.id = id
|
|
self.content = content
|
|
self.context = context
|
|
self.animationCache = animationCache
|
|
self.reactionItems = reactionItems
|
|
self.selectedReactionItems = selectedReactionItems
|
|
self.reactionsTitle = reactionsTitle
|
|
self.reactionsLocked = reactionsLocked
|
|
self.alwaysAllowPremiumReactions = alwaysAllowPremiumReactions
|
|
self.allPresetReactionsAreAvailable = allPresetReactionsAreAvailable
|
|
self.getEmojiContent = getEmojiContent
|
|
self.disablePositionLock = disablePositionLock
|
|
self.previewReaction = previewReaction
|
|
self.tip = tip
|
|
self.tipSignal = tipSignal
|
|
self.dismissed = dismissed
|
|
}
|
|
|
|
public init() {
|
|
self.id = nil
|
|
self.content = .list([])
|
|
self.context = nil
|
|
self.reactionItems = []
|
|
self.selectedReactionItems = Set()
|
|
self.reactionsTitle = nil
|
|
self.reactionsLocked = false
|
|
self.alwaysAllowPremiumReactions = false
|
|
self.allPresetReactionsAreAvailable = false
|
|
self.getEmojiContent = nil
|
|
self.disablePositionLock = false
|
|
self.previewReaction = nil
|
|
self.tip = nil
|
|
self.tipSignal = nil
|
|
self.dismissed = nil
|
|
}
|
|
}
|
|
|
|
public enum ContextControllerPreviousActionsTransition {
|
|
case scale
|
|
case slide(forward: Bool)
|
|
}
|
|
|
|
public enum ContextControllerTip: Equatable {
|
|
case textSelection
|
|
case quoteSelection
|
|
case messageViewsPrivacy
|
|
case messageCopyProtection(isChannel: Bool)
|
|
case animatedEmoji(text: String?, arguments: TextNodeWithEntities.Arguments?, file: TelegramMediaFile?, action: (() -> Void)?)
|
|
case notificationTopicExceptions(text: String, action: (() -> Void)?)
|
|
case starsReactions(topCount: Int)
|
|
case videoProcessing
|
|
case collageReordering
|
|
|
|
public static func ==(lhs: ContextControllerTip, rhs: ContextControllerTip) -> Bool {
|
|
switch lhs {
|
|
case .textSelection:
|
|
if case .textSelection = rhs {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
case .quoteSelection:
|
|
if case .quoteSelection = rhs {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
case .messageViewsPrivacy:
|
|
if case .messageViewsPrivacy = rhs {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
case let .messageCopyProtection(isChannel):
|
|
if case .messageCopyProtection(isChannel) = rhs {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
case let .animatedEmoji(text, _, file, _):
|
|
if case let .animatedEmoji(rhsText, _, rhsFile, _) = rhs {
|
|
if text != rhsText {
|
|
return false
|
|
}
|
|
if file?.fileId != rhsFile?.fileId {
|
|
return false
|
|
}
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
case let .notificationTopicExceptions(text, _):
|
|
if case .notificationTopicExceptions(text, _) = rhs {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
case let .starsReactions(topCount):
|
|
if case .starsReactions(topCount) = rhs {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
case .videoProcessing:
|
|
if case .videoProcessing = rhs {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
case .collageReordering:
|
|
if case .collageReordering = rhs {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public final class ContextControllerActionsHeight {
|
|
public let minY: CGFloat
|
|
public let contentOffset: CGFloat
|
|
|
|
public init(minY: CGFloat, contentOffset: CGFloat) {
|
|
self.minY = minY
|
|
self.contentOffset = contentOffset
|
|
}
|
|
}
|
|
|
|
public enum ContextControllerHandledTouchEvent {
|
|
case ignore
|
|
case dismiss(consume: Bool, result: UIView?)
|
|
}
|
|
|
|
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 }
|
|
}
|
|
|
|
public protocol ContextController: ViewController, StandalonePresentableController, ContextControllerProtocol, KeyShortcutResponder {
|
|
typealias ContentSource = ContextContentSource
|
|
typealias ItemsNode = ContextControllerItemsNode
|
|
typealias ItemsContent = ContextControllerItemsContent
|
|
typealias Source = ContextControllerSource
|
|
typealias Configuration = ContextControllerConfiguration
|
|
typealias Items = ContextControllerItems
|
|
typealias PreviousActionsTransition = ContextControllerPreviousActionsTransition
|
|
typealias Tip = ContextControllerTip
|
|
typealias ActionsHeight = ContextControllerActionsHeight
|
|
typealias HandledTouchEvent = ContextControllerHandledTouchEvent
|
|
|
|
var dismissed: (() -> Void)? { get set }
|
|
var dismissedForCancel: (() -> Void)? { get set }
|
|
var passthroughTouchEvent: ((UIView, CGPoint) -> HandledTouchEvent)? { get set }
|
|
var reactionSelected: ((UpdateMessageReaction, Bool) -> Void)? { get set }
|
|
var premiumReactionsSelected: (() -> Void)? { get set }
|
|
var getOverlayViews: (() -> [UIView])? { get set }
|
|
|
|
func addRelativeContentOffset(_ offset: CGPoint, transition: ContainedViewLayoutTransition)
|
|
func updateTheme(presentationData: PresentationData)
|
|
func dismissWithCustomTransition(transition: ContainedViewLayoutTransition, completion: (() -> Void)?)
|
|
func dismissWithoutContent()
|
|
func dismissNow()
|
|
func dismissWithReaction(value: MessageReaction.Reaction, targetView: UIView, hideNode: Bool, animateTargetContainer: UIView?, addStandaloneReactionAnimation: ((StandaloneReactionAnimation) -> Void)?, onHit: (() -> Void)?, completion: (() -> Void)?)
|
|
func animateDismissalIfNeeded()
|
|
func cancelReactionAnimation()
|
|
}
|
|
|
|
public func makeContextController(
|
|
context: AccountContext? = nil,
|
|
presentationData: PresentationData,
|
|
source: ContextContentSource,
|
|
items: Signal<ContextController.Items, NoError>,
|
|
recognizer: TapLongTapOrDoubleTapGestureRecognizer? = nil,
|
|
gesture: ContextGesture? = nil,
|
|
workaroundUseLegacyImplementation: Bool = false,
|
|
disableScreenshots: Bool = false,
|
|
hideReactionPanelTail: Bool = false
|
|
) -> ContextController {
|
|
return makeContextController(
|
|
context: context,
|
|
presentationData: presentationData,
|
|
configuration: ContextController.Configuration(
|
|
sources: [ContextController.Source(
|
|
id: AnyHashable(0 as Int),
|
|
title: "",
|
|
source: source,
|
|
items: items
|
|
)],
|
|
initialId: AnyHashable(0 as Int)
|
|
),
|
|
recognizer: recognizer,
|
|
gesture: gesture,
|
|
workaroundUseLegacyImplementation: workaroundUseLegacyImplementation,
|
|
disableScreenshots: disableScreenshots,
|
|
hideReactionPanelTail: hideReactionPanelTail
|
|
)
|
|
}
|
|
|
|
public var makeContextControllerImpl: ((
|
|
_ context: AccountContext?,
|
|
_ presentationData: PresentationData,
|
|
_ configuration: ContextController.Configuration,
|
|
_ recognizer: TapLongTapOrDoubleTapGestureRecognizer?,
|
|
_ gesture: ContextGesture?,
|
|
_ workaroundUseLegacyImplementation: Bool,
|
|
_ disableScreenshots: Bool,
|
|
_ hideReactionPanelTail: Bool
|
|
) -> ContextController)? = nil
|
|
|
|
public func makeContextController(
|
|
context: AccountContext? = nil,
|
|
presentationData: PresentationData,
|
|
configuration: ContextController.Configuration,
|
|
recognizer: TapLongTapOrDoubleTapGestureRecognizer? = nil,
|
|
gesture: ContextGesture? = nil,
|
|
workaroundUseLegacyImplementation: Bool = false,
|
|
disableScreenshots: Bool = false,
|
|
hideReactionPanelTail: Bool = false
|
|
) -> ContextController {
|
|
return makeContextControllerImpl!(
|
|
context,
|
|
presentationData,
|
|
configuration,
|
|
recognizer,
|
|
gesture,
|
|
workaroundUseLegacyImplementation,
|
|
disableScreenshots,
|
|
hideReactionPanelTail
|
|
)
|
|
}
|
|
|
|
public enum ContextControllerActionsStackNodePresentation {
|
|
case modal
|
|
case inline
|
|
case additional
|
|
}
|
|
|
|
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 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 }
|
|
}
|
|
|
|
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 ContextControllerActionsStackNode: ASDisplayNode {
|
|
typealias Presentation = ContextControllerActionsStackNodePresentation
|
|
|
|
var topReactionItems: ContextControllerReactionItems? { get }
|
|
var topPreviewReaction: ContextControllerPreviewReaction? { get }
|
|
var topPositionLock: CGFloat? { get }
|
|
var storedScrollingState: CGFloat? { get }
|
|
|
|
func replace(item: ContextControllerActionsStackItem, animated: Bool?)
|
|
func push(item: ContextControllerActionsStackItem, currentScrollingState: CGFloat?, positionLock: CGFloat?, animated: Bool)
|
|
func clearStoredScrollingState()
|
|
func pop()
|
|
func update(
|
|
presentationData: PresentationData,
|
|
constrainedSize: CGSize,
|
|
presentation: Presentation,
|
|
transition: ContainedViewLayoutTransition
|
|
) -> CGSize
|
|
func highlightGestureMoved(location: CGPoint)
|
|
func highlightGestureFinished(performAction: Bool)
|
|
func decreaseHighlightedIndex()
|
|
func increaseHighlightedIndex()
|
|
func updatePanSelection(isEnabled: Bool)
|
|
func animateIn()
|
|
}
|
|
|
|
public var makeContextControllerActionsListStackItemImpl: ((
|
|
_ id: AnyHashable?,
|
|
_ items: [ContextMenuItem],
|
|
_ reactionItems: ContextControllerReactionItems?,
|
|
_ previewReaction: ContextControllerPreviewReaction?,
|
|
_ tip: ContextController.Tip?,
|
|
_ tipSignal: Signal<ContextController.Tip?, NoError>?,
|
|
_ dismissed: (() -> Void)?
|
|
) -> ContextControllerActionsStackItem)?
|
|
|
|
public func makeContextControllerActionsListStackItem(
|
|
id: AnyHashable?,
|
|
items: [ContextMenuItem],
|
|
reactionItems: ContextControllerReactionItems?,
|
|
previewReaction: ContextControllerPreviewReaction?,
|
|
tip: ContextController.Tip?,
|
|
tipSignal: Signal<ContextController.Tip?, NoError>?,
|
|
dismissed: (() -> Void)?
|
|
) -> ContextControllerActionsStackItem {
|
|
return makeContextControllerActionsListStackItemImpl!(
|
|
id,
|
|
items,
|
|
reactionItems,
|
|
previewReaction,
|
|
tip,
|
|
tipSignal,
|
|
dismissed
|
|
)
|
|
}
|
|
|
|
public var makeContextControllerActionsStackNodeImpl: ((
|
|
_ context: AccountContext?,
|
|
_ getController: @escaping () -> ContextControllerProtocol?,
|
|
_ requestDismiss: @escaping (ContextMenuActionResult) -> Void,
|
|
_ requestUpdate: @escaping (ContainedViewLayoutTransition) -> Void
|
|
) -> ContextControllerActionsStackNode)?
|
|
|
|
public func makeContextControllerActionsStackNode(
|
|
context: AccountContext?,
|
|
getController: @escaping () -> ContextControllerProtocol?,
|
|
requestDismiss: @escaping (ContextMenuActionResult) -> Void,
|
|
requestUpdate: @escaping (ContainedViewLayoutTransition) -> Void
|
|
) -> ContextControllerActionsStackNode {
|
|
return makeContextControllerActionsStackNodeImpl!(
|
|
context,
|
|
getController,
|
|
requestDismiss,
|
|
requestUpdate
|
|
)
|
|
}
|
|
|
|
public var makeContextActionNodeImpl: ((
|
|
_ presentationData: PresentationData,
|
|
_ action: ContextMenuActionItem,
|
|
_ getController: @escaping () -> ContextControllerProtocol?,
|
|
_ actionSelected: @escaping (ContextMenuActionResult) -> Void,
|
|
_ requestLayout: @escaping () -> Void,
|
|
_ requestUpdateAction: @escaping (AnyHashable, ContextMenuActionItem) -> Void
|
|
) -> ContextActionNodeProtocol)?
|
|
|
|
public func makeContextActionNode(
|
|
presentationData: PresentationData,
|
|
action: ContextMenuActionItem,
|
|
getController: @escaping () -> ContextControllerProtocol?,
|
|
actionSelected: @escaping (ContextMenuActionResult) -> Void,
|
|
requestLayout: @escaping () -> Void,
|
|
requestUpdateAction: @escaping (AnyHashable, ContextMenuActionItem) -> Void
|
|
) -> ContextActionNodeProtocol {
|
|
return makeContextActionNodeImpl!(
|
|
presentationData,
|
|
action,
|
|
getController,
|
|
actionSelected,
|
|
requestLayout,
|
|
requestUpdateAction
|
|
)
|
|
}
|