mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
325 lines
16 KiB
Swift
325 lines
16 KiB
Swift
import Foundation
|
|
import UIKit
|
|
import Display
|
|
import TelegramPresentationData
|
|
import ComponentFlow
|
|
import ComponentDisplayAdapters
|
|
import EntityKeyboard
|
|
import AccountContext
|
|
import PagerComponent
|
|
import AudioToolbox
|
|
|
|
public final class EmojiSelectionComponent: Component {
|
|
public typealias EnvironmentType = Empty
|
|
|
|
public let theme: PresentationTheme
|
|
public let strings: PresentationStrings
|
|
public let sideInset: CGFloat
|
|
public let bottomInset: CGFloat
|
|
public let deviceMetrics: DeviceMetrics
|
|
public let emojiContent: EmojiPagerContentComponent?
|
|
public let stickerContent: EmojiPagerContentComponent?
|
|
public let backgroundIconColor: UIColor?
|
|
public let backgroundColor: UIColor
|
|
public let separatorColor: UIColor
|
|
public let backspace: (() -> Void)?
|
|
|
|
public init(
|
|
theme: PresentationTheme,
|
|
strings: PresentationStrings,
|
|
sideInset: CGFloat,
|
|
bottomInset: CGFloat,
|
|
deviceMetrics: DeviceMetrics,
|
|
emojiContent: EmojiPagerContentComponent?,
|
|
stickerContent: EmojiPagerContentComponent?,
|
|
backgroundIconColor: UIColor?,
|
|
backgroundColor: UIColor,
|
|
separatorColor: UIColor,
|
|
backspace: (() -> Void)?
|
|
) {
|
|
self.theme = theme
|
|
self.strings = strings
|
|
self.sideInset = sideInset
|
|
self.bottomInset = bottomInset
|
|
self.deviceMetrics = deviceMetrics
|
|
self.emojiContent = emojiContent
|
|
self.stickerContent = stickerContent
|
|
self.backgroundIconColor = backgroundIconColor
|
|
self.backgroundColor = backgroundColor
|
|
self.separatorColor = separatorColor
|
|
self.backspace = backspace
|
|
}
|
|
|
|
public static func ==(lhs: EmojiSelectionComponent, rhs: EmojiSelectionComponent) -> Bool {
|
|
if lhs.theme !== rhs.theme {
|
|
return false
|
|
}
|
|
if lhs.strings != rhs.strings {
|
|
return false
|
|
}
|
|
if lhs.sideInset != rhs.sideInset {
|
|
return false
|
|
}
|
|
if lhs.bottomInset != rhs.bottomInset {
|
|
return false
|
|
}
|
|
if lhs.deviceMetrics != rhs.deviceMetrics {
|
|
return false
|
|
}
|
|
if lhs.emojiContent != rhs.emojiContent {
|
|
return false
|
|
}
|
|
if lhs.stickerContent != rhs.stickerContent {
|
|
return false
|
|
}
|
|
if lhs.backgroundIconColor != rhs.backgroundIconColor {
|
|
return false
|
|
}
|
|
if lhs.backgroundColor != rhs.backgroundColor {
|
|
return false
|
|
}
|
|
if lhs.separatorColor != rhs.separatorColor {
|
|
return false
|
|
}
|
|
if (lhs.backspace == nil) != (rhs.backspace == nil) {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
public final class View: UIView {
|
|
private let keyboardView: ComponentView<Empty>
|
|
private let keyboardClippingView: UIView
|
|
private let panelHostView: PagerExternalTopPanelContainer
|
|
private let panelBackgroundView: BlurredBackgroundView
|
|
private let panelSeparatorView: UIView
|
|
private let shadowView: UIImageView
|
|
private let cornersView: UIImageView
|
|
|
|
private let backspaceButton = ComponentView<Empty>()
|
|
private let backspaceBackgroundView: UIImageView
|
|
|
|
private var component: EmojiSelectionComponent?
|
|
private weak var state: EmptyComponentState?
|
|
|
|
private var isSearchActive: Bool = false
|
|
|
|
override init(frame: CGRect) {
|
|
self.keyboardView = ComponentView<Empty>()
|
|
self.keyboardClippingView = UIView()
|
|
self.panelHostView = PagerExternalTopPanelContainer()
|
|
self.panelBackgroundView = BlurredBackgroundView(color: .clear, enableBlur: true)
|
|
self.panelSeparatorView = UIView()
|
|
self.shadowView = UIImageView()
|
|
self.cornersView = UIImageView()
|
|
|
|
self.backspaceBackgroundView = UIImageView()
|
|
|
|
super.init(frame: frame)
|
|
|
|
self.addSubview(self.keyboardClippingView)
|
|
self.addSubview(self.panelBackgroundView)
|
|
self.addSubview(self.panelSeparatorView)
|
|
self.addSubview(self.panelHostView)
|
|
self.addSubview(self.cornersView)
|
|
self.addSubview(self.shadowView)
|
|
|
|
self.shadowView.image = generateImage(CGSize(width: 16.0, height: 16.0), rotatedContext: { size, context in
|
|
context.clear(CGRect(origin: CGPoint(), size: size))
|
|
context.setShadow(offset: CGSize(), blur: 40.0, color: UIColor(white: 0.0, alpha: 0.05).cgColor)
|
|
context.setFillColor(UIColor.black.cgColor)
|
|
context.fillEllipse(in: CGRect(origin: CGPoint(x: 0.0, y: 8.0), size: size))
|
|
context.setBlendMode(.copy)
|
|
context.setFillColor(UIColor.clear.cgColor)
|
|
context.fillEllipse(in: CGRect(origin: CGPoint(x: 0.0, y: 8.0), size: size).insetBy(dx: -0.5, dy: -0.5))
|
|
})?.stretchableImage(withLeftCapWidth: 8, topCapHeight: 16)
|
|
|
|
self.cornersView.image = generateImage(CGSize(width: 16.0 + 1.0, height: 16.0), rotatedContext: { size, context in
|
|
context.setFillColor(UIColor.white.cgColor)
|
|
context.fill(CGRect(origin: CGPoint(), size: size))
|
|
context.setBlendMode(.copy)
|
|
context.setFillColor(UIColor.clear.cgColor)
|
|
context.addPath(UIBezierPath(roundedRect: CGRect(origin: CGPoint(x: 0.0, y: 8.0), size: size), cornerRadius: 8.0).cgPath)
|
|
context.fillPath()
|
|
context.clear(CGRect(origin: CGPoint(x: 8.0, y: 0.0), size: CGSize(width: 1.0, height: size.height)))
|
|
})?.withRenderingMode(.alwaysTemplate).stretchableImage(withLeftCapWidth: 8, topCapHeight: 16)
|
|
}
|
|
|
|
required init?(coder: NSCoder) {
|
|
fatalError("init(coder:) has not been implemented")
|
|
}
|
|
|
|
deinit {
|
|
}
|
|
|
|
public func internalRequestUpdate(transition: Transition) {
|
|
if let keyboardComponentView = self.keyboardView.view as? EntityKeyboardComponent.View {
|
|
keyboardComponentView.state?.updated(transition: transition)
|
|
}
|
|
}
|
|
|
|
func update(component: EmojiSelectionComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: Transition) -> CGSize {
|
|
self.backgroundColor = component.backgroundColor
|
|
let panelBackgroundColor = component.backgroundColor.withMultipliedAlpha(0.85)
|
|
self.panelBackgroundView.updateColor(color: panelBackgroundColor, transition: .immediate)
|
|
self.panelSeparatorView.backgroundColor = component.separatorColor
|
|
|
|
let previousComponent = self.component
|
|
self.component = component
|
|
self.state = state
|
|
|
|
var resolvedHeight: CGFloat = min(340.0, max(50.0, availableSize.height - 200.0))
|
|
if self.isSearchActive {
|
|
resolvedHeight = min(availableSize.height, resolvedHeight + 200.0)
|
|
}
|
|
|
|
self.cornersView.tintColor = component.theme.list.blocksBackgroundColor
|
|
transition.setFrame(view: self.cornersView, frame: CGRect(origin: CGPoint(x: 0.0, y: -8.0), size: CGSize(width: availableSize.width, height: 16.0)))
|
|
|
|
transition.setFrame(view: self.shadowView, frame: CGRect(origin: CGPoint(x: 0.0, y: -8.0), size: CGSize(width: availableSize.width, height: 16.0)))
|
|
|
|
let topPanelHeight: CGFloat = 42.0
|
|
|
|
let backspaceButtonInset = UIEdgeInsets(top: 9.0, left: 0.0, bottom: 36.0, right: 9.0)
|
|
let backspaceButtonSize = CGSize(width: 36.0, height: 36.0)
|
|
|
|
let _ = self.backspaceButton.update(
|
|
transition: transition,
|
|
component: AnyComponent(Button(
|
|
content: AnyComponent(HStack([], spacing: 0.0)),
|
|
action: { [weak self] in
|
|
guard let self, let component = self.component else {
|
|
return
|
|
}
|
|
component.backspace?()
|
|
AudioServicesPlaySystemSound(1155)
|
|
}
|
|
).withHoldAction({ [weak self] in
|
|
guard let self, let component = self.component else {
|
|
return
|
|
}
|
|
AudioServicesPlaySystemSound(1155)
|
|
component.backspace?()
|
|
})),
|
|
environment: {},
|
|
containerSize: backspaceButtonSize
|
|
)
|
|
|
|
if previousComponent?.theme !== component.theme {
|
|
self.backspaceBackgroundView.image = generateImage(CGSize(width: backspaceButtonSize.width + 12.0 * 2.0, height: backspaceButtonSize.height + 12.0 * 2.0), rotatedContext: { size, context in
|
|
context.clear(CGRect(origin: CGPoint(), size: size))
|
|
context.setShadow(offset: CGSize(), blur: 40.0, color: UIColor(white: 0.0, alpha: 0.15).cgColor)
|
|
context.setFillColor(component.theme.list.plainBackgroundColor.cgColor)
|
|
context.fillEllipse(in: CGRect(origin: CGPoint(x: 12.0, y: 12.0), size: backspaceButtonSize))
|
|
|
|
context.setShadow(offset: CGSize(), blur: 0.0, color: nil)
|
|
if let image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Media/EntityInputClearIcon"), color: component.theme.chat.inputMediaPanel.panelIconColor) {
|
|
let imageSize = image.size
|
|
context.draw(image.cgImage!, in: CGRect(origin: CGPoint(x: 12.0 + floor((backspaceButtonSize.width - imageSize.width) * 0.5) - 1.0, y: 12.0 + floor((backspaceButtonSize.height - imageSize.height) * 0.5)), size: imageSize))
|
|
}
|
|
})
|
|
}
|
|
self.backspaceBackgroundView.frame = CGRect(origin: CGPoint(), size: backspaceButtonSize).insetBy(dx: -12.0, dy: -12.0)
|
|
let backspaceButtonFrame = CGRect(origin: CGPoint(x: availableSize.width - component.sideInset - backspaceButtonInset.right - backspaceButtonSize.width, y: resolvedHeight - component.bottomInset - backspaceButtonInset.bottom), size: backspaceButtonSize)
|
|
|
|
if let backspaceButtonView = self.backspaceButton.view {
|
|
if backspaceButtonView.superview == nil {
|
|
backspaceButtonView.addSubview(self.backspaceBackgroundView)
|
|
self.addSubview(backspaceButtonView)
|
|
}
|
|
transition.setPosition(view: backspaceButtonView, position: backspaceButtonFrame.center)
|
|
transition.setBounds(view: backspaceButtonView, bounds: CGRect(origin: CGPoint(), size: backspaceButtonFrame.size))
|
|
|
|
if component.backspace != nil {
|
|
transition.setAlpha(view: backspaceButtonView, alpha: 1.0)
|
|
transition.setScale(view: backspaceButtonView, scale: 1.0)
|
|
} else {
|
|
transition.setAlpha(view: backspaceButtonView, alpha: 0.0)
|
|
transition.setScale(view: backspaceButtonView, scale: 0.001)
|
|
}
|
|
}
|
|
|
|
self.keyboardView.parentState = state
|
|
let keyboardSize = self.keyboardView.update(
|
|
transition: transition.withUserData(EmojiPagerContentComponent.SynchronousLoadBehavior(isDisabled: true)),
|
|
component: AnyComponent(EntityKeyboardComponent(
|
|
theme: component.theme,
|
|
strings: component.strings,
|
|
isContentInFocus: true,
|
|
containerInsets: UIEdgeInsets(top: topPanelHeight - 34.0, left: component.sideInset, bottom: component.bottomInset + 16.0, right: component.sideInset),
|
|
topPanelInsets: UIEdgeInsets(top: 0.0, left: 4.0, bottom: 0.0, right: 4.0),
|
|
emojiContent: component.emojiContent?.withCustomTintColor(component.theme.list.itemPrimaryTextColor),
|
|
stickerContent: component.stickerContent?.withCustomTintColor(component.theme.list.itemPrimaryTextColor),
|
|
maskContent: nil,
|
|
gifContent: nil,
|
|
hasRecentGifs: false,
|
|
availableGifSearchEmojies: [],
|
|
defaultToEmojiTab: true,
|
|
externalTopPanelContainer: self.panelHostView,
|
|
externalBottomPanelContainer: nil,
|
|
displayTopPanelBackground: .blur,
|
|
topPanelExtensionUpdated: { _, _ in },
|
|
topPanelScrollingOffset: { _, _ in },
|
|
hideInputUpdated: { _, _, _ in },
|
|
hideTopPanelUpdated: { [weak self] hideTopPanel, transition in
|
|
guard let self else {
|
|
return
|
|
}
|
|
if self.isSearchActive != hideTopPanel {
|
|
self.isSearchActive = hideTopPanel
|
|
self.state?.updated(transition: transition)
|
|
}
|
|
},
|
|
switchToTextInput: {},
|
|
switchToGifSubject: { _ in },
|
|
reorderItems: { _, _ in },
|
|
makeSearchContainerNode: { _ in return nil },
|
|
contentIdUpdated: { _ in },
|
|
deviceMetrics: component.deviceMetrics,
|
|
hiddenInputHeight: 0.0,
|
|
inputHeight: 0.0,
|
|
displayBottomPanel: false,
|
|
isExpanded: true,
|
|
clipContentToTopPanel: false,
|
|
useExternalSearchContainer: false,
|
|
customTintColor: component.backgroundIconColor
|
|
)),
|
|
environment: {},
|
|
containerSize: CGSize(width: availableSize.width, height: resolvedHeight)
|
|
)
|
|
if let keyboardComponentView = self.keyboardView.view {
|
|
if keyboardComponentView.superview == nil {
|
|
self.keyboardClippingView.addSubview(keyboardComponentView)
|
|
}
|
|
|
|
if panelBackgroundColor.alpha < 0.01 {
|
|
self.keyboardClippingView.clipsToBounds = true
|
|
} else {
|
|
self.keyboardClippingView.clipsToBounds = false
|
|
}
|
|
|
|
transition.setFrame(view: self.keyboardClippingView, frame: CGRect(origin: CGPoint(x: 0.0, y: topPanelHeight), size: CGSize(width: availableSize.width, height: resolvedHeight - topPanelHeight)))
|
|
|
|
transition.setFrame(view: keyboardComponentView, frame: CGRect(origin: CGPoint(x: 0.0, y: -topPanelHeight), size: keyboardSize))
|
|
transition.setFrame(view: self.panelHostView, frame: CGRect(origin: CGPoint(x: 0.0, y: topPanelHeight - 34.0), size: CGSize(width: keyboardSize.width, height: 0.0)))
|
|
|
|
transition.setFrame(view: self.panelBackgroundView, frame: CGRect(origin: CGPoint(), size: CGSize(width: keyboardSize.width, height: topPanelHeight)))
|
|
self.panelBackgroundView.update(size: self.panelBackgroundView.bounds.size, transition: transition.containedViewLayoutTransition)
|
|
|
|
transition.setFrame(view: self.panelSeparatorView, frame: CGRect(origin: CGPoint(x: 0.0, y: topPanelHeight), size: CGSize(width: keyboardSize.width, height: UIScreenPixel)))
|
|
transition.setAlpha(view: self.panelSeparatorView, alpha: 1.0)
|
|
}
|
|
|
|
return CGSize(width: availableSize.width, height: resolvedHeight)
|
|
}
|
|
}
|
|
|
|
public func makeView() -> View {
|
|
return View(frame: CGRect())
|
|
}
|
|
|
|
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: Transition) -> CGSize {
|
|
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
|
}
|
|
}
|