mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Effect improvements
This commit is contained in:
parent
6e28b2a4e2
commit
7c0216b907
@ -27,7 +27,7 @@ public final class AvatarVideoNode: ASDisplayNode {
|
|||||||
|
|
||||||
private var fileDisposable = MetaDisposable()
|
private var fileDisposable = MetaDisposable()
|
||||||
private var animationFile: TelegramMediaFile?
|
private var animationFile: TelegramMediaFile?
|
||||||
private var itemLayer: EmojiPagerContentComponent.View.ItemLayer?
|
private var itemLayer: EmojiKeyboardItemLayer?
|
||||||
private var useAnimationNode = false
|
private var useAnimationNode = false
|
||||||
private var animationNode: AnimatedStickerNode?
|
private var animationNode: AnimatedStickerNode?
|
||||||
private let stickerFetchedDisposable = MetaDisposable()
|
private let stickerFetchedDisposable = MetaDisposable()
|
||||||
@ -101,7 +101,7 @@ public final class AvatarVideoNode: ASDisplayNode {
|
|||||||
let itemNativeFitSize = self.internalSize.width > 100.0 ? CGSize(width: 192.0, height: 192.0) : CGSize(width: 64.0, height: 64.0)
|
let itemNativeFitSize = self.internalSize.width > 100.0 ? CGSize(width: 192.0, height: 192.0) : CGSize(width: 64.0, height: 64.0)
|
||||||
|
|
||||||
let animationData = EntityKeyboardAnimationData(file: animationFile)
|
let animationData = EntityKeyboardAnimationData(file: animationFile)
|
||||||
let itemLayer = EmojiPagerContentComponent.View.ItemLayer(
|
let itemLayer = EmojiKeyboardItemLayer(
|
||||||
item: EmojiPagerContentComponent.Item(
|
item: EmojiPagerContentComponent.Item(
|
||||||
animationData: animationData,
|
animationData: animationData,
|
||||||
content: .animation(animationData),
|
content: .animation(animationData),
|
||||||
|
@ -179,6 +179,9 @@ final class ChatSendMessageContextScreenComponent: Component {
|
|||||||
private var animateOutToEmpty: Bool = false
|
private var animateOutToEmpty: Bool = false
|
||||||
|
|
||||||
private var initializationDisplayLink: SharedDisplayLinkDriver.Link?
|
private var initializationDisplayLink: SharedDisplayLinkDriver.Link?
|
||||||
|
private var updateSourcePositionsDisplayLink: SharedDisplayLinkDriver.Link?
|
||||||
|
|
||||||
|
private var stableSourceSendButtonFrame: CGRect?
|
||||||
|
|
||||||
override init(frame: CGRect) {
|
override init(frame: CGRect) {
|
||||||
self.backgroundView = BlurredBackgroundView(color: .clear, enableBlur: true)
|
self.backgroundView = BlurredBackgroundView(color: .clear, enableBlur: true)
|
||||||
@ -273,6 +276,21 @@ final class ChatSendMessageContextScreenComponent: Component {
|
|||||||
|
|
||||||
let environment = environment[EnvironmentType.self].value
|
let environment = environment[EnvironmentType.self].value
|
||||||
|
|
||||||
|
if let previousEnvironment = self.environment, previousEnvironment.inputHeight != 0.0, environment.inputHeight == 0.0 {
|
||||||
|
DispatchQueue.main.async { [weak self] in
|
||||||
|
guard let self, let component = self.component else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let stableSourceSendButtonFrame = convertFrame(component.sourceSendButton.bounds, from: component.sourceSendButton.view, to: self)
|
||||||
|
if self.stableSourceSendButtonFrame != stableSourceSendButtonFrame {
|
||||||
|
self.stableSourceSendButtonFrame = stableSourceSendButtonFrame
|
||||||
|
if !self.isUpdating {
|
||||||
|
self.state?.updated(transition: .spring(duration: 0.35))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var transition = transition
|
var transition = transition
|
||||||
|
|
||||||
var transitionIsImmediate = transition.animation.isImmediate
|
var transitionIsImmediate = transition.animation.isImmediate
|
||||||
@ -365,7 +383,19 @@ final class ChatSendMessageContextScreenComponent: Component {
|
|||||||
self.addSubview(sendButton)
|
self.addSubview(sendButton)
|
||||||
}
|
}
|
||||||
|
|
||||||
let sourceSendButtonFrame = convertFrame(component.sourceSendButton.bounds, from: component.sourceSendButton.view, to: self)
|
let sourceSendButtonFrame: CGRect
|
||||||
|
switch self.presentationAnimationState {
|
||||||
|
case .animatedOut:
|
||||||
|
sourceSendButtonFrame = convertFrame(component.sourceSendButton.bounds, from: component.sourceSendButton.view, to: self)
|
||||||
|
self.stableSourceSendButtonFrame = sourceSendButtonFrame
|
||||||
|
default:
|
||||||
|
if let stableSourceSendButtonFrame = self.stableSourceSendButtonFrame {
|
||||||
|
sourceSendButtonFrame = stableSourceSendButtonFrame
|
||||||
|
} else {
|
||||||
|
sourceSendButtonFrame = convertFrame(component.sourceSendButton.bounds, from: component.sourceSendButton.view, to: self)
|
||||||
|
self.stableSourceSendButtonFrame = sourceSendButtonFrame
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let sendButtonScale: CGFloat
|
let sendButtonScale: CGFloat
|
||||||
switch self.presentationAnimationState {
|
switch self.presentationAnimationState {
|
||||||
@ -843,6 +873,8 @@ final class ChatSendMessageContextScreenComponent: Component {
|
|||||||
if !self.isUpdating {
|
if !self.isUpdating {
|
||||||
self.state?.updated(transition: .easeInOut(duration: 0.2))
|
self.state?.updated(transition: .easeInOut(duration: 0.2))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.endEditing(true)
|
||||||
})
|
})
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
@ -1014,6 +1046,13 @@ final class ChatSendMessageContextScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let standaloneReactionAnimation, let targetView = messageItemView.effectIconView {
|
||||||
|
let effectSize = CGSize(width: 380.0, height: 380.0)
|
||||||
|
var effectFrame = effectSize.centered(around: targetView.convert(targetView.bounds.center, to: self))
|
||||||
|
effectFrame.origin.x -= effectFrame.width * 0.3
|
||||||
|
transition.setFrame(view: standaloneReactionAnimation.view, frame: effectFrame)
|
||||||
|
}
|
||||||
|
|
||||||
if let reactionContextNode = self.reactionContextNode {
|
if let reactionContextNode = self.reactionContextNode {
|
||||||
let reactionContextY = environment.statusBarHeight
|
let reactionContextY = environment.statusBarHeight
|
||||||
let size = availableSize
|
let size = availableSize
|
||||||
|
@ -122,8 +122,8 @@ final class StickerPackEmojisItemNode: GridItemNode {
|
|||||||
|
|
||||||
private var boundsChangeTrackerLayer = SimpleLayer()
|
private var boundsChangeTrackerLayer = SimpleLayer()
|
||||||
|
|
||||||
private var visibleItemLayers: [EmojiPagerContentComponent.View.ItemLayer.Key: EmojiPagerContentComponent.View.ItemLayer] = [:]
|
private var visibleItemLayers: [EmojiKeyboardItemLayer.Key: EmojiKeyboardItemLayer] = [:]
|
||||||
private var visibleItemPlaceholderViews: [EmojiPagerContentComponent.View.ItemLayer.Key: EmojiPagerContentComponent.View.ItemPlaceholderView] = [:]
|
private var visibleItemPlaceholderViews: [EmojiKeyboardItemLayer.Key: EmojiPagerContentComponent.View.ItemPlaceholderView] = [:]
|
||||||
|
|
||||||
private let containerNode: ASDisplayNode
|
private let containerNode: ASDisplayNode
|
||||||
private let titleNode: ImmediateTextNode
|
private let titleNode: ImmediateTextNode
|
||||||
@ -195,7 +195,7 @@ final class StickerPackEmojisItemNode: GridItemNode {
|
|||||||
|
|
||||||
func targetItem(at point: CGPoint) -> (TelegramMediaFile, CALayer)? {
|
func targetItem(at point: CGPoint) -> (TelegramMediaFile, CALayer)? {
|
||||||
if let (item, _) = self.item(atPoint: point), let file = item.itemFile {
|
if let (item, _) = self.item(atPoint: point), let file = item.itemFile {
|
||||||
let itemId = EmojiPagerContentComponent.View.ItemLayer.Key(
|
let itemId = EmojiKeyboardItemLayer.Key(
|
||||||
groupId: 0,
|
groupId: 0,
|
||||||
itemId: .animation(.file(file.fileId))
|
itemId: .animation(.file(file.fileId))
|
||||||
)
|
)
|
||||||
@ -237,7 +237,7 @@ final class StickerPackEmojisItemNode: GridItemNode {
|
|||||||
private func item(atPoint point: CGPoint, extendedHitRange: Bool = false) -> (EmojiPagerContentComponent.Item, CGRect)? {
|
private func item(atPoint point: CGPoint, extendedHitRange: Bool = false) -> (EmojiPagerContentComponent.Item, CGRect)? {
|
||||||
let localPoint = point
|
let localPoint = point
|
||||||
|
|
||||||
var closestItem: (key: EmojiPagerContentComponent.View.ItemLayer.Key, distance: CGFloat)?
|
var closestItem: (key: EmojiKeyboardItemLayer.Key, distance: CGFloat)?
|
||||||
|
|
||||||
for (key, itemLayer) in self.visibleItemLayers {
|
for (key, itemLayer) in self.visibleItemLayers {
|
||||||
if extendedHitRange {
|
if extendedHitRange {
|
||||||
@ -308,7 +308,7 @@ final class StickerPackEmojisItemNode: GridItemNode {
|
|||||||
let animationRenderer = item.animationRenderer
|
let animationRenderer = item.animationRenderer
|
||||||
let theme = item.theme
|
let theme = item.theme
|
||||||
let items = item.items
|
let items = item.items
|
||||||
var validIds = Set<EmojiPagerContentComponent.View.ItemLayer.Key>()
|
var validIds = Set<EmojiKeyboardItemLayer.Key>()
|
||||||
|
|
||||||
let itemLayout: ItemLayout
|
let itemLayout: ItemLayout
|
||||||
if let current = self.itemLayout, current.width == self.size.width && current.itemsCount == items.count {
|
if let current = self.itemLayout, current.width == self.size.width && current.itemsCount == items.count {
|
||||||
@ -322,7 +322,7 @@ final class StickerPackEmojisItemNode: GridItemNode {
|
|||||||
|
|
||||||
for index in 0 ..< items.count {
|
for index in 0 ..< items.count {
|
||||||
let item = items[index]
|
let item = items[index]
|
||||||
let itemId = EmojiPagerContentComponent.View.ItemLayer.Key(
|
let itemId = EmojiKeyboardItemLayer.Key(
|
||||||
groupId: 0,
|
groupId: 0,
|
||||||
itemId: .animation(.file(item.file.fileId))
|
itemId: .animation(.file(item.file.fileId))
|
||||||
)
|
)
|
||||||
@ -334,7 +334,7 @@ final class StickerPackEmojisItemNode: GridItemNode {
|
|||||||
|
|
||||||
var updateItemLayerPlaceholder = false
|
var updateItemLayerPlaceholder = false
|
||||||
var itemTransition = transition
|
var itemTransition = transition
|
||||||
let itemLayer: EmojiPagerContentComponent.View.ItemLayer
|
let itemLayer: EmojiKeyboardItemLayer
|
||||||
if let current = self.visibleItemLayers[itemId] {
|
if let current = self.visibleItemLayers[itemId] {
|
||||||
itemLayer = current
|
itemLayer = current
|
||||||
} else {
|
} else {
|
||||||
@ -342,7 +342,7 @@ final class StickerPackEmojisItemNode: GridItemNode {
|
|||||||
itemTransition = .immediate
|
itemTransition = .immediate
|
||||||
|
|
||||||
let animationData = EntityKeyboardAnimationData(file: item.file)
|
let animationData = EntityKeyboardAnimationData(file: item.file)
|
||||||
itemLayer = EmojiPagerContentComponent.View.ItemLayer(
|
itemLayer = EmojiKeyboardItemLayer(
|
||||||
item: EmojiPagerContentComponent.Item(
|
item: EmojiPagerContentComponent.Item(
|
||||||
animationData: animationData,
|
animationData: animationData,
|
||||||
content: .animation(animationData),
|
content: .animation(animationData),
|
||||||
|
@ -638,6 +638,8 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
|||||||
private var replyRecognizer: ChatSwipeToReplyRecognizer?
|
private var replyRecognizer: ChatSwipeToReplyRecognizer?
|
||||||
private var currentSwipeAction: ChatControllerInteractionSwipeAction?
|
private var currentSwipeAction: ChatControllerInteractionSwipeAction?
|
||||||
|
|
||||||
|
private var fetchEffectDisposable: Disposable?
|
||||||
|
|
||||||
//private let debugNode: ASDisplayNode
|
//private let debugNode: ASDisplayNode
|
||||||
|
|
||||||
override public var visibility: ListViewItemNodeVisibility {
|
override public var visibility: ListViewItemNodeVisibility {
|
||||||
@ -838,6 +840,10 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
|||||||
required public init?(coder aDecoder: NSCoder) {
|
required public init?(coder aDecoder: NSCoder) {
|
||||||
fatalError("init(coder:) has not been implemented")
|
fatalError("init(coder:) has not been implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
self.fetchEffectDisposable?.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
override public func cancelInsertionAnimations() {
|
override public func cancelInsertionAnimations() {
|
||||||
self.shadowNode.layer.removeAllAnimations()
|
self.shadowNode.layer.removeAllAnimations()
|
||||||
@ -5877,6 +5883,9 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
|||||||
private var additionalAnimationNodes: [ChatMessageTransitionNode.DecorationItemNode] = []
|
private var additionalAnimationNodes: [ChatMessageTransitionNode.DecorationItemNode] = []
|
||||||
|
|
||||||
private func playPremiumStickerAnimation(effect: AvailableMessageEffects.MessageEffect, force: Bool) {
|
private func playPremiumStickerAnimation(effect: AvailableMessageEffects.MessageEffect, force: Bool) {
|
||||||
|
guard let item = self.item else {
|
||||||
|
return
|
||||||
|
}
|
||||||
if self.playedPremiumStickerAnimation && !force {
|
if self.playedPremiumStickerAnimation && !force {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -5884,10 +5893,16 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
|||||||
|
|
||||||
if let effectAnimation = effect.effectAnimation {
|
if let effectAnimation = effect.effectAnimation {
|
||||||
self.playEffectAnimation(resource: effectAnimation.resource, isStickerEffect: true)
|
self.playEffectAnimation(resource: effectAnimation.resource, isStickerEffect: true)
|
||||||
|
if self.fetchEffectDisposable == nil {
|
||||||
|
self.fetchEffectDisposable = freeMediaFileResourceInteractiveFetched(account: item.context.account, userLocation: .other, fileReference: .standalone(media: effectAnimation), resource: effectAnimation.resource).startStrict()
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
let effectSticker = effect.effectSticker
|
let effectSticker = effect.effectSticker
|
||||||
if let effectFile = effectSticker.videoThumbnails.first {
|
if let effectFile = effectSticker.videoThumbnails.first {
|
||||||
self.playEffectAnimation(resource: effectFile.resource, isStickerEffect: true)
|
self.playEffectAnimation(resource: effectFile.resource, isStickerEffect: true)
|
||||||
|
if self.fetchEffectDisposable == nil {
|
||||||
|
self.fetchEffectDisposable = freeMediaFileResourceInteractiveFetched(account: item.context.account, userLocation: .other, fileReference: .standalone(media: effectSticker), resource: effectFile.resource).startStrict()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,477 @@
|
|||||||
|
import Foundation
|
||||||
|
import UIKit
|
||||||
|
import Display
|
||||||
|
import ComponentFlow
|
||||||
|
import MultiAnimationRenderer
|
||||||
|
import AnimationCache
|
||||||
|
import SwiftSignalKit
|
||||||
|
import TelegramCore
|
||||||
|
import AccountContext
|
||||||
|
import TelegramPresentationData
|
||||||
|
import EmojiTextAttachmentView
|
||||||
|
import EmojiStatusComponent
|
||||||
|
|
||||||
|
final class EmojiKeyboardCloneItemLayer: SimpleLayer {
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class EmojiKeyboardItemLayer: MultiAnimationRenderTarget {
|
||||||
|
public struct Key: Hashable {
|
||||||
|
var groupId: AnyHashable
|
||||||
|
var itemId: EmojiPagerContentComponent.ItemContent.Id
|
||||||
|
|
||||||
|
public init(
|
||||||
|
groupId: AnyHashable,
|
||||||
|
itemId: EmojiPagerContentComponent.ItemContent.Id
|
||||||
|
) {
|
||||||
|
self.groupId = groupId
|
||||||
|
self.itemId = itemId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Badge: Equatable {
|
||||||
|
case premium
|
||||||
|
case locked
|
||||||
|
case featured
|
||||||
|
case text(String)
|
||||||
|
case customFile(TelegramMediaFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
public let item: EmojiPagerContentComponent.Item
|
||||||
|
private let context: AccountContext
|
||||||
|
|
||||||
|
private var content: EmojiPagerContentComponent.ItemContent
|
||||||
|
private var theme: PresentationTheme?
|
||||||
|
|
||||||
|
private let placeholderColor: UIColor
|
||||||
|
let pixelSize: CGSize
|
||||||
|
let pointSize: CGSize
|
||||||
|
private let size: CGSize
|
||||||
|
private var disposable: Disposable?
|
||||||
|
private var fetchDisposable: Disposable?
|
||||||
|
private var premiumBadgeView: PremiumBadgeView?
|
||||||
|
|
||||||
|
private var iconLayer: SimpleLayer?
|
||||||
|
private var tintIconLayer: SimpleLayer?
|
||||||
|
|
||||||
|
private(set) var tintContentLayer: SimpleLayer?
|
||||||
|
|
||||||
|
private var badge: Badge?
|
||||||
|
private var validSize: CGSize?
|
||||||
|
|
||||||
|
private var isInHierarchyValue: Bool = false
|
||||||
|
public var isVisibleForAnimations: Bool = false {
|
||||||
|
didSet {
|
||||||
|
if self.isVisibleForAnimations != oldValue {
|
||||||
|
self.updatePlayback()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public private(set) var displayPlaceholder: Bool = false
|
||||||
|
public let onUpdateDisplayPlaceholder: (Bool, Double) -> Void
|
||||||
|
|
||||||
|
weak var cloneLayer: EmojiKeyboardCloneItemLayer? {
|
||||||
|
didSet {
|
||||||
|
if let cloneLayer = self.cloneLayer {
|
||||||
|
cloneLayer.contents = self.contents
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override public var contents: Any? {
|
||||||
|
didSet {
|
||||||
|
self.onContentsUpdate()
|
||||||
|
if let cloneLayer = self.cloneLayer {
|
||||||
|
cloneLayer.contents = self.contents
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override public var position: CGPoint {
|
||||||
|
get {
|
||||||
|
return super.position
|
||||||
|
} set(value) {
|
||||||
|
if let mirrorLayer = self.tintContentLayer {
|
||||||
|
mirrorLayer.position = value
|
||||||
|
}
|
||||||
|
super.position = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override public var bounds: CGRect {
|
||||||
|
get {
|
||||||
|
return super.bounds
|
||||||
|
} set(value) {
|
||||||
|
if let mirrorLayer = self.tintContentLayer {
|
||||||
|
mirrorLayer.bounds = value
|
||||||
|
}
|
||||||
|
super.bounds = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override public func add(_ animation: CAAnimation, forKey key: String?) {
|
||||||
|
if let mirrorLayer = self.tintContentLayer {
|
||||||
|
mirrorLayer.add(animation, forKey: key)
|
||||||
|
}
|
||||||
|
|
||||||
|
super.add(animation, forKey: key)
|
||||||
|
}
|
||||||
|
|
||||||
|
override public func removeAllAnimations() {
|
||||||
|
if let mirrorLayer = self.tintContentLayer {
|
||||||
|
mirrorLayer.removeAllAnimations()
|
||||||
|
}
|
||||||
|
|
||||||
|
super.removeAllAnimations()
|
||||||
|
}
|
||||||
|
|
||||||
|
override public func removeAnimation(forKey: String) {
|
||||||
|
if let mirrorLayer = self.tintContentLayer {
|
||||||
|
mirrorLayer.removeAnimation(forKey: forKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
super.removeAnimation(forKey: forKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
public var onContentsUpdate: () -> Void = {}
|
||||||
|
public var onLoop: () -> Void = {}
|
||||||
|
|
||||||
|
public init(
|
||||||
|
item: EmojiPagerContentComponent.Item,
|
||||||
|
context: AccountContext,
|
||||||
|
attemptSynchronousLoad: Bool,
|
||||||
|
content: EmojiPagerContentComponent.ItemContent,
|
||||||
|
cache: AnimationCache,
|
||||||
|
renderer: MultiAnimationRenderer,
|
||||||
|
placeholderColor: UIColor,
|
||||||
|
blurredBadgeColor: UIColor,
|
||||||
|
accentIconColor: UIColor,
|
||||||
|
pointSize: CGSize,
|
||||||
|
onUpdateDisplayPlaceholder: @escaping (Bool, Double) -> Void
|
||||||
|
) {
|
||||||
|
self.item = item
|
||||||
|
self.context = context
|
||||||
|
self.content = content
|
||||||
|
self.placeholderColor = placeholderColor
|
||||||
|
self.onUpdateDisplayPlaceholder = onUpdateDisplayPlaceholder
|
||||||
|
|
||||||
|
let scale = min(2.0, UIScreenScale)
|
||||||
|
let pixelSize = CGSize(width: pointSize.width * scale, height: pointSize.height * scale)
|
||||||
|
self.pixelSize = pixelSize
|
||||||
|
self.pointSize = pointSize
|
||||||
|
self.size = CGSize(width: pixelSize.width / scale, height: pixelSize.height / scale)
|
||||||
|
|
||||||
|
super.init()
|
||||||
|
|
||||||
|
switch content {
|
||||||
|
case let .animation(animationData):
|
||||||
|
let loadAnimation: () -> Void = { [weak self] in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
strongSelf.disposable = renderer.add(target: strongSelf, cache: cache, itemId: animationData.resource.resource.id.stringRepresentation, unique: false, size: pixelSize, fetch: animationCacheFetchFile(context: context, userLocation: .other, userContentType: .sticker, resource: animationData.resource, type: animationData.type.animationCacheAnimationType, keyframeOnly: pixelSize.width >= 120.0, customColor: animationData.isTemplate ? .white : nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
if attemptSynchronousLoad {
|
||||||
|
if !renderer.loadFirstFrameSynchronously(target: self, cache: cache, itemId: animationData.resource.resource.id.stringRepresentation, size: pixelSize) {
|
||||||
|
self.updateDisplayPlaceholder(displayPlaceholder: true)
|
||||||
|
|
||||||
|
self.fetchDisposable = renderer.loadFirstFrame(target: self, cache: cache, itemId: animationData.resource.resource.id.stringRepresentation, size: pixelSize, fetch: animationCacheFetchFile(context: context, userLocation: .other, userContentType: .sticker, resource: animationData.resource, type: animationData.type.animationCacheAnimationType, keyframeOnly: true, customColor: animationData.isTemplate ? .white : nil), completion: { [weak self] success, isFinal in
|
||||||
|
if !isFinal {
|
||||||
|
if !success {
|
||||||
|
Queue.mainQueue().async {
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
strongSelf.updateDisplayPlaceholder(displayPlaceholder: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
Queue.mainQueue().async {
|
||||||
|
loadAnimation()
|
||||||
|
|
||||||
|
if !success {
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
strongSelf.updateDisplayPlaceholder(displayPlaceholder: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
loadAnimation()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.fetchDisposable = renderer.loadFirstFrame(target: self, cache: cache, itemId: animationData.resource.resource.id.stringRepresentation, size: pixelSize, fetch: animationCacheFetchFile(context: context, userLocation: .other, userContentType: .sticker, resource: animationData.resource, type: animationData.type.animationCacheAnimationType, keyframeOnly: true, customColor: animationData.isTemplate ? .white : nil), completion: { [weak self] success, isFinal in
|
||||||
|
if !isFinal {
|
||||||
|
if !success {
|
||||||
|
Queue.mainQueue().async {
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
strongSelf.updateDisplayPlaceholder(displayPlaceholder: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
Queue.mainQueue().async {
|
||||||
|
loadAnimation()
|
||||||
|
|
||||||
|
if !success {
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
strongSelf.updateDisplayPlaceholder(displayPlaceholder: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
case let .staticEmoji(staticEmoji):
|
||||||
|
let image = generateImage(pointSize, opaque: false, scale: min(UIScreenScale, 3.0), rotatedContext: { size, context in
|
||||||
|
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||||
|
|
||||||
|
let preScaleFactor: CGFloat = 1.0
|
||||||
|
let scaledSize = CGSize(width: floor(size.width * preScaleFactor), height: floor(size.height * preScaleFactor))
|
||||||
|
let scaleFactor = scaledSize.width / size.width
|
||||||
|
|
||||||
|
context.scaleBy(x: 1.0 / scaleFactor, y: 1.0 / scaleFactor)
|
||||||
|
|
||||||
|
let string = NSAttributedString(string: staticEmoji, font: Font.regular(floor(32.0 * scaleFactor)), textColor: .black)
|
||||||
|
let boundingRect = string.boundingRect(with: scaledSize, options: .usesLineFragmentOrigin, context: nil)
|
||||||
|
UIGraphicsPushContext(context)
|
||||||
|
string.draw(at: CGPoint(x: floorToScreenPixels((scaledSize.width - boundingRect.width) / 2.0 + boundingRect.minX), y: floorToScreenPixels((scaledSize.height - boundingRect.height) / 2.0 + boundingRect.minY)))
|
||||||
|
UIGraphicsPopContext()
|
||||||
|
})
|
||||||
|
self.contents = image?.cgImage
|
||||||
|
case let .icon(icon):
|
||||||
|
let image = generateImage(pointSize, opaque: false, scale: min(UIScreenScale, 3.0), rotatedContext: { size, context in
|
||||||
|
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||||
|
|
||||||
|
UIGraphicsPushContext(context)
|
||||||
|
|
||||||
|
switch icon {
|
||||||
|
case .premiumStar:
|
||||||
|
if let image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Media/EntityInputPremiumIcon"), color: accentIconColor) {
|
||||||
|
let imageSize = image.size.aspectFitted(CGSize(width: size.width - 6.0, height: size.height - 6.0))
|
||||||
|
image.draw(in: CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) / 2.0), y: floor((size.height - imageSize.height) / 2.0)), size: imageSize))
|
||||||
|
}
|
||||||
|
case let .topic(title, color):
|
||||||
|
let colors = topicIconColors(for: color)
|
||||||
|
if let image = generateTopicIcon(backgroundColors: colors.0.map { UIColor(rgb: $0) }, strokeColors: colors.1.map { UIColor(rgb: $0) }, title: title) {
|
||||||
|
let imageSize = image.size//.aspectFitted(CGSize(width: size.width - 6.0, height: size.height - 6.0))
|
||||||
|
image.draw(in: CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) / 2.0), y: floor((size.height - imageSize.height) / 2.0)), size: imageSize))
|
||||||
|
}
|
||||||
|
case .stop:
|
||||||
|
if let image = generateTintedImage(image: UIImage(bundleImageName: "Premium/NoIcon"), color: .white) {
|
||||||
|
let imageSize = image.size.aspectFitted(CGSize(width: size.width - 6.0, height: size.height - 6.0))
|
||||||
|
image.draw(in: CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) / 2.0), y: floor((size.height - imageSize.height) / 2.0)), size: imageSize))
|
||||||
|
}
|
||||||
|
case .add:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
UIGraphicsPopContext()
|
||||||
|
})?.withRenderingMode(icon == .stop ? .alwaysTemplate : .alwaysOriginal)
|
||||||
|
self.contents = image?.cgImage
|
||||||
|
}
|
||||||
|
|
||||||
|
if case .icon(.add) = content {
|
||||||
|
let tintContentLayer = SimpleLayer()
|
||||||
|
self.tintContentLayer = tintContentLayer
|
||||||
|
|
||||||
|
let iconLayer = SimpleLayer()
|
||||||
|
self.iconLayer = iconLayer
|
||||||
|
self.addSublayer(iconLayer)
|
||||||
|
|
||||||
|
let tintIconLayer = SimpleLayer()
|
||||||
|
self.tintIconLayer = tintIconLayer
|
||||||
|
tintContentLayer.addSublayer(tintIconLayer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override public init(layer: Any) {
|
||||||
|
guard let layer = layer as? EmojiKeyboardItemLayer else {
|
||||||
|
preconditionFailure()
|
||||||
|
}
|
||||||
|
|
||||||
|
self.context = layer.context
|
||||||
|
self.item = layer.item
|
||||||
|
|
||||||
|
self.content = layer.content
|
||||||
|
self.placeholderColor = layer.placeholderColor
|
||||||
|
self.size = layer.size
|
||||||
|
self.pixelSize = layer.pixelSize
|
||||||
|
self.pointSize = layer.pointSize
|
||||||
|
|
||||||
|
self.onUpdateDisplayPlaceholder = { _, _ in }
|
||||||
|
|
||||||
|
super.init(layer: layer)
|
||||||
|
}
|
||||||
|
|
||||||
|
required public init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
self.disposable?.dispose()
|
||||||
|
self.fetchDisposable?.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
public override func action(forKey event: String) -> CAAction? {
|
||||||
|
if event == kCAOnOrderIn {
|
||||||
|
self.isInHierarchyValue = true
|
||||||
|
} else if event == kCAOnOrderOut {
|
||||||
|
self.isInHierarchyValue = false
|
||||||
|
}
|
||||||
|
self.updatePlayback()
|
||||||
|
return nullAction
|
||||||
|
}
|
||||||
|
|
||||||
|
func update(
|
||||||
|
content: EmojiPagerContentComponent.ItemContent,
|
||||||
|
theme: PresentationTheme
|
||||||
|
) {
|
||||||
|
var themeUpdated = false
|
||||||
|
if self.theme !== theme {
|
||||||
|
self.theme = theme
|
||||||
|
themeUpdated = true
|
||||||
|
}
|
||||||
|
var contentUpdated = false
|
||||||
|
if self.content != content {
|
||||||
|
self.content = content
|
||||||
|
contentUpdated = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if themeUpdated || contentUpdated {
|
||||||
|
if case let .icon(icon) = content, case let .topic(title, color) = icon {
|
||||||
|
let image = generateImage(self.size, opaque: false, scale: min(UIScreenScale, 3.0), rotatedContext: { size, context in
|
||||||
|
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||||
|
|
||||||
|
UIGraphicsPushContext(context)
|
||||||
|
|
||||||
|
let colors = topicIconColors(for: color)
|
||||||
|
if let image = generateTopicIcon(backgroundColors: colors.0.map { UIColor(rgb: $0) }, strokeColors: colors.1.map { UIColor(rgb: $0) }, title: title) {
|
||||||
|
let imageSize = image.size
|
||||||
|
image.draw(in: CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) / 2.0), y: floor((size.height - imageSize.height) / 2.0)), size: imageSize))
|
||||||
|
}
|
||||||
|
|
||||||
|
UIGraphicsPopContext()
|
||||||
|
})
|
||||||
|
self.contents = image?.cgImage
|
||||||
|
} else if case .icon(.add) = content {
|
||||||
|
guard let iconLayer = self.iconLayer, let tintIconLayer = self.tintIconLayer else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func generateIcon(color: UIColor) -> UIImage? {
|
||||||
|
return generateImage(self.pointSize, opaque: false, scale: min(UIScreenScale, 3.0), rotatedContext: { size, context in
|
||||||
|
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||||
|
|
||||||
|
UIGraphicsPushContext(context)
|
||||||
|
|
||||||
|
context.setFillColor(color.withMultipliedAlpha(0.2).cgColor)
|
||||||
|
context.fillEllipse(in: CGRect(origin: .zero, size: size).insetBy(dx: 8.0, dy: 8.0))
|
||||||
|
context.setFillColor(color.cgColor)
|
||||||
|
|
||||||
|
let plusSize = CGSize(width: 4.5, height: 31.5)
|
||||||
|
context.addPath(UIBezierPath(roundedRect: CGRect(x: floorToScreenPixels((size.width - plusSize.width) / 2.0), y: floorToScreenPixels((size.height - plusSize.height) / 2.0), width: plusSize.width, height: plusSize.height), cornerRadius: plusSize.width / 2.0).cgPath)
|
||||||
|
context.addPath(UIBezierPath(roundedRect: CGRect(x: floorToScreenPixels((size.width - plusSize.height) / 2.0), y: floorToScreenPixels((size.height - plusSize.width) / 2.0), width: plusSize.height, height: plusSize.width), cornerRadius: plusSize.width / 2.0).cgPath)
|
||||||
|
context.fillPath()
|
||||||
|
|
||||||
|
UIGraphicsPopContext()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
let needsVibrancy = !theme.overallDarkAppearance
|
||||||
|
let color = theme.chat.inputMediaPanel.panelContentVibrantOverlayColor
|
||||||
|
|
||||||
|
iconLayer.contents = generateIcon(color: color)?.cgImage
|
||||||
|
tintIconLayer.contents = generateIcon(color: .white)?.cgImage
|
||||||
|
|
||||||
|
tintIconLayer.isHidden = !needsVibrancy
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func update(
|
||||||
|
transition: Transition,
|
||||||
|
size: CGSize,
|
||||||
|
badge: Badge?,
|
||||||
|
blurredBadgeColor: UIColor,
|
||||||
|
blurredBadgeBackgroundColor: UIColor
|
||||||
|
) {
|
||||||
|
if self.badge != badge || self.validSize != size {
|
||||||
|
self.badge = badge
|
||||||
|
self.validSize = size
|
||||||
|
|
||||||
|
if let iconLayer = self.iconLayer, let tintIconLayer = self.tintIconLayer {
|
||||||
|
transition.setFrame(layer: iconLayer, frame: CGRect(origin: .zero, size: size))
|
||||||
|
transition.setFrame(layer: tintIconLayer, frame: CGRect(origin: .zero, size: size))
|
||||||
|
}
|
||||||
|
|
||||||
|
if let badge = badge {
|
||||||
|
var badgeTransition = transition
|
||||||
|
let premiumBadgeView: PremiumBadgeView
|
||||||
|
if let current = self.premiumBadgeView {
|
||||||
|
premiumBadgeView = current
|
||||||
|
} else {
|
||||||
|
badgeTransition = .immediate
|
||||||
|
premiumBadgeView = PremiumBadgeView(context: self.context)
|
||||||
|
self.premiumBadgeView = premiumBadgeView
|
||||||
|
self.addSublayer(premiumBadgeView.layer)
|
||||||
|
}
|
||||||
|
|
||||||
|
let badgeDiameter = min(16.0, floor(size.height * 0.5))
|
||||||
|
let badgeSize = CGSize(width: badgeDiameter, height: badgeDiameter)
|
||||||
|
badgeTransition.setFrame(view: premiumBadgeView, frame: CGRect(origin: CGPoint(x: size.width - badgeSize.width, y: size.height - badgeSize.height), size: badgeSize))
|
||||||
|
premiumBadgeView.update(transition: badgeTransition, badge: badge, backgroundColor: blurredBadgeColor, size: badgeSize)
|
||||||
|
|
||||||
|
self.blurredRepresentationBackgroundColor = blurredBadgeBackgroundColor
|
||||||
|
self.blurredRepresentationTarget = premiumBadgeView.contentLayer
|
||||||
|
} else {
|
||||||
|
if let premiumBadgeView = self.premiumBadgeView {
|
||||||
|
self.premiumBadgeView = nil
|
||||||
|
premiumBadgeView.removeFromSuperview()
|
||||||
|
|
||||||
|
self.blurredRepresentationBackgroundColor = nil
|
||||||
|
self.blurredRepresentationTarget = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func updatePlayback() {
|
||||||
|
let shouldBePlaying = self.isInHierarchyValue && self.isVisibleForAnimations
|
||||||
|
|
||||||
|
self.shouldBeAnimating = shouldBePlaying
|
||||||
|
}
|
||||||
|
|
||||||
|
public override func updateDisplayPlaceholder(displayPlaceholder: Bool) {
|
||||||
|
if self.displayPlaceholder == displayPlaceholder {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
self.displayPlaceholder = displayPlaceholder
|
||||||
|
self.onUpdateDisplayPlaceholder(displayPlaceholder, 0.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
public override func transitionToContents(_ contents: AnyObject, didLoop: Bool) {
|
||||||
|
self.contents = contents
|
||||||
|
|
||||||
|
if self.displayPlaceholder {
|
||||||
|
self.displayPlaceholder = false
|
||||||
|
self.onUpdateDisplayPlaceholder(false, 0.2)
|
||||||
|
self.animateAlpha(from: 0.0, to: 1.0, duration: 0.18)
|
||||||
|
}
|
||||||
|
|
||||||
|
if didLoop {
|
||||||
|
self.onLoop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,607 @@
|
|||||||
|
import Foundation
|
||||||
|
import UIKit
|
||||||
|
import Display
|
||||||
|
import ComponentFlow
|
||||||
|
import TelegramCore
|
||||||
|
import TelegramPresentationData
|
||||||
|
import AccountContext
|
||||||
|
import SwiftSignalKit
|
||||||
|
|
||||||
|
public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate {
|
||||||
|
private final class EmojiSearchTextField: UITextField {
|
||||||
|
override func textRect(forBounds bounds: CGRect) -> CGRect {
|
||||||
|
return bounds.integral
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct Params: Equatable {
|
||||||
|
var context: AccountContext
|
||||||
|
var theme: PresentationTheme
|
||||||
|
var forceNeedsVibrancy: Bool
|
||||||
|
var strings: PresentationStrings
|
||||||
|
var text: String
|
||||||
|
var useOpaqueTheme: Bool
|
||||||
|
var isActive: Bool
|
||||||
|
var hasPresetSearch: Bool
|
||||||
|
var textInputState: EmojiSearchSearchBarComponent.TextInputState
|
||||||
|
var searchState: EmojiPagerContentComponent.SearchState
|
||||||
|
var size: CGSize
|
||||||
|
var canFocus: Bool
|
||||||
|
var searchCategories: EmojiSearchCategories?
|
||||||
|
|
||||||
|
static func ==(lhs: Params, rhs: Params) -> Bool {
|
||||||
|
if lhs.context !== rhs.context {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.theme !== rhs.theme {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.forceNeedsVibrancy != rhs.forceNeedsVibrancy {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.strings !== rhs.strings {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.text != rhs.text {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.useOpaqueTheme != rhs.useOpaqueTheme {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.isActive != rhs.isActive {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.hasPresetSearch != rhs.hasPresetSearch {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.textInputState != rhs.textInputState {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.searchState != rhs.searchState {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.size != rhs.size {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.canFocus != rhs.canFocus {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.searchCategories != rhs.searchCategories {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override public static var layerClass: AnyClass {
|
||||||
|
return PassthroughLayer.self
|
||||||
|
}
|
||||||
|
|
||||||
|
private let activated: (Bool) -> Void
|
||||||
|
private let deactivated: (Bool) -> Void
|
||||||
|
private let updateQuery: (EmojiPagerContentComponent.SearchQuery?) -> Void
|
||||||
|
|
||||||
|
let tintContainerView: UIView
|
||||||
|
|
||||||
|
private let backgroundLayer: SimpleLayer
|
||||||
|
private let tintBackgroundLayer: SimpleLayer
|
||||||
|
|
||||||
|
private let statusIcon = ComponentView<Empty>()
|
||||||
|
|
||||||
|
private let clearIconView: UIImageView
|
||||||
|
private let clearIconTintView: UIImageView
|
||||||
|
private let clearIconButton: HighlightTrackingButton
|
||||||
|
|
||||||
|
private let cancelButtonTintTitle: ComponentView<Empty>
|
||||||
|
private let cancelButtonTitle: ComponentView<Empty>
|
||||||
|
private let cancelButton: HighlightTrackingButton
|
||||||
|
|
||||||
|
private var placeholderContent = ComponentView<Empty>()
|
||||||
|
|
||||||
|
private var textFrame: CGRect?
|
||||||
|
private var textField: EmojiSearchTextField?
|
||||||
|
|
||||||
|
private var tapRecognizer: UITapGestureRecognizer?
|
||||||
|
private(set) var currentPresetSearchTerm: EmojiSearchCategories.Group?
|
||||||
|
|
||||||
|
private var params: Params?
|
||||||
|
|
||||||
|
public var wantsDisplayBelowKeyboard: Bool {
|
||||||
|
return self.textField != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
init(activated: @escaping (Bool) -> Void, deactivated: @escaping (Bool) -> Void, updateQuery: @escaping (EmojiPagerContentComponent.SearchQuery?) -> Void) {
|
||||||
|
self.activated = activated
|
||||||
|
self.deactivated = deactivated
|
||||||
|
self.updateQuery = updateQuery
|
||||||
|
|
||||||
|
self.tintContainerView = UIView()
|
||||||
|
|
||||||
|
self.backgroundLayer = SimpleLayer()
|
||||||
|
self.tintBackgroundLayer = SimpleLayer()
|
||||||
|
|
||||||
|
self.clearIconView = UIImageView()
|
||||||
|
self.clearIconTintView = UIImageView()
|
||||||
|
self.clearIconButton = HighlightableButton()
|
||||||
|
self.clearIconView.isHidden = true
|
||||||
|
self.clearIconTintView.isHidden = true
|
||||||
|
self.clearIconButton.isHidden = true
|
||||||
|
|
||||||
|
self.cancelButtonTintTitle = ComponentView()
|
||||||
|
self.cancelButtonTitle = ComponentView()
|
||||||
|
self.cancelButton = HighlightTrackingButton()
|
||||||
|
|
||||||
|
super.init(frame: CGRect())
|
||||||
|
|
||||||
|
self.layer.addSublayer(self.backgroundLayer)
|
||||||
|
self.tintContainerView.layer.addSublayer(self.tintBackgroundLayer)
|
||||||
|
|
||||||
|
self.addSubview(self.clearIconView)
|
||||||
|
self.tintContainerView.addSubview(self.clearIconTintView)
|
||||||
|
self.addSubview(self.clearIconButton)
|
||||||
|
|
||||||
|
self.addSubview(self.cancelButton)
|
||||||
|
self.clipsToBounds = true
|
||||||
|
|
||||||
|
(self.layer as? PassthroughLayer)?.mirrorLayer = self.tintContainerView.layer
|
||||||
|
|
||||||
|
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:)))
|
||||||
|
self.tapRecognizer = tapRecognizer
|
||||||
|
self.addGestureRecognizer(tapRecognizer)
|
||||||
|
|
||||||
|
self.cancelButton.highligthedChanged = { [weak self] highlighted in
|
||||||
|
if let strongSelf = self {
|
||||||
|
if highlighted {
|
||||||
|
if let cancelButtonTitleView = strongSelf.cancelButtonTitle.view {
|
||||||
|
cancelButtonTitleView.layer.removeAnimation(forKey: "opacity")
|
||||||
|
cancelButtonTitleView.alpha = 0.4
|
||||||
|
}
|
||||||
|
if let cancelButtonTintTitleView = strongSelf.cancelButtonTintTitle.view {
|
||||||
|
cancelButtonTintTitleView.layer.removeAnimation(forKey: "opacity")
|
||||||
|
cancelButtonTintTitleView.alpha = 0.4
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if let cancelButtonTitleView = strongSelf.cancelButtonTitle.view {
|
||||||
|
cancelButtonTitleView.alpha = 1.0
|
||||||
|
cancelButtonTitleView.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
|
||||||
|
}
|
||||||
|
if let cancelButtonTintTitleView = strongSelf.cancelButtonTintTitle.view {
|
||||||
|
cancelButtonTintTitleView.alpha = 1.0
|
||||||
|
cancelButtonTintTitleView.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.cancelButton.addTarget(self, action: #selector(self.cancelPressed), for: .touchUpInside)
|
||||||
|
|
||||||
|
self.clearIconButton.highligthedChanged = { [weak self] highlighted in
|
||||||
|
if let strongSelf = self {
|
||||||
|
if highlighted {
|
||||||
|
strongSelf.clearIconView.layer.removeAnimation(forKey: "opacity")
|
||||||
|
strongSelf.clearIconView.alpha = 0.4
|
||||||
|
strongSelf.clearIconTintView.layer.removeAnimation(forKey: "opacity")
|
||||||
|
strongSelf.clearIconTintView.alpha = 0.4
|
||||||
|
} else {
|
||||||
|
strongSelf.clearIconView.alpha = 1.0
|
||||||
|
strongSelf.clearIconView.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
|
||||||
|
strongSelf.clearIconTintView.alpha = 1.0
|
||||||
|
strongSelf.clearIconTintView.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.clearIconButton.addTarget(self, action: #selector(self.clearPressed), for: .touchUpInside)
|
||||||
|
}
|
||||||
|
|
||||||
|
required public init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func tapGesture(_ recognizer: UITapGestureRecognizer) {
|
||||||
|
if case .ended = recognizer.state {
|
||||||
|
let location = recognizer.location(in: self)
|
||||||
|
if let view = self.statusIcon.view, view.frame.contains(location), self.currentPresetSearchTerm != nil {
|
||||||
|
self.clearCategorySearch()
|
||||||
|
} else {
|
||||||
|
self.activateTextInput()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func clearCategorySearch() {
|
||||||
|
if let placeholderContentView = self.placeholderContent.view as? EmojiSearchSearchBarComponent.View {
|
||||||
|
placeholderContentView.clearSelection(dispatchEvent : true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func activateTextInput() {
|
||||||
|
guard let params = self.params else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if self.textField == nil, let textFrame = self.textFrame, params.canFocus == true {
|
||||||
|
let backgroundFrame = self.backgroundLayer.frame
|
||||||
|
let textFieldFrame = CGRect(origin: CGPoint(x: textFrame.minX, y: backgroundFrame.minY), size: CGSize(width: backgroundFrame.maxX - textFrame.minX, height: backgroundFrame.height))
|
||||||
|
|
||||||
|
let textField = EmojiSearchTextField(frame: textFieldFrame)
|
||||||
|
textField.keyboardAppearance = params.theme.rootController.keyboardColor.keyboardAppearance
|
||||||
|
textField.autocorrectionType = .no
|
||||||
|
textField.returnKeyType = .search
|
||||||
|
self.textField = textField
|
||||||
|
self.insertSubview(textField, belowSubview: self.clearIconView)
|
||||||
|
textField.delegate = self
|
||||||
|
textField.addTarget(self, action: #selector(self.textFieldChanged(_:)), for: .editingChanged)
|
||||||
|
}
|
||||||
|
|
||||||
|
if params.canFocus {
|
||||||
|
self.currentPresetSearchTerm = nil
|
||||||
|
if let placeholderContentView = self.placeholderContent.view as? EmojiSearchSearchBarComponent.View {
|
||||||
|
placeholderContentView.clearSelection(dispatchEvent: false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.activated(true)
|
||||||
|
|
||||||
|
self.textField?.becomeFirstResponder()
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func cancelPressed() {
|
||||||
|
self.currentPresetSearchTerm = nil
|
||||||
|
self.updateQuery(nil)
|
||||||
|
|
||||||
|
self.clearIconView.isHidden = true
|
||||||
|
self.clearIconTintView.isHidden = true
|
||||||
|
self.clearIconButton.isHidden = true
|
||||||
|
|
||||||
|
let textField = self.textField
|
||||||
|
self.textField = nil
|
||||||
|
|
||||||
|
self.deactivated(textField?.isFirstResponder ?? false)
|
||||||
|
|
||||||
|
if let textField {
|
||||||
|
textField.resignFirstResponder()
|
||||||
|
textField.removeFromSuperview()
|
||||||
|
}
|
||||||
|
|
||||||
|
/*self.tintTextView.view?.isHidden = false
|
||||||
|
self.textView.view?.isHidden = false*/
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func clearPressed() {
|
||||||
|
self.currentPresetSearchTerm = nil
|
||||||
|
self.updateQuery(nil)
|
||||||
|
self.textField?.text = ""
|
||||||
|
|
||||||
|
self.clearIconView.isHidden = true
|
||||||
|
self.clearIconTintView.isHidden = true
|
||||||
|
self.clearIconButton.isHidden = true
|
||||||
|
|
||||||
|
/*self.tintTextView.view?.isHidden = false
|
||||||
|
self.textView.view?.isHidden = false*/
|
||||||
|
}
|
||||||
|
|
||||||
|
var isActive: Bool {
|
||||||
|
return self.textField?.isFirstResponder ?? false
|
||||||
|
}
|
||||||
|
|
||||||
|
func deactivate() {
|
||||||
|
if let text = self.textField?.text, !text.isEmpty {
|
||||||
|
self.textField?.endEditing(true)
|
||||||
|
} else {
|
||||||
|
self.cancelPressed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func textFieldDidBeginEditing(_ textField: UITextField) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public func textFieldDidEndEditing(_ textField: UITextField) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public func textFieldShouldReturn(_ textField: UITextField) -> Bool {
|
||||||
|
textField.endEditing(true)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func textFieldChanged(_ textField: UITextField) {
|
||||||
|
self.update(transition: .immediate)
|
||||||
|
|
||||||
|
let text = textField.text ?? ""
|
||||||
|
|
||||||
|
var inputLanguage = textField.textInputMode?.primaryLanguage ?? "en"
|
||||||
|
if let range = inputLanguage.range(of: "-") {
|
||||||
|
inputLanguage = String(inputLanguage[inputLanguage.startIndex ..< range.lowerBound])
|
||||||
|
}
|
||||||
|
if let range = inputLanguage.range(of: "_") {
|
||||||
|
inputLanguage = String(inputLanguage[inputLanguage.startIndex ..< range.lowerBound])
|
||||||
|
}
|
||||||
|
|
||||||
|
self.clearIconView.isHidden = text.isEmpty
|
||||||
|
self.clearIconTintView.isHidden = text.isEmpty
|
||||||
|
self.clearIconButton.isHidden = text.isEmpty
|
||||||
|
|
||||||
|
self.currentPresetSearchTerm = nil
|
||||||
|
self.updateQuery(.text(value: text, language: inputLanguage))
|
||||||
|
}
|
||||||
|
|
||||||
|
private func update(transition: Transition) {
|
||||||
|
guard let params = self.params else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.params = nil
|
||||||
|
self.update(context: params.context, theme: params.theme, forceNeedsVibrancy: params.forceNeedsVibrancy, strings: params.strings, text: params.text, useOpaqueTheme: params.useOpaqueTheme, isActive: params.isActive, size: params.size, canFocus: params.canFocus, searchCategories: params.searchCategories, searchState: params.searchState, transition: transition)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func update(context: AccountContext, theme: PresentationTheme, forceNeedsVibrancy: Bool, strings: PresentationStrings, text: String, useOpaqueTheme: Bool, isActive: Bool, size: CGSize, canFocus: Bool, searchCategories: EmojiSearchCategories?, searchState: EmojiPagerContentComponent.SearchState, transition: Transition) {
|
||||||
|
let textInputState: EmojiSearchSearchBarComponent.TextInputState
|
||||||
|
if let textField = self.textField {
|
||||||
|
textInputState = .active(hasText: !(textField.text ?? "").isEmpty)
|
||||||
|
} else {
|
||||||
|
textInputState = .inactive
|
||||||
|
}
|
||||||
|
|
||||||
|
let params = Params(
|
||||||
|
context: context,
|
||||||
|
theme: theme,
|
||||||
|
forceNeedsVibrancy: forceNeedsVibrancy,
|
||||||
|
strings: strings,
|
||||||
|
text: text,
|
||||||
|
useOpaqueTheme: useOpaqueTheme,
|
||||||
|
isActive: isActive,
|
||||||
|
hasPresetSearch: self.currentPresetSearchTerm == nil,
|
||||||
|
textInputState: textInputState,
|
||||||
|
searchState: searchState,
|
||||||
|
size: size,
|
||||||
|
canFocus: canFocus,
|
||||||
|
searchCategories: searchCategories
|
||||||
|
)
|
||||||
|
|
||||||
|
if self.params == params {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let isActiveWithText = isActive && self.currentPresetSearchTerm == nil
|
||||||
|
|
||||||
|
if self.params?.theme !== theme {
|
||||||
|
/*self.searchIconView.image = generateTintedImage(image: UIImage(bundleImageName: "Components/Search Bar/Loupe"), color: .white)?.withRenderingMode(.alwaysTemplate)
|
||||||
|
self.searchIconView.tintColor = useOpaqueTheme ? theme.chat.inputMediaPanel.panelContentOpaqueSearchOverlayColor : theme.chat.inputMediaPanel.panelContentVibrantSearchOverlayColor
|
||||||
|
|
||||||
|
self.searchIconTintView.image = generateTintedImage(image: UIImage(bundleImageName: "Components/Search Bar/Loupe"), color: .white)
|
||||||
|
|
||||||
|
self.backIconView.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Back"), color: .white)?.withRenderingMode(.alwaysTemplate)
|
||||||
|
self.backIconView.tintColor = useOpaqueTheme ? theme.chat.inputMediaPanel.panelContentOpaqueSearchOverlayColor : theme.chat.inputMediaPanel.panelContentVibrantSearchOverlayColor
|
||||||
|
|
||||||
|
self.backIconTintView.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Back"), color: .white)*/
|
||||||
|
|
||||||
|
self.clearIconView.image = generateTintedImage(image: UIImage(bundleImageName: "Components/Search Bar/Clear"), color: .white)?.withRenderingMode(.alwaysTemplate)
|
||||||
|
self.clearIconView.tintColor = useOpaqueTheme ? theme.chat.inputMediaPanel.panelContentOpaqueSearchOverlayColor : theme.chat.inputMediaPanel.panelContentVibrantSearchOverlayColor
|
||||||
|
|
||||||
|
self.clearIconTintView.image = generateTintedImage(image: UIImage(bundleImageName: "Components/Search Bar/Clear"), color: .white)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.params = params
|
||||||
|
|
||||||
|
let sideInset: CGFloat = 12.0
|
||||||
|
let topInset: CGFloat = 8.0
|
||||||
|
let inputHeight: CGFloat = 36.0
|
||||||
|
|
||||||
|
let sideTextInset: CGFloat = sideInset + 4.0 + 24.0
|
||||||
|
|
||||||
|
if theme.overallDarkAppearance && forceNeedsVibrancy {
|
||||||
|
self.backgroundLayer.backgroundColor = theme.chat.inputMediaPanel.panelContentControlVibrantSelectionColor.withMultipliedAlpha(0.3).cgColor
|
||||||
|
self.tintBackgroundLayer.backgroundColor = UIColor(white: 1.0, alpha: 0.2).cgColor
|
||||||
|
} else if useOpaqueTheme {
|
||||||
|
self.backgroundLayer.backgroundColor = theme.chat.inputMediaPanel.panelContentControlOpaqueSelectionColor.cgColor
|
||||||
|
self.tintBackgroundLayer.backgroundColor = UIColor.white.cgColor
|
||||||
|
} else {
|
||||||
|
self.backgroundLayer.backgroundColor = theme.chat.inputMediaPanel.panelContentControlVibrantSelectionColor.cgColor
|
||||||
|
self.tintBackgroundLayer.backgroundColor = UIColor(white: 1.0, alpha: 0.2).cgColor
|
||||||
|
}
|
||||||
|
|
||||||
|
self.backgroundLayer.cornerRadius = inputHeight * 0.5
|
||||||
|
self.tintBackgroundLayer.cornerRadius = inputHeight * 0.5
|
||||||
|
|
||||||
|
let cancelColor: UIColor
|
||||||
|
if theme.overallDarkAppearance && forceNeedsVibrancy {
|
||||||
|
cancelColor = theme.chat.inputMediaPanel.panelContentVibrantSearchOverlayColor.withMultipliedAlpha(0.3)
|
||||||
|
} else {
|
||||||
|
cancelColor = useOpaqueTheme ? theme.list.itemAccentColor : theme.chat.inputMediaPanel.panelContentVibrantSearchOverlayColor
|
||||||
|
}
|
||||||
|
|
||||||
|
let cancelTextSize = self.cancelButtonTitle.update(
|
||||||
|
transition: .immediate,
|
||||||
|
component: AnyComponent(Text(
|
||||||
|
text: strings.Common_Cancel,
|
||||||
|
font: Font.regular(17.0),
|
||||||
|
color: cancelColor
|
||||||
|
)),
|
||||||
|
environment: {},
|
||||||
|
containerSize: CGSize(width: size.width - 32.0, height: 100.0)
|
||||||
|
)
|
||||||
|
let _ = self.cancelButtonTintTitle.update(
|
||||||
|
transition: .immediate,
|
||||||
|
component: AnyComponent(Text(
|
||||||
|
text: strings.Common_Cancel,
|
||||||
|
font: Font.regular(17.0),
|
||||||
|
color: .white
|
||||||
|
)),
|
||||||
|
environment: {},
|
||||||
|
containerSize: CGSize(width: size.width - 32.0, height: 100.0)
|
||||||
|
)
|
||||||
|
|
||||||
|
let cancelButtonSpacing: CGFloat = 8.0
|
||||||
|
|
||||||
|
var backgroundFrame = CGRect(origin: CGPoint(x: sideInset, y: topInset), size: CGSize(width: size.width - sideInset * 2.0, height: inputHeight))
|
||||||
|
if isActiveWithText {
|
||||||
|
backgroundFrame.size.width -= cancelTextSize.width + cancelButtonSpacing
|
||||||
|
}
|
||||||
|
transition.setFrame(layer: self.backgroundLayer, frame: backgroundFrame)
|
||||||
|
transition.setFrame(layer: self.tintBackgroundLayer, frame: backgroundFrame)
|
||||||
|
|
||||||
|
transition.setFrame(view: self.cancelButton, frame: CGRect(origin: CGPoint(x: backgroundFrame.maxX, y: 0.0), size: CGSize(width: cancelButtonSpacing + cancelTextSize.width, height: size.height)))
|
||||||
|
|
||||||
|
let textX: CGFloat = backgroundFrame.minX + sideTextInset
|
||||||
|
let textFrame = CGRect(origin: CGPoint(x: textX, y: backgroundFrame.minY), size: CGSize(width: backgroundFrame.maxX - textX, height: backgroundFrame.height))
|
||||||
|
self.textFrame = textFrame
|
||||||
|
|
||||||
|
let statusContent: EmojiSearchStatusComponent.Content
|
||||||
|
switch searchState {
|
||||||
|
case .empty:
|
||||||
|
statusContent = .search
|
||||||
|
case .searching:
|
||||||
|
statusContent = .progress
|
||||||
|
case .active:
|
||||||
|
statusContent = .results
|
||||||
|
}
|
||||||
|
|
||||||
|
let statusSize = CGSize(width: 24.0, height: 24.0)
|
||||||
|
let _ = self.statusIcon.update(
|
||||||
|
transition: transition,
|
||||||
|
component: AnyComponent(EmojiSearchStatusComponent(
|
||||||
|
theme: theme,
|
||||||
|
forceNeedsVibrancy: forceNeedsVibrancy,
|
||||||
|
strings: strings,
|
||||||
|
useOpaqueTheme: useOpaqueTheme,
|
||||||
|
content: statusContent
|
||||||
|
)),
|
||||||
|
environment: {},
|
||||||
|
containerSize: statusSize
|
||||||
|
)
|
||||||
|
let iconFrame = CGRect(origin: CGPoint(x: textFrame.minX - statusSize.width - 4.0, y: backgroundFrame.minY + floor((backgroundFrame.height - statusSize.height) / 2.0)), size: statusSize)
|
||||||
|
if let statusIconView = self.statusIcon.view as? EmojiSearchStatusComponent.View {
|
||||||
|
if statusIconView.superview == nil {
|
||||||
|
self.addSubview(statusIconView)
|
||||||
|
self.tintContainerView.addSubview(statusIconView.tintContainerView)
|
||||||
|
}
|
||||||
|
|
||||||
|
transition.setFrame(view: statusIconView, frame: iconFrame)
|
||||||
|
transition.setFrame(view: statusIconView.tintContainerView, frame: iconFrame)
|
||||||
|
}
|
||||||
|
|
||||||
|
let placeholderContentFrame = CGRect(origin: CGPoint(x: textFrame.minX - 6.0, y: backgroundFrame.minY), size: CGSize(width: backgroundFrame.maxX - (textFrame.minX - 6.0), height: backgroundFrame.height))
|
||||||
|
let _ = self.placeholderContent.update(
|
||||||
|
transition: transition,
|
||||||
|
component: AnyComponent(EmojiSearchSearchBarComponent(
|
||||||
|
context: context,
|
||||||
|
theme: theme,
|
||||||
|
forceNeedsVibrancy: forceNeedsVibrancy,
|
||||||
|
strings: strings,
|
||||||
|
useOpaqueTheme: useOpaqueTheme,
|
||||||
|
textInputState: textInputState,
|
||||||
|
categories: searchCategories,
|
||||||
|
searchTermUpdated: { [weak self] term in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var shouldChangeActivation = false
|
||||||
|
if (self.currentPresetSearchTerm == nil) != (term == nil) {
|
||||||
|
shouldChangeActivation = true
|
||||||
|
}
|
||||||
|
self.currentPresetSearchTerm = term
|
||||||
|
|
||||||
|
if shouldChangeActivation {
|
||||||
|
if let term {
|
||||||
|
self.update(transition: Transition(animation: .curve(duration: 0.4, curve: .spring)))
|
||||||
|
|
||||||
|
self.updateQuery(.category(value: term))
|
||||||
|
self.activated(false)
|
||||||
|
} else {
|
||||||
|
self.deactivated(self.textField?.isFirstResponder ?? false)
|
||||||
|
self.updateQuery(nil)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if let term {
|
||||||
|
self.updateQuery(.category(value: term))
|
||||||
|
} else {
|
||||||
|
self.updateQuery(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
activateTextInput: { [weak self] in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.activateTextInput()
|
||||||
|
}
|
||||||
|
)),
|
||||||
|
environment: {},
|
||||||
|
containerSize: placeholderContentFrame.size
|
||||||
|
)
|
||||||
|
if let placeholderContentView = self.placeholderContent.view as? EmojiSearchSearchBarComponent.View {
|
||||||
|
if placeholderContentView.superview == nil {
|
||||||
|
self.addSubview(placeholderContentView)
|
||||||
|
self.tintContainerView.addSubview(placeholderContentView.tintContainerView)
|
||||||
|
}
|
||||||
|
transition.setFrame(view: placeholderContentView, frame: placeholderContentFrame)
|
||||||
|
transition.setFrame(view: placeholderContentView.tintContainerView, frame: placeholderContentFrame)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*if let searchCategories {
|
||||||
|
let suggestedItemsView: ComponentView<Empty>
|
||||||
|
var suggestedItemsTransition = transition
|
||||||
|
if let current = self.suggestedItemsView {
|
||||||
|
suggestedItemsView = current
|
||||||
|
} else {
|
||||||
|
suggestedItemsTransition = .immediate
|
||||||
|
suggestedItemsView = ComponentView()
|
||||||
|
self.suggestedItemsView = suggestedItemsView
|
||||||
|
}
|
||||||
|
|
||||||
|
let itemsX: CGFloat = textFrame.maxX + 8.0
|
||||||
|
let suggestedItemsFrame = CGRect(origin: CGPoint(x: itemsX, y: backgroundFrame.minY), size: CGSize(width: backgroundFrame.maxX - itemsX, height: backgroundFrame.height))
|
||||||
|
|
||||||
|
if let suggestedItemsComponentView = suggestedItemsView.view {
|
||||||
|
if suggestedItemsComponentView.superview == nil {
|
||||||
|
self.addSubview(suggestedItemsComponentView)
|
||||||
|
}
|
||||||
|
suggestedItemsTransition.setFrame(view: suggestedItemsComponentView, frame: suggestedItemsFrame)
|
||||||
|
suggestedItemsTransition.setAlpha(view: suggestedItemsComponentView, alpha: isActiveWithText ? 0.0 : 1.0)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if let suggestedItemsView = self.suggestedItemsView {
|
||||||
|
self.suggestedItemsView = nil
|
||||||
|
if let suggestedItemsComponentView = suggestedItemsView.view {
|
||||||
|
transition.setAlpha(view: suggestedItemsComponentView, alpha: 0.0, completion: { [weak suggestedItemsComponentView] _ in
|
||||||
|
suggestedItemsComponentView?.removeFromSuperview()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
|
||||||
|
if let image = self.clearIconView.image {
|
||||||
|
let iconFrame = CGRect(origin: CGPoint(x: backgroundFrame.maxX - image.size.width - 4.0, y: backgroundFrame.minY + floor((backgroundFrame.height - image.size.height) / 2.0)), size: image.size)
|
||||||
|
transition.setFrame(view: self.clearIconView, frame: iconFrame)
|
||||||
|
transition.setFrame(view: self.clearIconTintView, frame: iconFrame)
|
||||||
|
transition.setFrame(view: self.clearIconButton, frame: iconFrame.insetBy(dx: -8.0, dy: -10.0))
|
||||||
|
}
|
||||||
|
|
||||||
|
if let cancelButtonTitleComponentView = self.cancelButtonTitle.view {
|
||||||
|
if cancelButtonTitleComponentView.superview == nil {
|
||||||
|
self.addSubview(cancelButtonTitleComponentView)
|
||||||
|
cancelButtonTitleComponentView.isUserInteractionEnabled = false
|
||||||
|
}
|
||||||
|
transition.setFrame(view: cancelButtonTitleComponentView, frame: CGRect(origin: CGPoint(x: backgroundFrame.maxX + cancelButtonSpacing, y: floor((size.height - cancelTextSize.height) / 2.0)), size: cancelTextSize))
|
||||||
|
transition.setAlpha(view: cancelButtonTitleComponentView, alpha: isActiveWithText ? 1.0 : 0.0)
|
||||||
|
}
|
||||||
|
if let cancelButtonTintTitleComponentView = self.cancelButtonTintTitle.view {
|
||||||
|
if cancelButtonTintTitleComponentView.superview == nil {
|
||||||
|
self.tintContainerView.addSubview(cancelButtonTintTitleComponentView)
|
||||||
|
cancelButtonTintTitleComponentView.isUserInteractionEnabled = false
|
||||||
|
}
|
||||||
|
transition.setFrame(view: cancelButtonTintTitleComponentView, frame: CGRect(origin: CGPoint(x: backgroundFrame.maxX + cancelButtonSpacing, y: floor((size.height - cancelTextSize.height) / 2.0)), size: cancelTextSize))
|
||||||
|
transition.setAlpha(view: cancelButtonTintTitleComponentView, alpha: isActiveWithText ? 1.0 : 0.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
var hasText = false
|
||||||
|
if let textField = self.textField {
|
||||||
|
textField.textColor = theme.contextMenu.primaryColor
|
||||||
|
transition.setFrame(view: textField, frame: CGRect(origin: CGPoint(x: backgroundFrame.minX + sideTextInset, y: backgroundFrame.minY), size: CGSize(width: backgroundFrame.width - sideTextInset - 32.0, height: backgroundFrame.height)))
|
||||||
|
|
||||||
|
if let text = textField.text, !text.isEmpty {
|
||||||
|
hasText = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let _ = hasText
|
||||||
|
|
||||||
|
/*self.tintTextView.view?.isHidden = hasText
|
||||||
|
self.textView.view?.isHidden = hasText*/
|
||||||
|
}
|
||||||
|
}
|
@ -420,6 +420,15 @@ final class EmojiSearchSearchBarComponent: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||||
|
guard let component = self.component else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
let _ = component
|
||||||
|
|
||||||
|
return super.hitTest(point, with: event)
|
||||||
|
}
|
||||||
|
|
||||||
private func updateScrolling(transition: Transition, fromScrolling: Bool) {
|
private func updateScrolling(transition: Transition, fromScrolling: Bool) {
|
||||||
guard let component = self.component, let itemLayout = self.itemLayout else {
|
guard let component = self.component, let itemLayout = self.itemLayout else {
|
||||||
return
|
return
|
||||||
@ -427,8 +436,12 @@ final class EmojiSearchSearchBarComponent: Component {
|
|||||||
|
|
||||||
let itemAlpha: CGFloat
|
let itemAlpha: CGFloat
|
||||||
switch component.textInputState {
|
switch component.textInputState {
|
||||||
case .active:
|
case let .active(hasText):
|
||||||
itemAlpha = 0.0
|
if hasText {
|
||||||
|
itemAlpha = 0.0
|
||||||
|
} else {
|
||||||
|
itemAlpha = 1.0
|
||||||
|
}
|
||||||
case .inactive:
|
case .inactive:
|
||||||
itemAlpha = 1.0
|
itemAlpha = 1.0
|
||||||
}
|
}
|
||||||
@ -674,7 +687,7 @@ final class EmojiSearchSearchBarComponent: Component {
|
|||||||
if self.scrollView.bounds.size != availableSize {
|
if self.scrollView.bounds.size != availableSize {
|
||||||
transition.setFrame(view: self.scrollView, frame: CGRect(origin: CGPoint(), size: availableSize))
|
transition.setFrame(view: self.scrollView, frame: CGRect(origin: CGPoint(), size: availableSize))
|
||||||
}
|
}
|
||||||
if case .active = component.textInputState {
|
if case .active(true) = component.textInputState {
|
||||||
transition.setBoundsOrigin(view: self.scrollView, origin: CGPoint())
|
transition.setBoundsOrigin(view: self.scrollView, origin: CGPoint())
|
||||||
}
|
}
|
||||||
if self.scrollView.contentSize != itemLayout.contentSize {
|
if self.scrollView.contentSize != itemLayout.contentSize {
|
||||||
|
@ -0,0 +1,101 @@
|
|||||||
|
import Foundation
|
||||||
|
import UIKit
|
||||||
|
import Display
|
||||||
|
import ComponentFlow
|
||||||
|
import AccountContext
|
||||||
|
import TelegramCore
|
||||||
|
import TelegramPresentationData
|
||||||
|
import EmojiStatusComponent
|
||||||
|
|
||||||
|
final class EmptySearchResultsView: UIView {
|
||||||
|
override public static var layerClass: AnyClass {
|
||||||
|
return PassthroughLayer.self
|
||||||
|
}
|
||||||
|
|
||||||
|
let tintContainerView: UIView
|
||||||
|
let titleLabel: ComponentView<Empty>
|
||||||
|
let titleTintLabel: ComponentView<Empty>
|
||||||
|
let icon: ComponentView<Empty>
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
self.tintContainerView = UIView()
|
||||||
|
|
||||||
|
self.titleLabel = ComponentView()
|
||||||
|
self.titleTintLabel = ComponentView()
|
||||||
|
self.icon = ComponentView()
|
||||||
|
|
||||||
|
super.init(frame: frame)
|
||||||
|
|
||||||
|
(self.layer as? PassthroughLayer)?.mirrorLayer = self.tintContainerView.layer
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func update(context: AccountContext, theme: PresentationTheme, useOpaqueTheme: Bool, text: String, file: TelegramMediaFile?, size: CGSize, searchInitiallyHidden: Bool, transition: Transition) {
|
||||||
|
let titleColor: UIColor
|
||||||
|
if useOpaqueTheme {
|
||||||
|
titleColor = theme.chat.inputMediaPanel.panelContentOpaqueSearchOverlayColor
|
||||||
|
} else {
|
||||||
|
titleColor = theme.chat.inputMediaPanel.panelContentVibrantSearchOverlayColor
|
||||||
|
}
|
||||||
|
|
||||||
|
let iconSize: CGSize
|
||||||
|
if let file = file {
|
||||||
|
iconSize = self.icon.update(
|
||||||
|
transition: .immediate,
|
||||||
|
component: AnyComponent(EmojiStatusComponent(
|
||||||
|
context: context,
|
||||||
|
animationCache: context.animationCache,
|
||||||
|
animationRenderer: context.animationRenderer,
|
||||||
|
content: .animation(content: .file(file: file), size: CGSize(width: 32.0, height: 32.0), placeholderColor: titleColor, themeColor: nil, loopMode: .forever),
|
||||||
|
isVisibleForAnimations: context.sharedContext.energyUsageSettings.loopEmoji,
|
||||||
|
action: nil
|
||||||
|
)),
|
||||||
|
environment: {},
|
||||||
|
containerSize: CGSize(width: 32.0, height: 32.0)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
iconSize = CGSize()
|
||||||
|
}
|
||||||
|
|
||||||
|
let titleSize = self.titleLabel.update(
|
||||||
|
transition: .immediate,
|
||||||
|
component: AnyComponent(Text(text: text, font: Font.regular(15.0), color: titleColor)),
|
||||||
|
environment: {},
|
||||||
|
containerSize: CGSize(width: size.width, height: 100.0)
|
||||||
|
)
|
||||||
|
let _ = self.titleTintLabel.update(
|
||||||
|
transition: .immediate,
|
||||||
|
component: AnyComponent(Text(text: text, font: Font.regular(15.0), color: .white)),
|
||||||
|
environment: {},
|
||||||
|
containerSize: CGSize(width: size.width, height: 100.0)
|
||||||
|
)
|
||||||
|
|
||||||
|
let spacing: CGFloat = 4.0
|
||||||
|
let contentHeight = iconSize.height + spacing + titleSize.height
|
||||||
|
let contentOriginY = searchInitiallyHidden ? floor((size.height - contentHeight) / 2.0) : 10.0
|
||||||
|
let iconFrame = CGRect(origin: CGPoint(x: floor((size.width - iconSize.width) / 2.0), y: contentOriginY), size: iconSize)
|
||||||
|
let titleFrame = CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: iconFrame.maxY + spacing), size: titleSize)
|
||||||
|
|
||||||
|
if let iconView = self.icon.view {
|
||||||
|
if iconView.superview == nil {
|
||||||
|
self.addSubview(iconView)
|
||||||
|
}
|
||||||
|
transition.setFrame(view: iconView, frame: iconFrame)
|
||||||
|
}
|
||||||
|
if let titleLabelView = self.titleLabel.view {
|
||||||
|
if titleLabelView.superview == nil {
|
||||||
|
self.addSubview(titleLabelView)
|
||||||
|
}
|
||||||
|
transition.setFrame(view: titleLabelView, frame: titleFrame)
|
||||||
|
}
|
||||||
|
if let titleTintLabelView = self.titleTintLabel.view {
|
||||||
|
if titleTintLabelView.superview == nil {
|
||||||
|
self.tintContainerView.addSubview(titleTintLabelView)
|
||||||
|
}
|
||||||
|
transition.setFrame(view: titleTintLabelView, frame: titleFrame)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -85,7 +85,7 @@ final class EntityKeyboardAnimationTopPanelComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final class View: UIView {
|
final class View: UIView {
|
||||||
var itemLayer: EmojiPagerContentComponent.View.ItemLayer?
|
var itemLayer: EmojiKeyboardItemLayer?
|
||||||
var placeholderView: EmojiPagerContentComponent.View.ItemPlaceholderView?
|
var placeholderView: EmojiPagerContentComponent.View.ItemPlaceholderView?
|
||||||
var component: EntityKeyboardAnimationTopPanelComponent?
|
var component: EntityKeyboardAnimationTopPanelComponent?
|
||||||
var titleView: ComponentView<Empty>?
|
var titleView: ComponentView<Empty>?
|
||||||
@ -116,7 +116,7 @@ final class EntityKeyboardAnimationTopPanelComponent: Component {
|
|||||||
|
|
||||||
if self.itemLayer == nil {
|
if self.itemLayer == nil {
|
||||||
let tintColor: EmojiPagerContentComponent.Item.TintMode = component.customTintColor.flatMap { .custom($0) } ?? .primary
|
let tintColor: EmojiPagerContentComponent.Item.TintMode = component.customTintColor.flatMap { .custom($0) } ?? .primary
|
||||||
let itemLayer = EmojiPagerContentComponent.View.ItemLayer(
|
let itemLayer = EmojiKeyboardItemLayer(
|
||||||
item: EmojiPagerContentComponent.Item(
|
item: EmojiPagerContentComponent.Item(
|
||||||
animationData: component.item,
|
animationData: component.item,
|
||||||
content: .animation(component.item),
|
content: .animation(component.item),
|
||||||
@ -157,7 +157,7 @@ final class EntityKeyboardAnimationTopPanelComponent: Component {
|
|||||||
transition.setPosition(layer: itemLayer, position: CGPoint(x: iconFrame.midX, y: iconFrame.midY))
|
transition.setPosition(layer: itemLayer, position: CGPoint(x: iconFrame.midX, y: iconFrame.midY))
|
||||||
transition.setBounds(layer: itemLayer, bounds: CGRect(origin: CGPoint(), size: iconFrame.size))
|
transition.setBounds(layer: itemLayer, bounds: CGRect(origin: CGPoint(), size: iconFrame.size))
|
||||||
|
|
||||||
var badge: EmojiPagerContentComponent.View.ItemLayer.Badge?
|
var badge: EmojiKeyboardItemLayer.Badge?
|
||||||
if component.isPremiumLocked {
|
if component.isPremiumLocked {
|
||||||
badge = .locked
|
badge = .locked
|
||||||
} else if component.isFeatured {
|
} else if component.isFeatured {
|
||||||
|
@ -0,0 +1,209 @@
|
|||||||
|
import Foundation
|
||||||
|
import UIKit
|
||||||
|
import Display
|
||||||
|
import ComponentFlow
|
||||||
|
import AccountContext
|
||||||
|
import TelegramPresentationData
|
||||||
|
import AnimationCache
|
||||||
|
import MultiAnimationRenderer
|
||||||
|
import PagerComponent
|
||||||
|
|
||||||
|
final class GroupEmbeddedView: UIScrollView, UIScrollViewDelegate, PagerExpandableScrollView {
|
||||||
|
private struct ItemLayout {
|
||||||
|
var itemSize: CGFloat
|
||||||
|
var itemSpacing: CGFloat
|
||||||
|
var sideInset: CGFloat
|
||||||
|
var itemCount: Int
|
||||||
|
var contentSize: CGSize
|
||||||
|
|
||||||
|
init(height: CGFloat, sideInset: CGFloat, itemCount: Int) {
|
||||||
|
self.itemSize = 30.0
|
||||||
|
self.itemSpacing = 20.0
|
||||||
|
self.sideInset = sideInset
|
||||||
|
self.itemCount = itemCount
|
||||||
|
|
||||||
|
self.contentSize = CGSize(width: self.sideInset * 2.0 + CGFloat(self.itemCount) * self.itemSize + CGFloat(self.itemCount - 1) * self.itemSpacing, height: height)
|
||||||
|
}
|
||||||
|
|
||||||
|
func frame(at index: Int) -> CGRect {
|
||||||
|
return CGRect(origin: CGPoint(x: sideInset + CGFloat(index) * (self.itemSize + self.itemSpacing), y: floor((self.contentSize.height - self.itemSize) / 2.0)), size: CGSize(width: self.itemSize, height: self.itemSize))
|
||||||
|
}
|
||||||
|
|
||||||
|
func visibleItems(for rect: CGRect) -> Range<Int>? {
|
||||||
|
let offsetRect = rect.offsetBy(dx: -self.sideInset, dy: 0.0)
|
||||||
|
var minVisibleIndex = Int(floor((offsetRect.minX - self.itemSpacing) / (self.itemSize + self.itemSpacing)))
|
||||||
|
minVisibleIndex = max(0, minVisibleIndex)
|
||||||
|
var maxVisibleIndex = Int(ceil((offsetRect.maxX - self.itemSpacing) / (self.itemSize + self.itemSpacing)))
|
||||||
|
maxVisibleIndex = min(maxVisibleIndex, self.itemCount - 1)
|
||||||
|
|
||||||
|
if minVisibleIndex <= maxVisibleIndex {
|
||||||
|
return minVisibleIndex ..< (maxVisibleIndex + 1)
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private let performItemAction: (EmojiPagerContentComponent.Item, UIView, CGRect, CALayer) -> Void
|
||||||
|
|
||||||
|
private var visibleItemLayers: [EmojiKeyboardItemLayer.Key: EmojiKeyboardItemLayer] = [:]
|
||||||
|
private var ignoreScrolling: Bool = false
|
||||||
|
|
||||||
|
private var context: AccountContext?
|
||||||
|
private var theme: PresentationTheme?
|
||||||
|
private var cache: AnimationCache?
|
||||||
|
private var renderer: MultiAnimationRenderer?
|
||||||
|
private var currentInsets: UIEdgeInsets?
|
||||||
|
private var currentSize: CGSize?
|
||||||
|
private var items: [EmojiPagerContentComponent.Item]?
|
||||||
|
private var isStickers: Bool = false
|
||||||
|
|
||||||
|
private var itemLayout: ItemLayout?
|
||||||
|
|
||||||
|
init(performItemAction: @escaping (EmojiPagerContentComponent.Item, UIView, CGRect, CALayer) -> Void) {
|
||||||
|
self.performItemAction = performItemAction
|
||||||
|
|
||||||
|
super.init(frame: CGRect())
|
||||||
|
|
||||||
|
self.delaysContentTouches = false
|
||||||
|
if #available(iOSApplicationExtension 11.0, iOS 11.0, *) {
|
||||||
|
self.contentInsetAdjustmentBehavior = .never
|
||||||
|
}
|
||||||
|
if #available(iOS 13.0, *) {
|
||||||
|
self.automaticallyAdjustsScrollIndicatorInsets = false
|
||||||
|
}
|
||||||
|
self.showsVerticalScrollIndicator = true
|
||||||
|
self.showsHorizontalScrollIndicator = false
|
||||||
|
self.delegate = self
|
||||||
|
self.clipsToBounds = true
|
||||||
|
self.scrollsToTop = false
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func tapGesture(point: CGPoint) -> Bool {
|
||||||
|
guard let itemLayout = self.itemLayout else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for (_, itemLayer) in self.visibleItemLayers {
|
||||||
|
if itemLayer.frame.inset(by: UIEdgeInsets(top: -6.0, left: -itemLayout.itemSpacing, bottom: -6.0, right: -itemLayout.itemSpacing)).contains(point) {
|
||||||
|
self.performItemAction(itemLayer.item, self, itemLayer.frame, itemLayer)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||||
|
if !self.ignoreScrolling {
|
||||||
|
self.updateVisibleItems(transition: .immediate, attemptSynchronousLoad: false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func updateVisibleItems(transition: Transition, attemptSynchronousLoad: Bool) {
|
||||||
|
guard let context = self.context, let theme = self.theme, let itemLayout = self.itemLayout, let items = self.items, let cache = self.cache, let renderer = self.renderer else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var validIds = Set<EmojiKeyboardItemLayer.Key>()
|
||||||
|
if let itemRange = itemLayout.visibleItems(for: self.bounds) {
|
||||||
|
for index in itemRange.lowerBound ..< itemRange.upperBound {
|
||||||
|
let item = items[index]
|
||||||
|
let itemId = EmojiKeyboardItemLayer.Key(
|
||||||
|
groupId: AnyHashable(0),
|
||||||
|
itemId: item.content.id
|
||||||
|
)
|
||||||
|
validIds.insert(itemId)
|
||||||
|
|
||||||
|
let itemLayer: EmojiKeyboardItemLayer
|
||||||
|
if let current = self.visibleItemLayers[itemId] {
|
||||||
|
itemLayer = current
|
||||||
|
} else {
|
||||||
|
itemLayer = EmojiKeyboardItemLayer(
|
||||||
|
item: item,
|
||||||
|
context: context,
|
||||||
|
attemptSynchronousLoad: attemptSynchronousLoad,
|
||||||
|
content: item.content,
|
||||||
|
cache: cache,
|
||||||
|
renderer: renderer,
|
||||||
|
placeholderColor: .clear,
|
||||||
|
blurredBadgeColor: .clear,
|
||||||
|
accentIconColor: theme.list.itemAccentColor,
|
||||||
|
pointSize: CGSize(width: 32.0, height: 32.0),
|
||||||
|
onUpdateDisplayPlaceholder: { _, _ in
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.visibleItemLayers[itemId] = itemLayer
|
||||||
|
self.layer.addSublayer(itemLayer)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch item.tintMode {
|
||||||
|
case let .custom(color):
|
||||||
|
itemLayer.layerTintColor = color.cgColor
|
||||||
|
case .accent:
|
||||||
|
itemLayer.layerTintColor = theme.list.itemAccentColor.cgColor
|
||||||
|
case .primary:
|
||||||
|
itemLayer.layerTintColor = theme.list.itemPrimaryTextColor.cgColor
|
||||||
|
case .none:
|
||||||
|
itemLayer.layerTintColor = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
let itemFrame = itemLayout.frame(at: index)
|
||||||
|
itemLayer.frame = itemFrame
|
||||||
|
|
||||||
|
itemLayer.isVisibleForAnimations = self.isStickers ? context.sharedContext.energyUsageSettings.loopStickers : context.sharedContext.energyUsageSettings.loopEmoji
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var removedIds: [EmojiKeyboardItemLayer.Key] = []
|
||||||
|
for (id, itemLayer) in self.visibleItemLayers {
|
||||||
|
if !validIds.contains(id) {
|
||||||
|
removedIds.append(id)
|
||||||
|
itemLayer.removeFromSuperlayer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for id in removedIds {
|
||||||
|
self.visibleItemLayers.removeValue(forKey: id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func update(
|
||||||
|
context: AccountContext,
|
||||||
|
theme: PresentationTheme,
|
||||||
|
insets: UIEdgeInsets,
|
||||||
|
size: CGSize,
|
||||||
|
items: [EmojiPagerContentComponent.Item],
|
||||||
|
isStickers: Bool,
|
||||||
|
cache: AnimationCache,
|
||||||
|
renderer: MultiAnimationRenderer,
|
||||||
|
attemptSynchronousLoad: Bool
|
||||||
|
) {
|
||||||
|
if self.theme === theme && self.currentInsets == insets && self.currentSize == size && self.items == items {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
self.context = context
|
||||||
|
self.theme = theme
|
||||||
|
self.currentInsets = insets
|
||||||
|
self.currentSize = size
|
||||||
|
self.items = items
|
||||||
|
self.isStickers = isStickers
|
||||||
|
self.cache = cache
|
||||||
|
self.renderer = renderer
|
||||||
|
|
||||||
|
let itemLayout = ItemLayout(height: size.height, sideInset: insets.left, itemCount: items.count)
|
||||||
|
self.itemLayout = itemLayout
|
||||||
|
|
||||||
|
self.ignoreScrolling = true
|
||||||
|
if itemLayout.contentSize != self.contentSize {
|
||||||
|
self.contentSize = itemLayout.contentSize
|
||||||
|
}
|
||||||
|
self.ignoreScrolling = false
|
||||||
|
|
||||||
|
self.updateVisibleItems(transition: .immediate, attemptSynchronousLoad: attemptSynchronousLoad)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,128 @@
|
|||||||
|
import Foundation
|
||||||
|
import UIKit
|
||||||
|
import Display
|
||||||
|
import ComponentFlow
|
||||||
|
import TelegramPresentationData
|
||||||
|
|
||||||
|
final class GroupExpandActionButton: UIButton {
|
||||||
|
override static var layerClass: AnyClass {
|
||||||
|
return PassthroughLayer.self
|
||||||
|
}
|
||||||
|
|
||||||
|
let tintContainerLayer: SimpleLayer
|
||||||
|
|
||||||
|
private var currentTextLayout: (string: String, color: UIColor, constrainedWidth: CGFloat, size: CGSize)?
|
||||||
|
private let backgroundLayer: SimpleLayer
|
||||||
|
private let tintBackgroundLayer: SimpleLayer
|
||||||
|
private let textLayer: SimpleLayer
|
||||||
|
private let pressed: () -> Void
|
||||||
|
|
||||||
|
init(pressed: @escaping () -> Void) {
|
||||||
|
self.pressed = pressed
|
||||||
|
|
||||||
|
self.tintContainerLayer = SimpleLayer()
|
||||||
|
|
||||||
|
self.backgroundLayer = SimpleLayer()
|
||||||
|
self.backgroundLayer.masksToBounds = true
|
||||||
|
|
||||||
|
self.tintBackgroundLayer = SimpleLayer()
|
||||||
|
self.tintBackgroundLayer.masksToBounds = true
|
||||||
|
|
||||||
|
self.textLayer = SimpleLayer()
|
||||||
|
|
||||||
|
super.init(frame: CGRect())
|
||||||
|
|
||||||
|
(self.layer as? PassthroughLayer)?.mirrorLayer = self.tintContainerLayer
|
||||||
|
|
||||||
|
self.layer.addSublayer(self.backgroundLayer)
|
||||||
|
|
||||||
|
self.layer.addSublayer(self.textLayer)
|
||||||
|
|
||||||
|
self.addTarget(self, action: #selector(self.onPressed), for: .touchUpInside)
|
||||||
|
}
|
||||||
|
|
||||||
|
required init(coder: NSCoder) {
|
||||||
|
preconditionFailure()
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func onPressed() {
|
||||||
|
self.pressed()
|
||||||
|
}
|
||||||
|
|
||||||
|
override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
|
||||||
|
self.alpha = 0.6
|
||||||
|
|
||||||
|
return super.beginTracking(touch, with: event)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func endTracking(_ touch: UITouch?, with event: UIEvent?) {
|
||||||
|
let alpha = self.alpha
|
||||||
|
self.alpha = 1.0
|
||||||
|
self.layer.animateAlpha(from: alpha, to: 1.0, duration: 0.25)
|
||||||
|
|
||||||
|
super.endTracking(touch, with: event)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func cancelTracking(with event: UIEvent?) {
|
||||||
|
let alpha = self.alpha
|
||||||
|
self.alpha = 1.0
|
||||||
|
self.layer.animateAlpha(from: alpha, to: 1.0, duration: 0.25)
|
||||||
|
|
||||||
|
super.cancelTracking(with: event)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
|
||||||
|
let alpha = self.alpha
|
||||||
|
self.alpha = 1.0
|
||||||
|
self.layer.animateAlpha(from: alpha, to: 1.0, duration: 0.25)
|
||||||
|
|
||||||
|
super.touchesCancelled(touches, with: event)
|
||||||
|
}
|
||||||
|
|
||||||
|
func update(theme: PresentationTheme, title: String, useOpaqueTheme: Bool) -> CGSize {
|
||||||
|
let textConstrainedWidth: CGFloat = 100.0
|
||||||
|
let color = theme.list.itemCheckColors.foregroundColor
|
||||||
|
|
||||||
|
if useOpaqueTheme {
|
||||||
|
self.backgroundLayer.backgroundColor = theme.chat.inputMediaPanel.panelContentControlOpaqueOverlayColor.cgColor
|
||||||
|
} else {
|
||||||
|
self.backgroundLayer.backgroundColor = theme.chat.inputMediaPanel.panelContentControlVibrantOverlayColor.cgColor
|
||||||
|
}
|
||||||
|
self.tintContainerLayer.backgroundColor = UIColor.white.cgColor
|
||||||
|
|
||||||
|
let textSize: CGSize
|
||||||
|
if let currentTextLayout = self.currentTextLayout, currentTextLayout.string == title, currentTextLayout.color == color, currentTextLayout.constrainedWidth == textConstrainedWidth {
|
||||||
|
textSize = currentTextLayout.size
|
||||||
|
} else {
|
||||||
|
let font: UIFont = Font.semibold(13.0)
|
||||||
|
let string = NSAttributedString(string: title.uppercased(), font: font, textColor: color)
|
||||||
|
let stringBounds = string.boundingRect(with: CGSize(width: textConstrainedWidth, height: 100.0), options: .usesLineFragmentOrigin, context: nil)
|
||||||
|
textSize = CGSize(width: ceil(stringBounds.width), height: ceil(stringBounds.height))
|
||||||
|
self.textLayer.contents = generateImage(textSize, opaque: false, scale: 0.0, rotatedContext: { size, context in
|
||||||
|
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||||
|
UIGraphicsPushContext(context)
|
||||||
|
|
||||||
|
string.draw(in: stringBounds)
|
||||||
|
|
||||||
|
UIGraphicsPopContext()
|
||||||
|
})?.cgImage
|
||||||
|
self.currentTextLayout = (title, color, textConstrainedWidth, textSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
var sideInset: CGFloat = 10.0
|
||||||
|
if textSize.width > 24.0 {
|
||||||
|
sideInset = 6.0
|
||||||
|
}
|
||||||
|
let size = CGSize(width: textSize.width + sideInset * 2.0, height: 28.0)
|
||||||
|
|
||||||
|
let textFrame = CGRect(origin: CGPoint(x: floor((size.width - textSize.width) / 2.0), y: floor((size.height - textSize.height) / 2.0)), size: textSize)
|
||||||
|
self.textLayer.frame = textFrame
|
||||||
|
|
||||||
|
self.backgroundLayer.frame = CGRect(origin: CGPoint(), size: size)
|
||||||
|
self.tintBackgroundLayer.frame = CGRect(origin: CGPoint(), size: size)
|
||||||
|
self.backgroundLayer.cornerRadius = min(size.width, size.height) / 2.0
|
||||||
|
self.tintContainerLayer.cornerRadius = min(size.width, size.height) / 2.0
|
||||||
|
|
||||||
|
return size
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,149 @@
|
|||||||
|
import Foundation
|
||||||
|
import UIKit
|
||||||
|
import Display
|
||||||
|
import ComponentFlow
|
||||||
|
import TelegramPresentationData
|
||||||
|
|
||||||
|
final class GroupHeaderActionButton: UIButton {
|
||||||
|
override static var layerClass: AnyClass {
|
||||||
|
return PassthroughLayer.self
|
||||||
|
}
|
||||||
|
|
||||||
|
let tintContainerLayer: SimpleLayer
|
||||||
|
|
||||||
|
private var currentTextLayout: (string: String, color: UIColor, constrainedWidth: CGFloat, size: CGSize)?
|
||||||
|
private let backgroundLayer: SimpleLayer
|
||||||
|
private let tintBackgroundLayer: SimpleLayer
|
||||||
|
private let textLayer: SimpleLayer
|
||||||
|
private let tintTextLayer: SimpleLayer
|
||||||
|
private let pressed: () -> Void
|
||||||
|
|
||||||
|
init(pressed: @escaping () -> Void) {
|
||||||
|
self.pressed = pressed
|
||||||
|
|
||||||
|
self.tintContainerLayer = SimpleLayer()
|
||||||
|
|
||||||
|
self.backgroundLayer = SimpleLayer()
|
||||||
|
self.backgroundLayer.masksToBounds = true
|
||||||
|
|
||||||
|
self.tintBackgroundLayer = SimpleLayer()
|
||||||
|
self.tintBackgroundLayer.masksToBounds = true
|
||||||
|
|
||||||
|
self.textLayer = SimpleLayer()
|
||||||
|
self.tintTextLayer = SimpleLayer()
|
||||||
|
|
||||||
|
super.init(frame: CGRect())
|
||||||
|
|
||||||
|
(self.layer as? PassthroughLayer)?.mirrorLayer = self.tintContainerLayer
|
||||||
|
|
||||||
|
self.layer.addSublayer(self.backgroundLayer)
|
||||||
|
self.layer.addSublayer(self.textLayer)
|
||||||
|
|
||||||
|
self.addTarget(self, action: #selector(self.onPressed), for: .touchUpInside)
|
||||||
|
|
||||||
|
self.tintContainerLayer.addSublayer(self.tintBackgroundLayer)
|
||||||
|
self.tintContainerLayer.addSublayer(self.tintTextLayer)
|
||||||
|
}
|
||||||
|
|
||||||
|
required init(coder: NSCoder) {
|
||||||
|
preconditionFailure()
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func onPressed() {
|
||||||
|
self.pressed()
|
||||||
|
}
|
||||||
|
|
||||||
|
override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
|
||||||
|
self.alpha = 0.6
|
||||||
|
|
||||||
|
return super.beginTracking(touch, with: event)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func endTracking(_ touch: UITouch?, with event: UIEvent?) {
|
||||||
|
let alpha = self.alpha
|
||||||
|
self.alpha = 1.0
|
||||||
|
self.layer.animateAlpha(from: alpha, to: 1.0, duration: 0.25)
|
||||||
|
|
||||||
|
super.endTracking(touch, with: event)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func cancelTracking(with event: UIEvent?) {
|
||||||
|
let alpha = self.alpha
|
||||||
|
self.alpha = 1.0
|
||||||
|
self.layer.animateAlpha(from: alpha, to: 1.0, duration: 0.25)
|
||||||
|
|
||||||
|
super.cancelTracking(with: event)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
|
||||||
|
let alpha = self.alpha
|
||||||
|
self.alpha = 1.0
|
||||||
|
self.layer.animateAlpha(from: alpha, to: 1.0, duration: 0.25)
|
||||||
|
|
||||||
|
super.touchesCancelled(touches, with: event)
|
||||||
|
}
|
||||||
|
|
||||||
|
func update(theme: PresentationTheme, title: String, compact: Bool) -> CGSize {
|
||||||
|
let textConstrainedWidth: CGFloat = 100.0
|
||||||
|
|
||||||
|
let needsVibrancy = !theme.overallDarkAppearance && compact
|
||||||
|
|
||||||
|
let foregroundColor: UIColor
|
||||||
|
let backgroundColor: UIColor
|
||||||
|
|
||||||
|
if compact {
|
||||||
|
foregroundColor = theme.chat.inputMediaPanel.panelContentVibrantOverlayColor
|
||||||
|
backgroundColor = foregroundColor.withMultipliedAlpha(0.2)
|
||||||
|
} else {
|
||||||
|
foregroundColor = theme.list.itemCheckColors.foregroundColor
|
||||||
|
backgroundColor = theme.list.itemCheckColors.fillColor
|
||||||
|
}
|
||||||
|
|
||||||
|
self.backgroundLayer.backgroundColor = backgroundColor.cgColor
|
||||||
|
self.tintBackgroundLayer.backgroundColor = UIColor.white.withAlphaComponent(0.2).cgColor
|
||||||
|
|
||||||
|
self.tintContainerLayer.isHidden = !needsVibrancy
|
||||||
|
|
||||||
|
let textSize: CGSize
|
||||||
|
if let currentTextLayout = self.currentTextLayout, currentTextLayout.string == title, currentTextLayout.color == foregroundColor, currentTextLayout.constrainedWidth == textConstrainedWidth {
|
||||||
|
textSize = currentTextLayout.size
|
||||||
|
} else {
|
||||||
|
let font: UIFont = compact ? Font.medium(11.0) : Font.semibold(15.0)
|
||||||
|
let string = NSAttributedString(string: title.uppercased(), font: font, textColor: foregroundColor)
|
||||||
|
let tintString = NSAttributedString(string: title.uppercased(), font: font, textColor: .white)
|
||||||
|
let stringBounds = string.boundingRect(with: CGSize(width: textConstrainedWidth, height: 100.0), options: .usesLineFragmentOrigin, context: nil)
|
||||||
|
textSize = CGSize(width: ceil(stringBounds.width), height: ceil(stringBounds.height))
|
||||||
|
self.textLayer.contents = generateImage(textSize, opaque: false, scale: 0.0, rotatedContext: { size, context in
|
||||||
|
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||||
|
UIGraphicsPushContext(context)
|
||||||
|
|
||||||
|
string.draw(in: stringBounds)
|
||||||
|
|
||||||
|
UIGraphicsPopContext()
|
||||||
|
})?.cgImage
|
||||||
|
self.tintTextLayer.contents = generateImage(textSize, opaque: false, scale: 0.0, rotatedContext: { size, context in
|
||||||
|
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||||
|
UIGraphicsPushContext(context)
|
||||||
|
|
||||||
|
tintString.draw(in: stringBounds)
|
||||||
|
|
||||||
|
UIGraphicsPopContext()
|
||||||
|
})?.cgImage
|
||||||
|
self.currentTextLayout = (title, foregroundColor, textConstrainedWidth, textSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
let size = CGSize(width: textSize.width + (compact ? 6.0 : 16.0) * 2.0, height: compact ? 16.0 : 28.0)
|
||||||
|
|
||||||
|
let textFrame = CGRect(origin: CGPoint(x: floor((size.width - textSize.width) / 2.0), y: floorToScreenPixels((size.height - textSize.height) / 2.0)), size: textSize)
|
||||||
|
self.textLayer.frame = textFrame
|
||||||
|
self.tintTextLayer.frame = textFrame
|
||||||
|
|
||||||
|
self.backgroundLayer.frame = CGRect(origin: CGPoint(), size: size)
|
||||||
|
self.backgroundLayer.cornerRadius = min(size.width, size.height) / 2.0
|
||||||
|
|
||||||
|
self.tintBackgroundLayer.frame = self.backgroundLayer.frame
|
||||||
|
self.tintBackgroundLayer.cornerRadius = self.backgroundLayer.cornerRadius
|
||||||
|
|
||||||
|
return size
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,525 @@
|
|||||||
|
import Foundation
|
||||||
|
import UIKit
|
||||||
|
import Display
|
||||||
|
import ComponentFlow
|
||||||
|
import AccountContext
|
||||||
|
import TelegramCore
|
||||||
|
import TelegramPresentationData
|
||||||
|
import AnimationCache
|
||||||
|
import MultiAnimationRenderer
|
||||||
|
|
||||||
|
final class GroupHeaderLayer: UIView {
|
||||||
|
override static var layerClass: AnyClass {
|
||||||
|
return PassthroughLayer.self
|
||||||
|
}
|
||||||
|
|
||||||
|
private let actionPressed: () -> Void
|
||||||
|
private let performItemAction: (EmojiPagerContentComponent.Item, UIView, CGRect, CALayer) -> Void
|
||||||
|
|
||||||
|
private let textLayer: SimpleLayer
|
||||||
|
private let tintTextLayer: SimpleLayer
|
||||||
|
|
||||||
|
private var subtitleLayer: SimpleLayer?
|
||||||
|
private var tintSubtitleLayer: SimpleLayer?
|
||||||
|
private var lockIconLayer: SimpleLayer?
|
||||||
|
private var tintLockIconLayer: SimpleLayer?
|
||||||
|
private var badgeLayer: SimpleLayer?
|
||||||
|
private var tintBadgeLayer: SimpleLayer?
|
||||||
|
private(set) var clearIconLayer: SimpleLayer?
|
||||||
|
private var tintClearIconLayer: SimpleLayer?
|
||||||
|
private var separatorLayer: SimpleLayer?
|
||||||
|
private var tintSeparatorLayer: SimpleLayer?
|
||||||
|
private var actionButton: GroupHeaderActionButton?
|
||||||
|
|
||||||
|
private var groupEmbeddedView: GroupEmbeddedView?
|
||||||
|
|
||||||
|
private var theme: PresentationTheme?
|
||||||
|
|
||||||
|
private var currentTextLayout: (string: String, color: UIColor, constrainedWidth: CGFloat, size: CGSize)?
|
||||||
|
private var currentSubtitleLayout: (string: String, color: UIColor, constrainedWidth: CGFloat, size: CGSize)?
|
||||||
|
|
||||||
|
let tintContentLayer: SimpleLayer
|
||||||
|
|
||||||
|
init(actionPressed: @escaping () -> Void, performItemAction: @escaping (EmojiPagerContentComponent.Item, UIView, CGRect, CALayer) -> Void) {
|
||||||
|
self.actionPressed = actionPressed
|
||||||
|
self.performItemAction = performItemAction
|
||||||
|
|
||||||
|
self.textLayer = SimpleLayer()
|
||||||
|
self.tintTextLayer = SimpleLayer()
|
||||||
|
|
||||||
|
self.tintContentLayer = SimpleLayer()
|
||||||
|
|
||||||
|
super.init(frame: CGRect())
|
||||||
|
|
||||||
|
self.layer.addSublayer(self.textLayer)
|
||||||
|
self.tintContentLayer.addSublayer(self.tintTextLayer)
|
||||||
|
|
||||||
|
(self.layer as? PassthroughLayer)?.mirrorLayer = self.tintContentLayer
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func update(
|
||||||
|
context: AccountContext,
|
||||||
|
theme: PresentationTheme,
|
||||||
|
forceNeedsVibrancy: Bool,
|
||||||
|
layoutType: EmojiPagerContentComponent.ItemLayoutType,
|
||||||
|
hasTopSeparator: Bool,
|
||||||
|
actionButtonTitle: String?,
|
||||||
|
actionButtonIsCompact: Bool,
|
||||||
|
title: String,
|
||||||
|
subtitle: String?,
|
||||||
|
badge: String?,
|
||||||
|
isPremiumLocked: Bool,
|
||||||
|
hasClear: Bool,
|
||||||
|
embeddedItems: [EmojiPagerContentComponent.Item]?,
|
||||||
|
isStickers: Bool,
|
||||||
|
constrainedSize: CGSize,
|
||||||
|
insets: UIEdgeInsets,
|
||||||
|
cache: AnimationCache,
|
||||||
|
renderer: MultiAnimationRenderer,
|
||||||
|
attemptSynchronousLoad: Bool
|
||||||
|
) -> (size: CGSize, centralContentWidth: CGFloat) {
|
||||||
|
var themeUpdated = false
|
||||||
|
if self.theme !== theme {
|
||||||
|
self.theme = theme
|
||||||
|
themeUpdated = true
|
||||||
|
}
|
||||||
|
|
||||||
|
let needsVibrancy = !theme.overallDarkAppearance || forceNeedsVibrancy
|
||||||
|
|
||||||
|
let textOffsetY: CGFloat
|
||||||
|
if hasTopSeparator {
|
||||||
|
textOffsetY = 9.0
|
||||||
|
} else {
|
||||||
|
textOffsetY = 0.0
|
||||||
|
}
|
||||||
|
|
||||||
|
let subtitleColor: UIColor
|
||||||
|
if theme.overallDarkAppearance && forceNeedsVibrancy {
|
||||||
|
subtitleColor = theme.chat.inputMediaPanel.panelContentVibrantOverlayColor.withMultipliedAlpha(0.2)
|
||||||
|
} else {
|
||||||
|
subtitleColor = theme.chat.inputMediaPanel.panelContentVibrantOverlayColor
|
||||||
|
}
|
||||||
|
|
||||||
|
let color: UIColor
|
||||||
|
let needsTintText: Bool
|
||||||
|
if subtitle != nil {
|
||||||
|
color = theme.chat.inputPanel.primaryTextColor
|
||||||
|
needsTintText = false
|
||||||
|
} else {
|
||||||
|
color = subtitleColor
|
||||||
|
needsTintText = true
|
||||||
|
}
|
||||||
|
|
||||||
|
let titleHorizontalOffset: CGFloat
|
||||||
|
if isPremiumLocked {
|
||||||
|
titleHorizontalOffset = 10.0 + 2.0
|
||||||
|
} else {
|
||||||
|
titleHorizontalOffset = 0.0
|
||||||
|
}
|
||||||
|
|
||||||
|
var actionButtonSize: CGSize?
|
||||||
|
if let actionButtonTitle = actionButtonTitle {
|
||||||
|
let actionButton: GroupHeaderActionButton
|
||||||
|
if let current = self.actionButton {
|
||||||
|
actionButton = current
|
||||||
|
} else {
|
||||||
|
actionButton = GroupHeaderActionButton(pressed: self.actionPressed)
|
||||||
|
self.actionButton = actionButton
|
||||||
|
self.addSubview(actionButton)
|
||||||
|
self.tintContentLayer.addSublayer(actionButton.tintContainerLayer)
|
||||||
|
}
|
||||||
|
|
||||||
|
actionButtonSize = actionButton.update(theme: theme, title: actionButtonTitle, compact: actionButtonIsCompact)
|
||||||
|
} else {
|
||||||
|
if let actionButton = self.actionButton {
|
||||||
|
self.actionButton = nil
|
||||||
|
actionButton.removeFromSuperview()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var clearSize: CGSize = .zero
|
||||||
|
var clearWidth: CGFloat = 0.0
|
||||||
|
if hasClear {
|
||||||
|
var updateImage = themeUpdated
|
||||||
|
|
||||||
|
let clearIconLayer: SimpleLayer
|
||||||
|
if let current = self.clearIconLayer {
|
||||||
|
clearIconLayer = current
|
||||||
|
} else {
|
||||||
|
updateImage = true
|
||||||
|
clearIconLayer = SimpleLayer()
|
||||||
|
self.clearIconLayer = clearIconLayer
|
||||||
|
self.layer.addSublayer(clearIconLayer)
|
||||||
|
}
|
||||||
|
let tintClearIconLayer: SimpleLayer
|
||||||
|
if let current = self.tintClearIconLayer {
|
||||||
|
tintClearIconLayer = current
|
||||||
|
} else {
|
||||||
|
updateImage = true
|
||||||
|
tintClearIconLayer = SimpleLayer()
|
||||||
|
self.tintClearIconLayer = tintClearIconLayer
|
||||||
|
self.tintContentLayer.addSublayer(tintClearIconLayer)
|
||||||
|
}
|
||||||
|
|
||||||
|
tintClearIconLayer.isHidden = !needsVibrancy
|
||||||
|
|
||||||
|
clearSize = clearIconLayer.bounds.size
|
||||||
|
if updateImage, let image = PresentationResourcesChat.chatInputMediaPanelGridDismissImage(theme, color: subtitleColor) {
|
||||||
|
clearSize = image.size
|
||||||
|
clearIconLayer.contents = image.cgImage
|
||||||
|
}
|
||||||
|
if updateImage, let image = PresentationResourcesChat.chatInputMediaPanelGridDismissImage(theme, color: .white) {
|
||||||
|
tintClearIconLayer.contents = image.cgImage
|
||||||
|
}
|
||||||
|
|
||||||
|
tintClearIconLayer.frame = clearIconLayer.frame
|
||||||
|
clearWidth = 4.0 + clearSize.width
|
||||||
|
} else {
|
||||||
|
if let clearIconLayer = self.clearIconLayer {
|
||||||
|
self.clearIconLayer = nil
|
||||||
|
clearIconLayer.removeFromSuperlayer()
|
||||||
|
}
|
||||||
|
if let tintClearIconLayer = self.tintClearIconLayer {
|
||||||
|
self.tintClearIconLayer = nil
|
||||||
|
tintClearIconLayer.removeFromSuperlayer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var textConstrainedWidth = constrainedSize.width - titleHorizontalOffset - 10.0
|
||||||
|
if let actionButtonSize = actionButtonSize {
|
||||||
|
if actionButtonIsCompact {
|
||||||
|
textConstrainedWidth -= actionButtonSize.width * 2.0 + 10.0
|
||||||
|
} else {
|
||||||
|
textConstrainedWidth -= actionButtonSize.width + 10.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if clearWidth > 0.0 {
|
||||||
|
textConstrainedWidth -= clearWidth + 8.0
|
||||||
|
}
|
||||||
|
|
||||||
|
let textSize: CGSize
|
||||||
|
if let currentTextLayout = self.currentTextLayout, currentTextLayout.string == title, currentTextLayout.color == color, currentTextLayout.constrainedWidth == textConstrainedWidth {
|
||||||
|
textSize = currentTextLayout.size
|
||||||
|
} else {
|
||||||
|
let font: UIFont
|
||||||
|
let stringValue: String
|
||||||
|
if subtitle == nil {
|
||||||
|
font = Font.medium(13.0)
|
||||||
|
stringValue = title.uppercased()
|
||||||
|
} else {
|
||||||
|
font = Font.semibold(16.0)
|
||||||
|
stringValue = title
|
||||||
|
}
|
||||||
|
let string = NSAttributedString(string: stringValue, font: font, textColor: color)
|
||||||
|
let whiteString = NSAttributedString(string: stringValue, font: font, textColor: .white)
|
||||||
|
let stringBounds = string.boundingRect(with: CGSize(width: textConstrainedWidth, height: 18.0), options: [.usesLineFragmentOrigin, .truncatesLastVisibleLine], context: nil)
|
||||||
|
textSize = CGSize(width: ceil(stringBounds.width), height: ceil(stringBounds.height))
|
||||||
|
self.textLayer.contents = generateImage(textSize, opaque: false, scale: 0.0, rotatedContext: { size, context in
|
||||||
|
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||||
|
UIGraphicsPushContext(context)
|
||||||
|
|
||||||
|
//string.draw(in: stringBounds)
|
||||||
|
string.draw(with: stringBounds, options: [.usesLineFragmentOrigin, .truncatesLastVisibleLine], context: nil)
|
||||||
|
|
||||||
|
UIGraphicsPopContext()
|
||||||
|
})?.cgImage
|
||||||
|
self.tintTextLayer.contents = generateImage(textSize, opaque: false, scale: 0.0, rotatedContext: { size, context in
|
||||||
|
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||||
|
UIGraphicsPushContext(context)
|
||||||
|
|
||||||
|
//whiteString.draw(in: stringBounds)
|
||||||
|
whiteString.draw(with: stringBounds, options: [.usesLineFragmentOrigin, .truncatesLastVisibleLine], context: nil)
|
||||||
|
|
||||||
|
UIGraphicsPopContext()
|
||||||
|
})?.cgImage
|
||||||
|
self.tintTextLayer.isHidden = !needsVibrancy
|
||||||
|
self.currentTextLayout = (title, color, textConstrainedWidth, textSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
var badgeSize: CGSize = .zero
|
||||||
|
if let badge {
|
||||||
|
func generateBadgeImage(color: UIColor) -> UIImage? {
|
||||||
|
let string = NSAttributedString(string: badge, font: Font.semibold(11.0), textColor: .white)
|
||||||
|
let stringBounds = string.boundingRect(with: CGSize(width: 120, height: 18.0), options: [.usesLineFragmentOrigin, .truncatesLastVisibleLine], context: nil)
|
||||||
|
|
||||||
|
let badgeSize = CGSize(width: stringBounds.width + 8.0, height: 16.0)
|
||||||
|
return generateImage(badgeSize, opaque: false, scale: 0.0, rotatedContext: { size, context in
|
||||||
|
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||||
|
|
||||||
|
context.setFillColor(color.cgColor)
|
||||||
|
context.addPath(UIBezierPath(roundedRect: CGRect(origin: .zero, size: badgeSize), cornerRadius: badgeSize.height / 2.0).cgPath)
|
||||||
|
context.fillPath()
|
||||||
|
|
||||||
|
context.setBlendMode(.clear)
|
||||||
|
|
||||||
|
UIGraphicsPushContext(context)
|
||||||
|
|
||||||
|
string.draw(with: CGRect(origin: CGPoint(x: floorToScreenPixels((badgeSize.width - stringBounds.size.width) / 2.0), y: floorToScreenPixels((badgeSize.height - stringBounds.size.height) / 2.0)), size: stringBounds.size), options: [.usesLineFragmentOrigin, .truncatesLastVisibleLine], context: nil)
|
||||||
|
|
||||||
|
UIGraphicsPopContext()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
let badgeLayer: SimpleLayer
|
||||||
|
if let current = self.badgeLayer {
|
||||||
|
badgeLayer = current
|
||||||
|
} else {
|
||||||
|
badgeLayer = SimpleLayer()
|
||||||
|
self.badgeLayer = badgeLayer
|
||||||
|
self.layer.addSublayer(badgeLayer)
|
||||||
|
|
||||||
|
if let image = generateBadgeImage(color: color.withMultipliedAlpha(0.66)) {
|
||||||
|
badgeLayer.contents = image.cgImage
|
||||||
|
badgeLayer.bounds = CGRect(origin: .zero, size: image.size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
badgeSize = badgeLayer.bounds.size
|
||||||
|
|
||||||
|
let tintBadgeLayer: SimpleLayer
|
||||||
|
if let current = self.tintBadgeLayer {
|
||||||
|
tintBadgeLayer = current
|
||||||
|
} else {
|
||||||
|
tintBadgeLayer = SimpleLayer()
|
||||||
|
self.tintBadgeLayer = tintBadgeLayer
|
||||||
|
self.tintContentLayer.addSublayer(tintBadgeLayer)
|
||||||
|
|
||||||
|
if let image = generateBadgeImage(color: .white) {
|
||||||
|
tintBadgeLayer.contents = image.cgImage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if let badgeLayer = self.badgeLayer {
|
||||||
|
self.badgeLayer = nil
|
||||||
|
badgeLayer.removeFromSuperlayer()
|
||||||
|
}
|
||||||
|
if let tintBadgeLayer = self.tintBadgeLayer {
|
||||||
|
self.tintBadgeLayer = nil
|
||||||
|
tintBadgeLayer.removeFromSuperlayer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let textFrame: CGRect
|
||||||
|
if subtitle == nil {
|
||||||
|
textFrame = CGRect(origin: CGPoint(x: titleHorizontalOffset + floor((constrainedSize.width - titleHorizontalOffset - (textSize.width + badgeSize.width)) / 2.0), y: textOffsetY), size: textSize)
|
||||||
|
} else {
|
||||||
|
textFrame = CGRect(origin: CGPoint(x: titleHorizontalOffset, y: textOffsetY), size: textSize)
|
||||||
|
}
|
||||||
|
self.textLayer.frame = textFrame
|
||||||
|
self.tintTextLayer.frame = textFrame
|
||||||
|
self.tintTextLayer.isHidden = !needsTintText
|
||||||
|
|
||||||
|
if let badgeLayer = self.badgeLayer, let tintBadgeLayer = self.tintBadgeLayer {
|
||||||
|
badgeLayer.frame = CGRect(origin: CGPoint(x: textFrame.maxX + 4.0, y: 0.0), size: badgeLayer.frame.size)
|
||||||
|
tintBadgeLayer.frame = badgeLayer.frame
|
||||||
|
}
|
||||||
|
|
||||||
|
if isPremiumLocked {
|
||||||
|
let lockIconLayer: SimpleLayer
|
||||||
|
if let current = self.lockIconLayer {
|
||||||
|
lockIconLayer = current
|
||||||
|
} else {
|
||||||
|
lockIconLayer = SimpleLayer()
|
||||||
|
self.lockIconLayer = lockIconLayer
|
||||||
|
self.layer.addSublayer(lockIconLayer)
|
||||||
|
}
|
||||||
|
if let image = PresentationResourcesChat.chatEntityKeyboardLock(theme, color: color) {
|
||||||
|
let imageSize = image.size
|
||||||
|
lockIconLayer.contents = image.cgImage
|
||||||
|
lockIconLayer.frame = CGRect(origin: CGPoint(x: textFrame.minX - imageSize.width - 3.0, y: 2.0 + UIScreenPixel), size: imageSize)
|
||||||
|
} else {
|
||||||
|
lockIconLayer.contents = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
let tintLockIconLayer: SimpleLayer
|
||||||
|
if let current = self.tintLockIconLayer {
|
||||||
|
tintLockIconLayer = current
|
||||||
|
} else {
|
||||||
|
tintLockIconLayer = SimpleLayer()
|
||||||
|
self.tintLockIconLayer = tintLockIconLayer
|
||||||
|
self.tintContentLayer.addSublayer(tintLockIconLayer)
|
||||||
|
}
|
||||||
|
if let image = PresentationResourcesChat.chatEntityKeyboardLock(theme, color: .white) {
|
||||||
|
tintLockIconLayer.contents = image.cgImage
|
||||||
|
tintLockIconLayer.frame = lockIconLayer.frame
|
||||||
|
tintLockIconLayer.isHidden = !needsVibrancy
|
||||||
|
} else {
|
||||||
|
tintLockIconLayer.contents = nil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if let lockIconLayer = self.lockIconLayer {
|
||||||
|
self.lockIconLayer = nil
|
||||||
|
lockIconLayer.removeFromSuperlayer()
|
||||||
|
}
|
||||||
|
if let tintLockIconLayer = self.tintLockIconLayer {
|
||||||
|
self.tintLockIconLayer = nil
|
||||||
|
tintLockIconLayer.removeFromSuperlayer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let subtitleSize: CGSize
|
||||||
|
if let subtitle = subtitle {
|
||||||
|
var updateSubtitleContents: UIImage?
|
||||||
|
var updateTintSubtitleContents: UIImage?
|
||||||
|
if let currentSubtitleLayout = self.currentSubtitleLayout, currentSubtitleLayout.string == subtitle, currentSubtitleLayout.color == subtitleColor, currentSubtitleLayout.constrainedWidth == textConstrainedWidth {
|
||||||
|
subtitleSize = currentSubtitleLayout.size
|
||||||
|
} else {
|
||||||
|
let string = NSAttributedString(string: subtitle, font: Font.regular(15.0), textColor: subtitleColor)
|
||||||
|
let whiteString = NSAttributedString(string: subtitle, font: Font.regular(15.0), textColor: .white)
|
||||||
|
let stringBounds = string.boundingRect(with: CGSize(width: textConstrainedWidth, height: 100.0), options: .usesLineFragmentOrigin, context: nil)
|
||||||
|
subtitleSize = CGSize(width: ceil(stringBounds.width), height: ceil(stringBounds.height))
|
||||||
|
updateSubtitleContents = generateImage(subtitleSize, opaque: false, scale: 0.0, rotatedContext: { size, context in
|
||||||
|
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||||
|
UIGraphicsPushContext(context)
|
||||||
|
|
||||||
|
string.draw(in: stringBounds)
|
||||||
|
|
||||||
|
UIGraphicsPopContext()
|
||||||
|
})
|
||||||
|
updateTintSubtitleContents = generateImage(subtitleSize, opaque: false, scale: 0.0, rotatedContext: { size, context in
|
||||||
|
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||||
|
UIGraphicsPushContext(context)
|
||||||
|
|
||||||
|
whiteString.draw(in: stringBounds)
|
||||||
|
|
||||||
|
UIGraphicsPopContext()
|
||||||
|
})
|
||||||
|
self.currentSubtitleLayout = (subtitle, subtitleColor, textConstrainedWidth, subtitleSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
let subtitleLayer: SimpleLayer
|
||||||
|
if let current = self.subtitleLayer {
|
||||||
|
subtitleLayer = current
|
||||||
|
} else {
|
||||||
|
subtitleLayer = SimpleLayer()
|
||||||
|
self.subtitleLayer = subtitleLayer
|
||||||
|
self.layer.addSublayer(subtitleLayer)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let updateSubtitleContents = updateSubtitleContents {
|
||||||
|
subtitleLayer.contents = updateSubtitleContents.cgImage
|
||||||
|
}
|
||||||
|
|
||||||
|
let tintSubtitleLayer: SimpleLayer
|
||||||
|
if let current = self.tintSubtitleLayer {
|
||||||
|
tintSubtitleLayer = current
|
||||||
|
} else {
|
||||||
|
tintSubtitleLayer = SimpleLayer()
|
||||||
|
self.tintSubtitleLayer = tintSubtitleLayer
|
||||||
|
self.tintContentLayer.addSublayer(tintSubtitleLayer)
|
||||||
|
}
|
||||||
|
tintSubtitleLayer.isHidden = !needsVibrancy
|
||||||
|
|
||||||
|
if let updateTintSubtitleContents = updateTintSubtitleContents {
|
||||||
|
tintSubtitleLayer.contents = updateTintSubtitleContents.cgImage
|
||||||
|
}
|
||||||
|
|
||||||
|
let subtitleFrame = CGRect(origin: CGPoint(x: 0.0, y: textFrame.maxY + 1.0), size: subtitleSize)
|
||||||
|
subtitleLayer.frame = subtitleFrame
|
||||||
|
tintSubtitleLayer.frame = subtitleFrame
|
||||||
|
} else {
|
||||||
|
subtitleSize = CGSize()
|
||||||
|
if let subtitleLayer = self.subtitleLayer {
|
||||||
|
self.subtitleLayer = nil
|
||||||
|
subtitleLayer.removeFromSuperlayer()
|
||||||
|
}
|
||||||
|
if let tintSubtitleLayer = self.tintSubtitleLayer {
|
||||||
|
self.tintSubtitleLayer = nil
|
||||||
|
tintSubtitleLayer.removeFromSuperlayer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.clearIconLayer?.frame = CGRect(origin: CGPoint(x: constrainedSize.width - clearSize.width, y: floorToScreenPixels((textSize.height - clearSize.height) / 2.0)), size: clearSize)
|
||||||
|
|
||||||
|
var size: CGSize
|
||||||
|
size = CGSize(width: constrainedSize.width, height: constrainedSize.height)
|
||||||
|
|
||||||
|
if let embeddedItems = embeddedItems {
|
||||||
|
let groupEmbeddedView: GroupEmbeddedView
|
||||||
|
if let current = self.groupEmbeddedView {
|
||||||
|
groupEmbeddedView = current
|
||||||
|
} else {
|
||||||
|
groupEmbeddedView = GroupEmbeddedView(performItemAction: self.performItemAction)
|
||||||
|
self.groupEmbeddedView = groupEmbeddedView
|
||||||
|
self.addSubview(groupEmbeddedView)
|
||||||
|
}
|
||||||
|
|
||||||
|
let groupEmbeddedViewSize = CGSize(width: constrainedSize.width + insets.left + insets.right, height: 36.0)
|
||||||
|
groupEmbeddedView.frame = CGRect(origin: CGPoint(x: -insets.left, y: size.height - groupEmbeddedViewSize.height), size: groupEmbeddedViewSize)
|
||||||
|
groupEmbeddedView.update(
|
||||||
|
context: context,
|
||||||
|
theme: theme,
|
||||||
|
insets: insets,
|
||||||
|
size: groupEmbeddedViewSize,
|
||||||
|
items: embeddedItems,
|
||||||
|
isStickers: isStickers,
|
||||||
|
cache: cache,
|
||||||
|
renderer: renderer,
|
||||||
|
attemptSynchronousLoad: attemptSynchronousLoad
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
if let groupEmbeddedView = self.groupEmbeddedView {
|
||||||
|
self.groupEmbeddedView = nil
|
||||||
|
groupEmbeddedView.removeFromSuperview()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let actionButtonSize = actionButtonSize, let actionButton = self.actionButton {
|
||||||
|
let actionButtonFrame = CGRect(origin: CGPoint(x: size.width - actionButtonSize.width, y: textFrame.minY + (actionButtonIsCompact ? 0.0 : 3.0)), size: actionButtonSize)
|
||||||
|
actionButton.bounds = CGRect(origin: CGPoint(), size: actionButtonFrame.size)
|
||||||
|
actionButton.center = actionButtonFrame.center
|
||||||
|
}
|
||||||
|
|
||||||
|
if hasTopSeparator {
|
||||||
|
let separatorLayer: SimpleLayer
|
||||||
|
if let current = self.separatorLayer {
|
||||||
|
separatorLayer = current
|
||||||
|
} else {
|
||||||
|
separatorLayer = SimpleLayer()
|
||||||
|
self.separatorLayer = separatorLayer
|
||||||
|
self.layer.addSublayer(separatorLayer)
|
||||||
|
}
|
||||||
|
separatorLayer.backgroundColor = subtitleColor.cgColor
|
||||||
|
separatorLayer.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: UIScreenPixel))
|
||||||
|
|
||||||
|
let tintSeparatorLayer: SimpleLayer
|
||||||
|
if let current = self.tintSeparatorLayer {
|
||||||
|
tintSeparatorLayer = current
|
||||||
|
} else {
|
||||||
|
tintSeparatorLayer = SimpleLayer()
|
||||||
|
self.tintSeparatorLayer = tintSeparatorLayer
|
||||||
|
self.tintContentLayer.addSublayer(tintSeparatorLayer)
|
||||||
|
}
|
||||||
|
tintSeparatorLayer.backgroundColor = UIColor.white.cgColor
|
||||||
|
tintSeparatorLayer.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: UIScreenPixel))
|
||||||
|
|
||||||
|
tintSeparatorLayer.isHidden = !needsVibrancy
|
||||||
|
} else {
|
||||||
|
if let separatorLayer = self.separatorLayer {
|
||||||
|
self.separatorLayer = separatorLayer
|
||||||
|
separatorLayer.removeFromSuperlayer()
|
||||||
|
}
|
||||||
|
if let tintSeparatorLayer = self.tintSeparatorLayer {
|
||||||
|
self.tintSeparatorLayer = tintSeparatorLayer
|
||||||
|
tintSeparatorLayer.removeFromSuperlayer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (size, titleHorizontalOffset + textSize.width + clearWidth)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||||
|
return super.hitTest(point, with: event)
|
||||||
|
}
|
||||||
|
|
||||||
|
func tapGesture(point: CGPoint) -> Bool {
|
||||||
|
if let groupEmbeddedView = self.groupEmbeddedView {
|
||||||
|
return groupEmbeddedView.tapGesture(point: self.convert(point, to: groupEmbeddedView))
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,346 @@
|
|||||||
|
import Foundation
|
||||||
|
import UIKit
|
||||||
|
import Display
|
||||||
|
import ComponentFlow
|
||||||
|
|
||||||
|
public class PassthroughLayer: CALayer {
|
||||||
|
public var mirrorLayer: CALayer?
|
||||||
|
|
||||||
|
override init() {
|
||||||
|
super.init()
|
||||||
|
}
|
||||||
|
|
||||||
|
override init(layer: Any) {
|
||||||
|
super.init(layer: layer)
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override public var position: CGPoint {
|
||||||
|
get {
|
||||||
|
return super.position
|
||||||
|
} set(value) {
|
||||||
|
if let mirrorLayer = self.mirrorLayer {
|
||||||
|
mirrorLayer.position = value
|
||||||
|
}
|
||||||
|
super.position = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override public var bounds: CGRect {
|
||||||
|
get {
|
||||||
|
return super.bounds
|
||||||
|
} set(value) {
|
||||||
|
if let mirrorLayer = self.mirrorLayer {
|
||||||
|
mirrorLayer.bounds = value
|
||||||
|
}
|
||||||
|
super.bounds = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override public var opacity: Float {
|
||||||
|
get {
|
||||||
|
return super.opacity
|
||||||
|
} set(value) {
|
||||||
|
if let mirrorLayer = self.mirrorLayer {
|
||||||
|
mirrorLayer.opacity = value
|
||||||
|
}
|
||||||
|
super.opacity = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override public var sublayerTransform: CATransform3D {
|
||||||
|
get {
|
||||||
|
return super.sublayerTransform
|
||||||
|
} set(value) {
|
||||||
|
if let mirrorLayer = self.mirrorLayer {
|
||||||
|
mirrorLayer.sublayerTransform = value
|
||||||
|
}
|
||||||
|
super.sublayerTransform = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override public var transform: CATransform3D {
|
||||||
|
get {
|
||||||
|
return super.transform
|
||||||
|
} set(value) {
|
||||||
|
if let mirrorLayer = self.mirrorLayer {
|
||||||
|
mirrorLayer.transform = value
|
||||||
|
}
|
||||||
|
super.transform = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override public func add(_ animation: CAAnimation, forKey key: String?) {
|
||||||
|
if let mirrorLayer = self.mirrorLayer {
|
||||||
|
mirrorLayer.add(animation, forKey: key)
|
||||||
|
}
|
||||||
|
|
||||||
|
super.add(animation, forKey: key)
|
||||||
|
}
|
||||||
|
|
||||||
|
override public func removeAllAnimations() {
|
||||||
|
if let mirrorLayer = self.mirrorLayer {
|
||||||
|
mirrorLayer.removeAllAnimations()
|
||||||
|
}
|
||||||
|
|
||||||
|
super.removeAllAnimations()
|
||||||
|
}
|
||||||
|
|
||||||
|
override public func removeAnimation(forKey: String) {
|
||||||
|
if let mirrorLayer = self.mirrorLayer {
|
||||||
|
mirrorLayer.removeAnimation(forKey: forKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
super.removeAnimation(forKey: forKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
open class PassthroughView: UIView {
|
||||||
|
override public static var layerClass: AnyClass {
|
||||||
|
return PassthroughLayer.self
|
||||||
|
}
|
||||||
|
|
||||||
|
public let passthroughView: UIView
|
||||||
|
|
||||||
|
override public init(frame: CGRect) {
|
||||||
|
self.passthroughView = UIView()
|
||||||
|
|
||||||
|
super.init(frame: frame)
|
||||||
|
|
||||||
|
(self.layer as? PassthroughLayer)?.mirrorLayer = self.passthroughView.layer
|
||||||
|
}
|
||||||
|
|
||||||
|
required public init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PassthroughShapeLayer: CAShapeLayer {
|
||||||
|
var mirrorLayer: CAShapeLayer?
|
||||||
|
|
||||||
|
override init() {
|
||||||
|
super.init()
|
||||||
|
}
|
||||||
|
|
||||||
|
override init(layer: Any) {
|
||||||
|
super.init(layer: layer)
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override var position: CGPoint {
|
||||||
|
get {
|
||||||
|
return super.position
|
||||||
|
} set(value) {
|
||||||
|
if let mirrorLayer = self.mirrorLayer {
|
||||||
|
mirrorLayer.position = value
|
||||||
|
}
|
||||||
|
super.position = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override var bounds: CGRect {
|
||||||
|
get {
|
||||||
|
return super.bounds
|
||||||
|
} set(value) {
|
||||||
|
if let mirrorLayer = self.mirrorLayer {
|
||||||
|
mirrorLayer.bounds = value
|
||||||
|
}
|
||||||
|
super.bounds = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override var opacity: Float {
|
||||||
|
get {
|
||||||
|
return super.opacity
|
||||||
|
} set(value) {
|
||||||
|
if let mirrorLayer = self.mirrorLayer {
|
||||||
|
mirrorLayer.opacity = value
|
||||||
|
}
|
||||||
|
super.opacity = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override var sublayerTransform: CATransform3D {
|
||||||
|
get {
|
||||||
|
return super.sublayerTransform
|
||||||
|
} set(value) {
|
||||||
|
if let mirrorLayer = self.mirrorLayer {
|
||||||
|
mirrorLayer.sublayerTransform = value
|
||||||
|
}
|
||||||
|
super.sublayerTransform = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override var transform: CATransform3D {
|
||||||
|
get {
|
||||||
|
return super.transform
|
||||||
|
} set(value) {
|
||||||
|
if let mirrorLayer = self.mirrorLayer {
|
||||||
|
mirrorLayer.transform = value
|
||||||
|
}
|
||||||
|
super.transform = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override var path: CGPath? {
|
||||||
|
get {
|
||||||
|
return super.path
|
||||||
|
} set(value) {
|
||||||
|
if let mirrorLayer = self.mirrorLayer {
|
||||||
|
mirrorLayer.path = value
|
||||||
|
}
|
||||||
|
super.path = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override var fillColor: CGColor? {
|
||||||
|
get {
|
||||||
|
return super.fillColor
|
||||||
|
} set(value) {
|
||||||
|
if let mirrorLayer = self.mirrorLayer {
|
||||||
|
mirrorLayer.fillColor = value
|
||||||
|
}
|
||||||
|
super.fillColor = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override var fillRule: CAShapeLayerFillRule {
|
||||||
|
get {
|
||||||
|
return super.fillRule
|
||||||
|
} set(value) {
|
||||||
|
if let mirrorLayer = self.mirrorLayer {
|
||||||
|
mirrorLayer.fillRule = value
|
||||||
|
}
|
||||||
|
super.fillRule = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override var strokeColor: CGColor? {
|
||||||
|
get {
|
||||||
|
return super.strokeColor
|
||||||
|
} set(value) {
|
||||||
|
/*if let mirrorLayer = self.mirrorLayer {
|
||||||
|
mirrorLayer.strokeColor = value
|
||||||
|
}*/
|
||||||
|
super.strokeColor = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override var strokeStart: CGFloat {
|
||||||
|
get {
|
||||||
|
return super.strokeStart
|
||||||
|
} set(value) {
|
||||||
|
if let mirrorLayer = self.mirrorLayer {
|
||||||
|
mirrorLayer.strokeStart = value
|
||||||
|
}
|
||||||
|
super.strokeStart = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override var strokeEnd: CGFloat {
|
||||||
|
get {
|
||||||
|
return super.strokeEnd
|
||||||
|
} set(value) {
|
||||||
|
if let mirrorLayer = self.mirrorLayer {
|
||||||
|
mirrorLayer.strokeEnd = value
|
||||||
|
}
|
||||||
|
super.strokeEnd = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override var lineWidth: CGFloat {
|
||||||
|
get {
|
||||||
|
return super.lineWidth
|
||||||
|
} set(value) {
|
||||||
|
if let mirrorLayer = self.mirrorLayer {
|
||||||
|
mirrorLayer.lineWidth = value
|
||||||
|
}
|
||||||
|
super.lineWidth = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override var miterLimit: CGFloat {
|
||||||
|
get {
|
||||||
|
return super.miterLimit
|
||||||
|
} set(value) {
|
||||||
|
if let mirrorLayer = self.mirrorLayer {
|
||||||
|
mirrorLayer.miterLimit = value
|
||||||
|
}
|
||||||
|
super.miterLimit = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override var lineCap: CAShapeLayerLineCap {
|
||||||
|
get {
|
||||||
|
return super.lineCap
|
||||||
|
} set(value) {
|
||||||
|
if let mirrorLayer = self.mirrorLayer {
|
||||||
|
mirrorLayer.lineCap = value
|
||||||
|
}
|
||||||
|
super.lineCap = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override var lineJoin: CAShapeLayerLineJoin {
|
||||||
|
get {
|
||||||
|
return super.lineJoin
|
||||||
|
} set(value) {
|
||||||
|
if let mirrorLayer = self.mirrorLayer {
|
||||||
|
mirrorLayer.lineJoin = value
|
||||||
|
}
|
||||||
|
super.lineJoin = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override var lineDashPhase: CGFloat {
|
||||||
|
get {
|
||||||
|
return super.lineDashPhase
|
||||||
|
} set(value) {
|
||||||
|
if let mirrorLayer = self.mirrorLayer {
|
||||||
|
mirrorLayer.lineDashPhase = value
|
||||||
|
}
|
||||||
|
super.lineDashPhase = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override var lineDashPattern: [NSNumber]? {
|
||||||
|
get {
|
||||||
|
return super.lineDashPattern
|
||||||
|
} set(value) {
|
||||||
|
if let mirrorLayer = self.mirrorLayer {
|
||||||
|
mirrorLayer.lineDashPattern = value
|
||||||
|
}
|
||||||
|
super.lineDashPattern = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override func add(_ animation: CAAnimation, forKey key: String?) {
|
||||||
|
if let mirrorLayer = self.mirrorLayer {
|
||||||
|
mirrorLayer.add(animation, forKey: key)
|
||||||
|
}
|
||||||
|
|
||||||
|
super.add(animation, forKey: key)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func removeAllAnimations() {
|
||||||
|
if let mirrorLayer = self.mirrorLayer {
|
||||||
|
mirrorLayer.removeAllAnimations()
|
||||||
|
}
|
||||||
|
|
||||||
|
super.removeAllAnimations()
|
||||||
|
}
|
||||||
|
|
||||||
|
override func removeAnimation(forKey: String) {
|
||||||
|
if let mirrorLayer = self.mirrorLayer {
|
||||||
|
mirrorLayer.removeAnimation(forKey: forKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
super.removeAnimation(forKey: forKey)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,140 @@
|
|||||||
|
import Foundation
|
||||||
|
import UIKit
|
||||||
|
import Display
|
||||||
|
import ComponentFlow
|
||||||
|
import AccountContext
|
||||||
|
import TelegramCore
|
||||||
|
|
||||||
|
private let premiumBadgeIcon: UIImage? = generateTintedImage(image: UIImage(bundleImageName: "Chat List/PeerPremiumIcon"), color: .white)
|
||||||
|
private let featuredBadgeIcon: UIImage? = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Media/PanelBadgeAdd"), color: .white)
|
||||||
|
private let lockedBadgeIcon: UIImage? = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Media/PanelBadgeLock"), color: .white)
|
||||||
|
|
||||||
|
private let itemBadgeTextFont: UIFont = {
|
||||||
|
return Font.regular(10.0)
|
||||||
|
}()
|
||||||
|
|
||||||
|
final class PremiumBadgeView: UIView {
|
||||||
|
private let context: AccountContext
|
||||||
|
|
||||||
|
private var badge: EmojiKeyboardItemLayer.Badge?
|
||||||
|
|
||||||
|
let contentLayer: SimpleLayer
|
||||||
|
private let overlayColorLayer: SimpleLayer
|
||||||
|
private let iconLayer: SimpleLayer
|
||||||
|
private var customFileLayer: InlineFileIconLayer?
|
||||||
|
|
||||||
|
init(context: AccountContext) {
|
||||||
|
self.context = context
|
||||||
|
|
||||||
|
self.contentLayer = SimpleLayer()
|
||||||
|
self.contentLayer.contentsGravity = .resize
|
||||||
|
self.contentLayer.masksToBounds = true
|
||||||
|
|
||||||
|
self.overlayColorLayer = SimpleLayer()
|
||||||
|
self.overlayColorLayer.masksToBounds = true
|
||||||
|
|
||||||
|
self.iconLayer = SimpleLayer()
|
||||||
|
|
||||||
|
super.init(frame: CGRect())
|
||||||
|
|
||||||
|
self.layer.addSublayer(self.contentLayer)
|
||||||
|
self.layer.addSublayer(self.overlayColorLayer)
|
||||||
|
self.layer.addSublayer(self.iconLayer)
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func update(transition: Transition, badge: EmojiKeyboardItemLayer.Badge, backgroundColor: UIColor, size: CGSize) {
|
||||||
|
if self.badge != badge {
|
||||||
|
self.badge = badge
|
||||||
|
|
||||||
|
switch badge {
|
||||||
|
case .premium:
|
||||||
|
self.iconLayer.contents = premiumBadgeIcon?.cgImage
|
||||||
|
case .featured:
|
||||||
|
self.iconLayer.contents = featuredBadgeIcon?.cgImage
|
||||||
|
case .locked:
|
||||||
|
self.iconLayer.contents = lockedBadgeIcon?.cgImage
|
||||||
|
case let .text(text):
|
||||||
|
let string = NSAttributedString(string: text, font: itemBadgeTextFont)
|
||||||
|
let size = CGSize(width: 12.0, height: 12.0)
|
||||||
|
let stringBounds = string.boundingRect(with: CGSize(width: 100.0, height: 100.0), options: .usesLineFragmentOrigin, context: nil)
|
||||||
|
let image = generateImage(size, rotatedContext: { size, context in
|
||||||
|
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||||
|
UIGraphicsPushContext(context)
|
||||||
|
string.draw(at: CGPoint(x: floor((size.width - stringBounds.width) * 0.5), y: floor((size.height - stringBounds.height) * 0.5)))
|
||||||
|
UIGraphicsPopContext()
|
||||||
|
})
|
||||||
|
self.iconLayer.contents = image?.cgImage
|
||||||
|
case .customFile:
|
||||||
|
self.iconLayer.contents = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if case let .customFile(customFile) = badge {
|
||||||
|
let customFileLayer: InlineFileIconLayer
|
||||||
|
if let current = self.customFileLayer {
|
||||||
|
customFileLayer = current
|
||||||
|
} else {
|
||||||
|
customFileLayer = InlineFileIconLayer(
|
||||||
|
context: self.context,
|
||||||
|
userLocation: .other,
|
||||||
|
attemptSynchronousLoad: false,
|
||||||
|
file: customFile,
|
||||||
|
cache: self.context.animationCache,
|
||||||
|
renderer: self.context.animationRenderer,
|
||||||
|
unique: false,
|
||||||
|
placeholderColor: .clear,
|
||||||
|
pointSize: CGSize(width: 18.0, height: 18.0),
|
||||||
|
dynamicColor: nil
|
||||||
|
)
|
||||||
|
self.customFileLayer = customFileLayer
|
||||||
|
self.layer.addSublayer(customFileLayer)
|
||||||
|
}
|
||||||
|
let _ = customFileLayer
|
||||||
|
} else {
|
||||||
|
if let customFileLayer = self.customFileLayer {
|
||||||
|
self.customFileLayer = nil
|
||||||
|
customFileLayer.removeFromSuperlayer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let iconInset: CGFloat
|
||||||
|
switch badge {
|
||||||
|
case .premium:
|
||||||
|
iconInset = 2.0
|
||||||
|
case .featured:
|
||||||
|
iconInset = 0.0
|
||||||
|
case .locked:
|
||||||
|
iconInset = 0.0
|
||||||
|
case .text, .customFile:
|
||||||
|
iconInset = 0.0
|
||||||
|
}
|
||||||
|
|
||||||
|
switch badge {
|
||||||
|
case .text, .customFile:
|
||||||
|
self.contentLayer.isHidden = true
|
||||||
|
self.overlayColorLayer.isHidden = true
|
||||||
|
default:
|
||||||
|
self.contentLayer.isHidden = false
|
||||||
|
self.overlayColorLayer.isHidden = false
|
||||||
|
}
|
||||||
|
|
||||||
|
self.overlayColorLayer.backgroundColor = backgroundColor.cgColor
|
||||||
|
|
||||||
|
transition.setFrame(layer: self.contentLayer, frame: CGRect(origin: CGPoint(), size: size))
|
||||||
|
transition.setCornerRadius(layer: self.contentLayer, cornerRadius: min(size.width / 2.0, size.height / 2.0))
|
||||||
|
|
||||||
|
transition.setFrame(layer: self.overlayColorLayer, frame: CGRect(origin: CGPoint(), size: size))
|
||||||
|
transition.setCornerRadius(layer: self.overlayColorLayer, cornerRadius: min(size.width / 2.0, size.height / 2.0))
|
||||||
|
|
||||||
|
transition.setFrame(layer: self.iconLayer, frame: CGRect(origin: CGPoint(), size: size).insetBy(dx: iconInset, dy: iconInset))
|
||||||
|
|
||||||
|
if let customFileLayer = self.customFileLayer {
|
||||||
|
let iconSize = CGSize(width: 18.0, height: 18.0)
|
||||||
|
transition.setFrame(layer: customFileLayer, frame: CGRect(origin: CGPoint(), size: iconSize))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,138 @@
|
|||||||
|
import Foundation
|
||||||
|
import UIKit
|
||||||
|
import Display
|
||||||
|
import ComponentFlow
|
||||||
|
import TelegramPresentationData
|
||||||
|
|
||||||
|
final class WarpView: UIView {
|
||||||
|
private final class WarpPartView: UIView {
|
||||||
|
let cloneView: PortalView
|
||||||
|
|
||||||
|
init?(contentView: PortalSourceView) {
|
||||||
|
guard let cloneView = PortalView(matchPosition: false) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
self.cloneView = cloneView
|
||||||
|
|
||||||
|
super.init(frame: CGRect())
|
||||||
|
|
||||||
|
self.layer.anchorPoint = CGPoint(x: 0.5, y: 0.0)
|
||||||
|
|
||||||
|
self.clipsToBounds = true
|
||||||
|
self.addSubview(cloneView.view)
|
||||||
|
contentView.addPortal(view: cloneView)
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func update(containerSize: CGSize, rect: CGRect, transition: Transition) {
|
||||||
|
transition.setFrame(view: self.cloneView.view, frame: CGRect(origin: CGPoint(x: -rect.minX, y: -rect.minY), size: CGSize(width: containerSize.width, height: containerSize.height)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let contentView: PortalSourceView
|
||||||
|
|
||||||
|
private let clippingView: UIView
|
||||||
|
|
||||||
|
private var warpViews: [WarpPartView] = []
|
||||||
|
private let warpMaskContainer: UIView
|
||||||
|
private let warpMaskGradientLayer: SimpleGradientLayer
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
self.contentView = PortalSourceView()
|
||||||
|
self.clippingView = UIView()
|
||||||
|
|
||||||
|
self.warpMaskContainer = UIView()
|
||||||
|
self.warpMaskGradientLayer = SimpleGradientLayer()
|
||||||
|
self.warpMaskContainer.layer.mask = self.warpMaskGradientLayer
|
||||||
|
|
||||||
|
super.init(frame: frame)
|
||||||
|
|
||||||
|
self.clippingView.addSubview(self.contentView)
|
||||||
|
|
||||||
|
self.clippingView.clipsToBounds = true
|
||||||
|
self.addSubview(self.clippingView)
|
||||||
|
self.addSubview(self.warpMaskContainer)
|
||||||
|
|
||||||
|
for _ in 0 ..< 8 {
|
||||||
|
if let warpView = WarpPartView(contentView: self.contentView) {
|
||||||
|
self.warpViews.append(warpView)
|
||||||
|
self.warpMaskContainer.addSubview(warpView)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func update(size: CGSize, topInset: CGFloat, warpHeight: CGFloat, theme: PresentationTheme, transition: Transition) {
|
||||||
|
transition.setFrame(view: self.contentView, frame: CGRect(origin: CGPoint(), size: size))
|
||||||
|
|
||||||
|
let allItemsHeight = warpHeight * 0.5
|
||||||
|
for i in 0 ..< self.warpViews.count {
|
||||||
|
let itemHeight = warpHeight / CGFloat(self.warpViews.count)
|
||||||
|
let itemFraction = CGFloat(i + 1) / CGFloat(self.warpViews.count)
|
||||||
|
let _ = itemHeight
|
||||||
|
|
||||||
|
let da = CGFloat.pi * 0.5 / CGFloat(self.warpViews.count)
|
||||||
|
let alpha = CGFloat.pi * 0.5 - itemFraction * CGFloat.pi * 0.5
|
||||||
|
let endPoint = CGPoint(x: cos(alpha), y: sin(alpha))
|
||||||
|
let prevAngle = alpha + da
|
||||||
|
let prevPt = CGPoint(x: cos(prevAngle), y: sin(prevAngle))
|
||||||
|
var angle: CGFloat
|
||||||
|
angle = -atan2(endPoint.y - prevPt.y, endPoint.x - prevPt.x)
|
||||||
|
|
||||||
|
let itemLengthVector = CGPoint(x: endPoint.x - prevPt.x, y: endPoint.y - prevPt.y)
|
||||||
|
let itemLength = sqrt(itemLengthVector.x * itemLengthVector.x + itemLengthVector.y * itemLengthVector.y) * warpHeight * 0.5
|
||||||
|
let _ = itemLength
|
||||||
|
|
||||||
|
var transform: CATransform3D
|
||||||
|
transform = CATransform3DIdentity
|
||||||
|
transform.m34 = 1.0 / 240.0
|
||||||
|
|
||||||
|
transform = CATransform3DTranslate(transform, 0.0, prevPt.x * allItemsHeight, (1.0 - prevPt.y) * allItemsHeight)
|
||||||
|
transform = CATransform3DRotate(transform, angle, 1.0, 0.0, 0.0)
|
||||||
|
|
||||||
|
let positionY = size.height - allItemsHeight + 4.0 + CGFloat(i) * itemLength
|
||||||
|
let rect = CGRect(origin: CGPoint(x: 0.0, y: positionY), size: CGSize(width: size.width, height: itemLength))
|
||||||
|
transition.setPosition(view: self.warpViews[i], position: CGPoint(x: rect.midX, y: 4.0))
|
||||||
|
transition.setBounds(view: self.warpViews[i], bounds: CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: itemLength)))
|
||||||
|
transition.setTransform(view: self.warpViews[i], transform: transform)
|
||||||
|
self.warpViews[i].update(containerSize: size, rect: rect, transition: transition)
|
||||||
|
}
|
||||||
|
|
||||||
|
let clippingTopInset: CGFloat = topInset
|
||||||
|
let frame = CGRect(origin: CGPoint(x: 0.0, y: clippingTopInset), size: CGSize(width: size.width, height: -clippingTopInset + size.height - 21.0))
|
||||||
|
transition.setPosition(view: self.clippingView, position: frame.center)
|
||||||
|
transition.setBounds(view: self.clippingView, bounds: CGRect(origin: CGPoint(x: 0.0, y: clippingTopInset), size: frame.size))
|
||||||
|
self.clippingView.clipsToBounds = true
|
||||||
|
|
||||||
|
transition.setFrame(view: self.warpMaskContainer, frame: CGRect(origin: CGPoint(x: 0.0, y: size.height - allItemsHeight), size: CGSize(width: size.width, height: allItemsHeight)))
|
||||||
|
|
||||||
|
var locations: [NSNumber] = []
|
||||||
|
var colors: [CGColor] = []
|
||||||
|
let numStops = 6
|
||||||
|
for i in 0 ..< numStops {
|
||||||
|
let step = CGFloat(i) / CGFloat(numStops - 1)
|
||||||
|
locations.append(step as NSNumber)
|
||||||
|
colors.append(UIColor.black.withAlphaComponent(1.0 - step * step).cgColor)
|
||||||
|
}
|
||||||
|
|
||||||
|
let gradientHeight: CGFloat = 6.0
|
||||||
|
self.warpMaskGradientLayer.startPoint = CGPoint(x: 0.0, y: (allItemsHeight - gradientHeight) / allItemsHeight)
|
||||||
|
self.warpMaskGradientLayer.endPoint = CGPoint(x: 0.0, y: 1.0)
|
||||||
|
|
||||||
|
self.warpMaskGradientLayer.locations = locations
|
||||||
|
self.warpMaskGradientLayer.colors = colors
|
||||||
|
self.warpMaskGradientLayer.type = .axial
|
||||||
|
|
||||||
|
transition.setFrame(layer: self.warpMaskGradientLayer, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: allItemsHeight)))
|
||||||
|
}
|
||||||
|
|
||||||
|
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||||
|
return self.contentView.hitTest(point, with: event)
|
||||||
|
}
|
||||||
|
}
|
@ -105,7 +105,7 @@ final class EmojiListInputComponent: Component {
|
|||||||
private var component: EmojiListInputComponent?
|
private var component: EmojiListInputComponent?
|
||||||
private weak var state: EmptyComponentState?
|
private weak var state: EmptyComponentState?
|
||||||
|
|
||||||
private var itemLayers: [Int64: EmojiPagerContentComponent.View.ItemLayer] = [:]
|
private var itemLayers: [Int64: EmojiKeyboardItemLayer] = [:]
|
||||||
private let trailingPlaceholder = ComponentView<Empty>()
|
private let trailingPlaceholder = ComponentView<Empty>()
|
||||||
private let caretIndicator: CaretIndicatorView
|
private let caretIndicator: CaretIndicatorView
|
||||||
|
|
||||||
@ -239,7 +239,7 @@ final class EmojiListInputComponent: Component {
|
|||||||
|
|
||||||
var itemTransition = transition
|
var itemTransition = transition
|
||||||
var animateIn = false
|
var animateIn = false
|
||||||
let itemLayer: EmojiPagerContentComponent.View.ItemLayer
|
let itemLayer: EmojiKeyboardItemLayer
|
||||||
if let current = self.itemLayers[itemKey] {
|
if let current = self.itemLayers[itemKey] {
|
||||||
itemLayer = current
|
itemLayer = current
|
||||||
} else {
|
} else {
|
||||||
@ -249,7 +249,7 @@ final class EmojiListInputComponent: Component {
|
|||||||
let animationData = EntityKeyboardAnimationData(
|
let animationData = EntityKeyboardAnimationData(
|
||||||
file: item.file
|
file: item.file
|
||||||
)
|
)
|
||||||
itemLayer = EmojiPagerContentComponent.View.ItemLayer(
|
itemLayer = EmojiKeyboardItemLayer(
|
||||||
item: EmojiPagerContentComponent.Item(
|
item: EmojiPagerContentComponent.Item(
|
||||||
animationData: animationData,
|
animationData: animationData,
|
||||||
content: .animation(animationData),
|
content: .animation(animationData),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user