mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-23 22:55:00 +00:00
Merge branch 'master' into glass
# Conflicts: # submodules/AttachmentUI/Sources/AttachmentPanel.swift # submodules/ChatPresentationInterfaceState/Sources/ChatPanelInterfaceInteraction.swift # submodules/TelegramUI/BUILD # submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsController.swift # submodules/TelegramUI/Components/Chat/ChatTextInputActionButtonsNode/Sources/ChatTextInputActionButtonsNode.swift # submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelNode.swift # submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift # submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionControllerNode.swift # submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift # submodules/TelegramUI/Sources/ChatInterfaceStateInputPanels.swift
This commit is contained in:
@@ -0,0 +1,365 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import AsyncDisplayKit
|
||||
import TelegramPresentationData
|
||||
import ChatPresentationInterfaceState
|
||||
import GlassBackgroundComponent
|
||||
import ComponentFlow
|
||||
import LottieAnimationComponent
|
||||
import LottieComponent
|
||||
|
||||
private let accessoryButtonFont = Font.medium(14.0)
|
||||
|
||||
final class AccessoryItemIconButton: HighlightTrackingButton, GlassBackgroundView.ContentView {
|
||||
private var item: ChatTextInputAccessoryItem
|
||||
private var theme: PresentationTheme
|
||||
private var strings: PresentationStrings
|
||||
private var width: CGFloat
|
||||
private let iconImageView: UIImageView
|
||||
private let tintMaskIconImageView: UIImageView
|
||||
private var textView: ImmediateTextView?
|
||||
private var tintMaskTextView: ImmediateTextView?
|
||||
private var animationView: ComponentView<Empty>?
|
||||
private var tintMaskAnimationView: UIImageView?
|
||||
|
||||
override static var layerClass: AnyClass {
|
||||
return GlassBackgroundView.ContentLayer.self
|
||||
}
|
||||
|
||||
let tintMask = UIView()
|
||||
|
||||
init(item: ChatTextInputAccessoryItem, theme: PresentationTheme, strings: PresentationStrings) {
|
||||
self.item = item
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
|
||||
self.iconImageView = UIImageView()
|
||||
self.tintMaskIconImageView = UIImageView()
|
||||
|
||||
let (image, text, accessibilityLabel, alpha, _) = AccessoryItemIconButton.imageAndInsets(item: item, theme: theme, strings: strings)
|
||||
|
||||
self.width = AccessoryItemIconButton.calculateWidth(item: item, image: image, text: text, strings: strings)
|
||||
|
||||
super.init(frame: CGRect())
|
||||
|
||||
(self.layer as? GlassBackgroundView.ContentLayer)?.targetLayer = self.tintMask.layer
|
||||
|
||||
self.isAccessibilityElement = true
|
||||
self.accessibilityTraits = [.button]
|
||||
|
||||
self.iconImageView.isUserInteractionEnabled = false
|
||||
self.addSubview(self.iconImageView)
|
||||
|
||||
self.tintMask.addSubview(self.tintMaskIconImageView)
|
||||
|
||||
switch item {
|
||||
case .input, .botInput, .silentPost:
|
||||
self.iconImageView.isHidden = true
|
||||
self.tintMaskIconImageView.isHidden = self.iconImageView.isHidden
|
||||
self.animationView = ComponentView<Empty>()
|
||||
self.tintMaskAnimationView = UIImageView()
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
if let text {
|
||||
if self.textView == nil {
|
||||
let textView = ImmediateTextView()
|
||||
self.textView = textView
|
||||
self.addSubview(textView)
|
||||
}
|
||||
if self.tintMaskTextView == nil {
|
||||
let tintMaskTextView = ImmediateTextView()
|
||||
self.tintMaskTextView = tintMaskTextView
|
||||
self.tintMask.addSubview(tintMaskTextView)
|
||||
}
|
||||
|
||||
self.textView?.attributedText = NSAttributedString(string: text, font: accessoryButtonFont, textColor: theme.chat.inputPanel.inputControlColor)
|
||||
self.tintMaskTextView?.attributedText = NSAttributedString(string: text, font: accessoryButtonFont, textColor: .black)
|
||||
} else {
|
||||
if let textView = self.textView {
|
||||
self.textView = nil
|
||||
textView.removeFromSuperview()
|
||||
}
|
||||
if let tintMaskTextView = self.tintMaskTextView {
|
||||
self.tintMaskTextView = nil
|
||||
tintMaskTextView.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
|
||||
self.iconImageView.image = image
|
||||
self.iconImageView.tintColor = theme.chat.inputPanel.inputControlColor
|
||||
self.iconImageView.alpha = alpha
|
||||
|
||||
self.tintMaskIconImageView.image = self.iconImageView.image
|
||||
self.tintMaskIconImageView.tintColor = .black
|
||||
self.tintMaskIconImageView.alpha = self.iconImageView.alpha
|
||||
|
||||
self.accessibilityLabel = accessibilityLabel
|
||||
|
||||
self.highligthedChanged = { [weak self] highlighted in
|
||||
if let strongSelf = self {
|
||||
if highlighted {
|
||||
strongSelf.layer.removeAnimation(forKey: "opacity")
|
||||
strongSelf.alpha = 0.4
|
||||
strongSelf.layer.allowsGroupOpacity = true
|
||||
} else {
|
||||
strongSelf.alpha = 1.0
|
||||
strongSelf.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
|
||||
strongSelf.layer.allowsGroupOpacity = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
guard let result = super.hitTest(point, with: event) else {
|
||||
return nil
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func updateThemeAndStrings(theme: PresentationTheme, strings: PresentationStrings) {
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
|
||||
let (image, text, accessibilityLabel, alpha, _) = AccessoryItemIconButton.imageAndInsets(item: item, theme: theme, strings: strings)
|
||||
|
||||
self.width = AccessoryItemIconButton.calculateWidth(item: item, image: image, text: text, strings: strings)
|
||||
|
||||
if let text {
|
||||
self.textView?.attributedText = NSAttributedString(string: text, font: accessoryButtonFont, textColor: theme.chat.inputPanel.inputControlColor)
|
||||
self.tintMaskTextView?.attributedText = NSAttributedString(string: text, font: accessoryButtonFont, textColor: .black)
|
||||
}
|
||||
|
||||
self.iconImageView.image = image
|
||||
self.iconImageView.tintColor = theme.chat.inputPanel.inputControlColor
|
||||
self.iconImageView.alpha = alpha
|
||||
|
||||
self.tintMaskIconImageView.image = self.iconImageView.image
|
||||
self.tintMaskIconImageView.tintColor = .black
|
||||
self.tintMaskIconImageView.alpha = self.iconImageView.alpha
|
||||
|
||||
self.accessibilityLabel = accessibilityLabel
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
private static func imageAndInsets(item: ChatTextInputAccessoryItem, theme: PresentationTheme, strings: PresentationStrings) -> (UIImage?, String?, String, CGFloat, UIEdgeInsets) {
|
||||
switch item {
|
||||
case let .input(isEnabled, inputMode), let .botInput(isEnabled, inputMode):
|
||||
switch inputMode {
|
||||
case .keyboard:
|
||||
return (PresentationResourcesChat.chatInputTextFieldKeyboardImage(theme), nil, strings.VoiceOver_Keyboard, 1.0, UIEdgeInsets())
|
||||
case .stickers, .emoji:
|
||||
return (PresentationResourcesChat.chatInputTextFieldStickersImage(theme), nil, strings.VoiceOver_Stickers, isEnabled ? 1.0 : 0.4, UIEdgeInsets())
|
||||
case .bot:
|
||||
return (PresentationResourcesChat.chatInputTextFieldInputButtonsImage(theme), nil, strings.VoiceOver_BotKeyboard, 1.0, UIEdgeInsets())
|
||||
}
|
||||
case .commands:
|
||||
return (PresentationResourcesChat.chatInputTextFieldCommandsImage(theme), nil, strings.VoiceOver_BotCommands, 1.0, UIEdgeInsets())
|
||||
case let .silentPost(value):
|
||||
if value {
|
||||
return (PresentationResourcesChat.chatInputTextFieldSilentPostOnImage(theme), nil, strings.VoiceOver_SilentPostOn, 1.0, UIEdgeInsets())
|
||||
} else {
|
||||
return (PresentationResourcesChat.chatInputTextFieldSilentPostOffImage(theme), nil, strings.VoiceOver_SilentPostOff, 1.0, UIEdgeInsets())
|
||||
}
|
||||
case .suggestPost:
|
||||
return (PresentationResourcesChat.chatInputTextFieldSuggestPostImage(theme), nil, strings.VoiceOver_SuggestPost, 1.0, UIEdgeInsets())
|
||||
case let .messageAutoremoveTimeout(timeout):
|
||||
if let timeout = timeout {
|
||||
return (nil, shortTimeIntervalString(strings: strings, value: timeout), strings.VoiceOver_SelfDestructTimerOn(timeIntervalString(strings: strings, value: timeout)).string, 1.0, UIEdgeInsets())
|
||||
} else {
|
||||
return (PresentationResourcesChat.chatInputTextFieldTimerImage(theme), nil, strings.VoiceOver_SelfDestructTimerOff, 1.0, UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0))
|
||||
}
|
||||
case .scheduledMessages:
|
||||
return (PresentationResourcesChat.chatInputTextFieldScheduleImage(theme), nil, strings.VoiceOver_ScheduledMessages, 1.0, UIEdgeInsets())
|
||||
case .gift:
|
||||
return (PresentationResourcesChat.chatInputTextFieldGiftImage(theme), nil, strings.VoiceOver_GiftPremium, 1.0, UIEdgeInsets())
|
||||
}
|
||||
}
|
||||
|
||||
private static func calculateWidth(item: ChatTextInputAccessoryItem, image: UIImage?, text: String?, strings: PresentationStrings) -> CGFloat {
|
||||
switch item {
|
||||
case .input, .botInput, .silentPost, .commands, .scheduledMessages, .gift, .suggestPost:
|
||||
return 32.0
|
||||
case let .messageAutoremoveTimeout(timeout):
|
||||
var imageWidth = (image?.size.width ?? 0.0) + CGFloat(8.0)
|
||||
if let _ = timeout, let text = text {
|
||||
imageWidth = ceil((text as NSString).size(withAttributes: [.font: accessoryButtonFont]).width) + 10.0
|
||||
}
|
||||
|
||||
return max(imageWidth, 24.0)
|
||||
}
|
||||
}
|
||||
|
||||
func updateLayout(item: ChatTextInputAccessoryItem, size: CGSize) {
|
||||
let previousItem = self.item
|
||||
self.item = item
|
||||
|
||||
let (updatedImage, text, _, _, _) = AccessoryItemIconButton.imageAndInsets(item: item, theme: self.theme, strings: self.strings)
|
||||
|
||||
if let image = self.iconImageView.image {
|
||||
self.iconImageView.image = updatedImage
|
||||
self.tintMaskIconImageView.image = updatedImage
|
||||
|
||||
let bottomInset: CGFloat = 0.0
|
||||
let imageFrame = CGRect(origin: CGPoint(x: floor((size.width - image.size.width) / 2.0), y: floor((size.height - image.size.height) / 2.0) - bottomInset), size: image.size)
|
||||
self.iconImageView.frame = imageFrame
|
||||
self.tintMaskIconImageView.frame = imageFrame
|
||||
|
||||
if let animationView = self.animationView {
|
||||
let width = AccessoryItemIconButton.calculateWidth(item: item, image: image, text: "", strings: self.strings)
|
||||
|
||||
let animationFrame = CGRect(origin: CGPoint(x: floor((size.width - width) / 2.0), y: floor((size.height - width) / 2.0) - bottomInset), size: CGSize(width: width, height: width))
|
||||
|
||||
let animationName: String
|
||||
var animationMode: LottieAnimationComponent.AnimationItem.Mode = .still(position: .end)
|
||||
|
||||
if case let .silentPost(muted) = item {
|
||||
if case let .silentPost(previousMuted) = previousItem {
|
||||
if muted {
|
||||
animationName = "input_anim_channelMute"
|
||||
} else {
|
||||
animationName = "input_anim_channelUnmute"
|
||||
}
|
||||
if muted != previousMuted {
|
||||
animationMode = .animating(loop: false)
|
||||
}
|
||||
} else {
|
||||
animationName = "input_anim_channelMute"
|
||||
}
|
||||
} else {
|
||||
var previousInputMode: ChatTextInputAccessoryItem.InputMode?
|
||||
var inputMode: ChatTextInputAccessoryItem.InputMode?
|
||||
|
||||
switch previousItem {
|
||||
case let .input(_, itemInputMode), let .botInput(_, itemInputMode):
|
||||
previousInputMode = itemInputMode
|
||||
default:
|
||||
break
|
||||
}
|
||||
switch item {
|
||||
case let .input(_, itemInputMode), let .botInput(_, itemInputMode):
|
||||
inputMode = itemInputMode
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
if let inputMode = inputMode {
|
||||
switch inputMode {
|
||||
case .keyboard:
|
||||
if let previousInputMode = previousInputMode {
|
||||
if case .stickers = previousInputMode {
|
||||
animationName = "input_anim_stickerToKey"
|
||||
animationMode = .animating(loop: false)
|
||||
} else if case .emoji = previousInputMode {
|
||||
animationName = "input_anim_smileToKey"
|
||||
animationMode = .animating(loop: false)
|
||||
} else if case .bot = previousInputMode {
|
||||
animationName = "input_anim_botToKey"
|
||||
animationMode = .animating(loop: false)
|
||||
} else {
|
||||
animationName = "input_anim_stickerToKey"
|
||||
}
|
||||
} else {
|
||||
animationName = "input_anim_stickerToKey"
|
||||
}
|
||||
case .stickers:
|
||||
if let previousInputMode = previousInputMode {
|
||||
if case .keyboard = previousInputMode {
|
||||
animationName = "input_anim_keyToSticker"
|
||||
animationMode = .animating(loop: false)
|
||||
} else if case .emoji = previousInputMode {
|
||||
animationName = "input_anim_smileToSticker"
|
||||
animationMode = .animating(loop: false)
|
||||
} else {
|
||||
animationName = "input_anim_keyToSticker"
|
||||
}
|
||||
} else {
|
||||
animationName = "input_anim_keyToSticker"
|
||||
}
|
||||
case .emoji:
|
||||
if let previousInputMode = previousInputMode {
|
||||
if case .keyboard = previousInputMode {
|
||||
animationName = "input_anim_keyToSmile"
|
||||
animationMode = .animating(loop: false)
|
||||
} else if case .stickers = previousInputMode {
|
||||
animationName = "input_anim_stickerToSmile"
|
||||
animationMode = .animating(loop: false)
|
||||
} else {
|
||||
animationName = "input_anim_keyToSmile"
|
||||
}
|
||||
} else {
|
||||
animationName = "input_anim_keyToSmile"
|
||||
}
|
||||
case .bot:
|
||||
if let previousInputMode = previousInputMode {
|
||||
if case .keyboard = previousInputMode {
|
||||
animationName = "input_anim_keyToBot"
|
||||
animationMode = .animating(loop: false)
|
||||
} else {
|
||||
animationName = "input_anim_keyToBot"
|
||||
}
|
||||
} else {
|
||||
animationName = "input_anim_keyToBot"
|
||||
}
|
||||
}
|
||||
} else {
|
||||
animationName = ""
|
||||
}
|
||||
}
|
||||
|
||||
let animationSize = animationView.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(LottieComponent(
|
||||
content: LottieComponent.AppBundleContent(name: animationName),
|
||||
color: self.theme.chat.inputPanel.inputControlColor
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: animationFrame.size
|
||||
)
|
||||
if let view = animationView.view as? LottieComponent.View {
|
||||
view.isUserInteractionEnabled = false
|
||||
if view.superview == nil {
|
||||
view.output = self.tintMaskAnimationView
|
||||
self.addSubview(view)
|
||||
if let tintMaskAnimationView = self.tintMaskAnimationView {
|
||||
self.tintMask.addSubview(tintMaskAnimationView)
|
||||
}
|
||||
}
|
||||
let animationFrameValue = CGRect(origin: CGPoint(x: animationFrame.minX + floor((animationFrame.width - animationSize.width) / 2.0), y: animationFrame.minY + floor((animationFrame.height - animationSize.height) / 2.0)), size: animationSize)
|
||||
view.frame = animationFrameValue
|
||||
if let tintMaskAnimationView = self.tintMaskAnimationView {
|
||||
tintMaskAnimationView.frame = animationFrameValue
|
||||
}
|
||||
|
||||
if case .animating = animationMode {
|
||||
view.playOnce()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let text {
|
||||
self.textView?.attributedText = NSAttributedString(string: text, font: accessoryButtonFont, textColor: theme.chat.inputPanel.inputControlColor)
|
||||
self.tintMaskTextView?.attributedText = NSAttributedString(string: text, font: accessoryButtonFont, textColor: .black)
|
||||
}
|
||||
|
||||
if let textView = self.textView, let tintMaskTextView = self.tintMaskTextView {
|
||||
let textSize = textView.updateLayout(CGSize(width: 100.0, height: 100.0))
|
||||
let _ = tintMaskTextView.updateLayout(CGSize(width: 100.0, height: 100.0))
|
||||
|
||||
let textFrame = CGRect(origin: CGPoint(x: floor((size.width - textSize.width) * 0.5), y: floor((size.height - textSize.height) * 0.5)), size: textSize)
|
||||
textView.frame = textFrame
|
||||
tintMaskTextView.frame = textFrame
|
||||
}
|
||||
}
|
||||
|
||||
var buttonWidth: CGFloat {
|
||||
return self.width
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user