mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-08-17 11:00:07 +00:00
Merge commit 'f2b219b3df9f38c223796e47c3d9463661ff4e07'
This commit is contained in:
commit
a19bb79f90
@ -216,50 +216,50 @@ private func updatedContextQueryResultStateForQuery(context: AccountContext, pee
|
||||
|
||||
let chatPeer = peer
|
||||
let contextBot = resolvePeerByName(account: context.account, name: addressName)
|
||||
|> mapToSignal { peerId -> Signal<Peer?, NoError> in
|
||||
if let peerId = peerId {
|
||||
return context.account.postbox.loadedPeerWithId(peerId)
|
||||
|> map { peer -> Peer? in
|
||||
return peer
|
||||
}
|
||||
|> take(1)
|
||||
} else {
|
||||
return .single(nil)
|
||||
|> mapToSignal { peerId -> Signal<Peer?, NoError> in
|
||||
if let peerId = peerId {
|
||||
return context.account.postbox.loadedPeerWithId(peerId)
|
||||
|> map { peer -> Peer? in
|
||||
return peer
|
||||
}
|
||||
|> take(1)
|
||||
} else {
|
||||
return .single(nil)
|
||||
}
|
||||
|> mapToSignal { peer -> Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, NoError> in
|
||||
if let user = peer as? TelegramUser, let botInfo = user.botInfo, let _ = botInfo.inlinePlaceholder {
|
||||
let contextResults = requestChatContextResults(account: context.account, botId: user.id, peerId: chatPeer.id, query: query, offset: "")
|
||||
|> map { results -> (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult? in
|
||||
return { _ in
|
||||
return .contextRequestResult(user, results)
|
||||
}
|
||||
|> mapToSignal { peer -> Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, NoError> in
|
||||
if let user = peer as? TelegramUser, let botInfo = user.botInfo, let _ = botInfo.inlinePlaceholder {
|
||||
let contextResults = requestChatContextResults(account: context.account, botId: user.id, peerId: chatPeer.id, query: query, offset: "")
|
||||
|> map { results -> (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult? in
|
||||
return { _ in
|
||||
return .contextRequestResult(user, results)
|
||||
}
|
||||
}
|
||||
|
||||
let botResult: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, NoError> = .single({ previousResult in
|
||||
var passthroughPreviousResult: ChatContextResultCollection?
|
||||
if let previousResult = previousResult {
|
||||
if case let .contextRequestResult(previousUser, previousResults) = previousResult {
|
||||
if previousUser?.id == user.id {
|
||||
passthroughPreviousResult = previousResults
|
||||
}
|
||||
}
|
||||
|
||||
let botResult: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, NoError> = .single({ previousResult in
|
||||
var passthroughPreviousResult: ChatContextResultCollection?
|
||||
if let previousResult = previousResult {
|
||||
if case let .contextRequestResult(previousUser, previousResults) = previousResult {
|
||||
if previousUser?.id == user.id {
|
||||
passthroughPreviousResult = previousResults
|
||||
}
|
||||
}
|
||||
}
|
||||
return .contextRequestResult(user, passthroughPreviousResult)
|
||||
})
|
||||
|
||||
let maybeDelayedContextResults: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, NoError>
|
||||
if delayRequest {
|
||||
maybeDelayedContextResults = contextResults |> delay(0.4, queue: Queue.concurrentDefaultQueue())
|
||||
} else {
|
||||
maybeDelayedContextResults = contextResults
|
||||
}
|
||||
|
||||
return botResult |> then(maybeDelayedContextResults)
|
||||
return .contextRequestResult(user, passthroughPreviousResult)
|
||||
})
|
||||
|
||||
let maybeDelayedContextResults: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, NoError>
|
||||
if delayRequest {
|
||||
maybeDelayedContextResults = contextResults |> delay(0.4, queue: Queue.concurrentDefaultQueue())
|
||||
} else {
|
||||
return .single({ _ in return nil })
|
||||
maybeDelayedContextResults = contextResults
|
||||
}
|
||||
|
||||
return botResult |> then(maybeDelayedContextResults)
|
||||
} else {
|
||||
return .single({ _ in return nil })
|
||||
}
|
||||
}
|
||||
|
||||
return signal |> then(contextBot)
|
||||
case let .emojiSearch(query, languageCode):
|
||||
|
@ -29,7 +29,7 @@ kernel vec4 alphaFrame(__sample s, __sample m) {
|
||||
}
|
||||
}
|
||||
|
||||
private func createVideoComposition(for playerItem: AVPlayerItem) -> AVVideoComposition? {
|
||||
private func createVideoComposition(for playerItem: AVPlayerItem, ready: @escaping () -> Void) -> AVVideoComposition? {
|
||||
let videoSize = CGSize(width: playerItem.presentationSize.width, height: playerItem.presentationSize.height / 2.0)
|
||||
if #available(iOSApplicationExtension 9.0, *) {
|
||||
let composition = AVMutableVideoComposition(asset: playerItem.asset, applyingCIFiltersWithHandler: { request in
|
||||
@ -39,7 +39,8 @@ private func createVideoComposition(for playerItem: AVPlayerItem) -> AVVideoComp
|
||||
filter.inputImage = request.sourceImage.cropped(to: alphaRect)
|
||||
.transformed(by: CGAffineTransform(translationX: 0, y: -sourceRect.height))
|
||||
filter.maskImage = request.sourceImage.cropped(to: sourceRect)
|
||||
return request.finish(with: filter.outputImage!, context: nil)
|
||||
request.finish(with: filter.outputImage!, context: nil)
|
||||
ready()
|
||||
})
|
||||
composition.renderSize = videoSize
|
||||
return composition
|
||||
@ -60,6 +61,19 @@ private final class StickerAnimationNode: ASDisplayNode {
|
||||
|
||||
var started: () -> Void = {}
|
||||
|
||||
var ready = false
|
||||
var visibility = false {
|
||||
didSet {
|
||||
if self.visibility {
|
||||
if self.ready {
|
||||
self.player?.play()
|
||||
}
|
||||
} else{
|
||||
self.player?.pause()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var player: AVPlayer? {
|
||||
get {
|
||||
if self.isNodeLoaded {
|
||||
@ -69,13 +83,7 @@ private final class StickerAnimationNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
set {
|
||||
if let player = self.playerLayer.player {
|
||||
player.removeObserver(self, forKeyPath: #keyPath(AVPlayer.rate))
|
||||
}
|
||||
self.playerLayer.player = newValue
|
||||
if let newValue = newValue {
|
||||
newValue.addObserver(self, forKeyPath: #keyPath(AVPlayer.rate), options: [], context: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -95,6 +103,7 @@ private final class StickerAnimationNode: ASDisplayNode {
|
||||
self.setLayerBlock({
|
||||
let layer = AVPlayerLayer()
|
||||
layer.isHidden = true
|
||||
layer.videoGravity = .resize
|
||||
if #available(iOSApplicationExtension 9.0, *) {
|
||||
layer.pixelBufferAttributes = [(kCVPixelBufferPixelFormatTypeKey as String): kCVPixelFormatType_32BGRA]
|
||||
}
|
||||
@ -149,31 +158,22 @@ private final class StickerAnimationNode: ASDisplayNode {
|
||||
if let playerItem = object as? AVPlayerItem, playerItem === self.playerItem {
|
||||
if case .readyToPlay = playerItem.status, playerItem.videoComposition == nil {
|
||||
playerItem.seekingWaitsForVideoCompositionRendering = true
|
||||
let composition = createVideoComposition(for: playerItem)
|
||||
let composition = createVideoComposition(for: playerItem, ready: { [weak self] in
|
||||
Queue.mainQueue().async {
|
||||
self?.playerLayer.isHidden = false
|
||||
self?.started()
|
||||
}
|
||||
})
|
||||
playerItem.videoComposition = composition
|
||||
//playerItem.videoComposition = nil
|
||||
//playerItem.videoComposition = composition
|
||||
self.player?.play()
|
||||
}
|
||||
} else if let player = object as? AVPlayer, player === self.player {
|
||||
if self.playerLayer.isHidden && player.rate > 0.0 {
|
||||
//Queue.mainQueue().after(0.2) {
|
||||
self.playerLayer.isHidden = false
|
||||
self.started()
|
||||
//}
|
||||
ready = true
|
||||
if self.visibility {
|
||||
self.player?.play()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
|
||||
}
|
||||
}
|
||||
|
||||
func play() {
|
||||
|
||||
}
|
||||
|
||||
func reset() {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
@ -244,13 +244,25 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
self.view.addGestureRecognizer(replyRecognizer)
|
||||
}
|
||||
|
||||
override var visibility: ListViewItemNodeVisibility {
|
||||
didSet {
|
||||
if self.visibility != oldValue {
|
||||
switch self.visibility {
|
||||
case .visible:
|
||||
self.animationNode.visibility = true
|
||||
case .none:
|
||||
self.animationNode.visibility = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func setupItem(_ item: ChatMessageItem) {
|
||||
super.setupItem(item)
|
||||
|
||||
for media in item.message.media {
|
||||
if let telegramFile = media as? TelegramMediaFile {
|
||||
if self.telegramFile != telegramFile {
|
||||
|
||||
if self.telegramFile?.id != telegramFile.id {
|
||||
self.telegramFile = telegramFile
|
||||
self.imageNode.setSignal(chatMessageSticker(account: item.context.account, file: telegramFile, small: false, thumbnail: true))
|
||||
self.animationNode.setup(account: item.context.account, fileReference: .message(message: MessageReference(item.message), media: telegramFile))
|
||||
|
@ -89,18 +89,6 @@ func textAttributedStringForStateText(_ stateText: NSAttributedString, fontSize:
|
||||
return result
|
||||
}
|
||||
|
||||
private func textMentionRangesEqual(_ lhs: [(NSRange, ChatTextInputTextMentionAttribute)], _ rhs: [(NSRange, ChatTextInputTextMentionAttribute)]) -> Bool {
|
||||
if lhs.count != rhs.count {
|
||||
return false
|
||||
}
|
||||
for i in 0 ..< lhs.count {
|
||||
if lhs[i].0 != rhs[i].0 || lhs[i].1.peerId != rhs[i].1.peerId {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
final class ChatTextInputTextMentionAttribute: NSObject {
|
||||
let peerId: PeerId
|
||||
|
||||
@ -119,6 +107,18 @@ final class ChatTextInputTextMentionAttribute: NSObject {
|
||||
}
|
||||
}
|
||||
|
||||
private func textMentionRangesEqual(_ lhs: [(NSRange, ChatTextInputTextMentionAttribute)], _ rhs: [(NSRange, ChatTextInputTextMentionAttribute)]) -> Bool {
|
||||
if lhs.count != rhs.count {
|
||||
return false
|
||||
}
|
||||
for i in 0 ..< lhs.count {
|
||||
if lhs[i].0 != rhs[i].0 || lhs[i].1.peerId != rhs[i].1.peerId {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
final class ChatTextInputTextUrlAttribute: NSObject {
|
||||
let url: String
|
||||
|
||||
@ -137,16 +137,19 @@ final class ChatTextInputTextUrlAttribute: NSObject {
|
||||
}
|
||||
}
|
||||
|
||||
func refreshChatTextInputAttributes(_ textNode: ASEditableTextNode, theme: PresentationTheme, baseFontSize: CGFloat) {
|
||||
guard let initialAttributedText = textNode.attributedText, initialAttributedText.length != 0 else {
|
||||
return
|
||||
private func textUrlRangesEqual(_ lhs: [(NSRange, ChatTextInputTextUrlAttribute)], _ rhs: [(NSRange, ChatTextInputTextUrlAttribute)]) -> Bool {
|
||||
if lhs.count != rhs.count {
|
||||
return false
|
||||
}
|
||||
|
||||
let text: NSString = initialAttributedText.string as NSString
|
||||
let fullRange = NSRange(location: 0, length: initialAttributedText.length)
|
||||
|
||||
let attributedText = NSMutableAttributedString(attributedString: stateAttributedStringForText(initialAttributedText))
|
||||
|
||||
for i in 0 ..< lhs.count {
|
||||
if lhs[i].0 != rhs[i].0 || lhs[i].1.url != rhs[i].1.url {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private func refreshTextMentions(text: NSString, initialAttributedText: NSAttributedString, attributedText: NSMutableAttributedString, fullRange: NSRange) {
|
||||
var textMentionRanges: [(NSRange, ChatTextInputTextMentionAttribute)] = []
|
||||
initialAttributedText.enumerateAttribute(ChatTextInputAttributes.textMention, in: fullRange, options: [], using: { value, range, _ in
|
||||
if let value = value as? ChatTextInputTextMentionAttribute {
|
||||
@ -260,8 +263,142 @@ func refreshChatTextInputAttributes(_ textNode: ASEditableTextNode, theme: Prese
|
||||
attributedText.addAttribute(ChatTextInputAttributes.textMention, value: ChatTextInputTextMentionAttribute(peerId: attribute.peerId), range: range)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func refreshTextUrls(text: NSString, initialAttributedText: NSAttributedString, attributedText: NSMutableAttributedString, fullRange: NSRange) {
|
||||
var textUrlRanges: [(NSRange, ChatTextInputTextUrlAttribute)] = []
|
||||
initialAttributedText.enumerateAttribute(ChatTextInputAttributes.textUrl, in: fullRange, options: [], using: { value, range, _ in
|
||||
if let value = value as? ChatTextInputTextUrlAttribute {
|
||||
textUrlRanges.append((range, value))
|
||||
}
|
||||
})
|
||||
textUrlRanges.sort(by: { $0.0.location < $1.0.location })
|
||||
let initialTextUrlRanges = textUrlRanges
|
||||
|
||||
let resultAttributedText = textAttributedStringForStateText(attributedText, fontSize: baseFontSize, textColor: theme.chat.inputPanel.primaryTextColor, accentTextColor: theme.chat.inputPanel.panelControlAccentColor)
|
||||
for i in 0 ..< textUrlRanges.count {
|
||||
let range = textUrlRanges[i].0
|
||||
|
||||
var validLower = range.lowerBound
|
||||
inner1: for i in range.lowerBound ..< range.upperBound {
|
||||
if let c = UnicodeScalar(text.character(at: i)) {
|
||||
if alphanumericCharacters.contains(c) || c == " " as UnicodeScalar {
|
||||
validLower = i
|
||||
break inner1
|
||||
}
|
||||
} else {
|
||||
break inner1
|
||||
}
|
||||
}
|
||||
var validUpper = range.upperBound
|
||||
inner2: for i in (validLower ..< range.upperBound).reversed() {
|
||||
if let c = UnicodeScalar(text.character(at: i)) {
|
||||
if alphanumericCharacters.contains(c) || c == " " as UnicodeScalar {
|
||||
validUpper = i + 1
|
||||
break inner2
|
||||
}
|
||||
} else {
|
||||
break inner2
|
||||
}
|
||||
}
|
||||
|
||||
let minLower = (i == 0) ? fullRange.lowerBound : textUrlRanges[i - 1].0.upperBound
|
||||
inner3: for i in (minLower ..< validLower).reversed() {
|
||||
if let c = UnicodeScalar(text.character(at: i)) {
|
||||
if alphanumericCharacters.contains(c) {
|
||||
validLower = i
|
||||
} else {
|
||||
break inner3
|
||||
}
|
||||
} else {
|
||||
break inner3
|
||||
}
|
||||
}
|
||||
|
||||
let maxUpper = (i == textUrlRanges.count - 1) ? fullRange.upperBound : textUrlRanges[i + 1].0.lowerBound
|
||||
inner3: for i in validUpper ..< maxUpper {
|
||||
if let c = UnicodeScalar(text.character(at: i)) {
|
||||
if alphanumericCharacters.contains(c) {
|
||||
validUpper = i + 1
|
||||
} else {
|
||||
break inner3
|
||||
}
|
||||
} else {
|
||||
break inner3
|
||||
}
|
||||
}
|
||||
|
||||
textUrlRanges[i] = (NSRange(location: validLower, length: validUpper - validLower), textUrlRanges[i].1)
|
||||
}
|
||||
|
||||
textUrlRanges = textUrlRanges.filter({ $0.0.length > 0 })
|
||||
|
||||
while textUrlRanges.count > 1 {
|
||||
var hadReductions = false
|
||||
outer: for i in 0 ..< textUrlRanges.count - 1 {
|
||||
if textUrlRanges[i].1 === textUrlRanges[i + 1].1 {
|
||||
var combine = true
|
||||
inner: for j in textUrlRanges[i].0.upperBound ..< textUrlRanges[i + 1].0.lowerBound {
|
||||
if let c = UnicodeScalar(text.character(at: j)) {
|
||||
if alphanumericCharacters.contains(c) || c == " " as UnicodeScalar {
|
||||
} else {
|
||||
combine = false
|
||||
break inner
|
||||
}
|
||||
} else {
|
||||
combine = false
|
||||
break inner
|
||||
}
|
||||
}
|
||||
if combine {
|
||||
hadReductions = true
|
||||
textUrlRanges[i] = (NSRange(location: textUrlRanges[i].0.lowerBound, length: textUrlRanges[i + 1].0.upperBound - textUrlRanges[i].0.lowerBound), textUrlRanges[i].1)
|
||||
textUrlRanges.remove(at: i + 1)
|
||||
break outer
|
||||
}
|
||||
}
|
||||
}
|
||||
if !hadReductions {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if textUrlRanges.count > 1 {
|
||||
outer: for i in (1 ..< textUrlRanges.count).reversed() {
|
||||
for j in 0 ..< i {
|
||||
if textUrlRanges[j].1 === textUrlRanges[i].1 {
|
||||
textUrlRanges.remove(at: i)
|
||||
continue outer
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !textUrlRangesEqual(textUrlRanges, initialTextUrlRanges) {
|
||||
attributedText.removeAttribute(ChatTextInputAttributes.textUrl, range: fullRange)
|
||||
for (range, attribute) in textUrlRanges {
|
||||
attributedText.addAttribute(ChatTextInputAttributes.textUrl, value: ChatTextInputTextUrlAttribute(url: attribute.url), range: range)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func refreshChatTextInputAttributes(_ textNode: ASEditableTextNode, theme: PresentationTheme, baseFontSize: CGFloat) {
|
||||
guard var initialAttributedText = textNode.attributedText, initialAttributedText.length != 0 else {
|
||||
return
|
||||
}
|
||||
|
||||
var text: NSString = initialAttributedText.string as NSString
|
||||
var fullRange = NSRange(location: 0, length: initialAttributedText.length)
|
||||
var attributedText = NSMutableAttributedString(attributedString: stateAttributedStringForText(initialAttributedText))
|
||||
refreshTextMentions(text: text, initialAttributedText: initialAttributedText, attributedText: attributedText, fullRange: fullRange)
|
||||
|
||||
var resultAttributedText = textAttributedStringForStateText(attributedText, fontSize: baseFontSize, textColor: theme.chat.inputPanel.primaryTextColor, accentTextColor: theme.chat.inputPanel.panelControlAccentColor)
|
||||
|
||||
text = resultAttributedText.string as NSString
|
||||
fullRange = NSRange(location: 0, length: initialAttributedText.length)
|
||||
attributedText = NSMutableAttributedString(attributedString: stateAttributedStringForText(resultAttributedText))
|
||||
refreshTextUrls(text: text, initialAttributedText: resultAttributedText, attributedText: attributedText, fullRange: fullRange)
|
||||
|
||||
resultAttributedText = textAttributedStringForStateText(attributedText, fontSize: baseFontSize, textColor: theme.chat.inputPanel.primaryTextColor, accentTextColor: theme.chat.inputPanel.panelControlAccentColor)
|
||||
|
||||
if !resultAttributedText.isEqual(to: initialAttributedText) {
|
||||
textNode.textView.textStorage.removeAttribute(NSAttributedStringKey.font, range: fullRange)
|
||||
@ -299,13 +436,13 @@ func refreshChatTextInputAttributes(_ textNode: ASEditableTextNode, theme: Prese
|
||||
if !fontAttributes.isEmpty {
|
||||
var font: UIFont?
|
||||
if fontAttributes == [.bold, .italic, .monospace] {
|
||||
|
||||
font = Font.semiboldItalicMonospace(baseFontSize)
|
||||
} else if fontAttributes == [.bold, .italic] {
|
||||
font = Font.semiboldItalic(baseFontSize)
|
||||
} else if fontAttributes == [.bold, .monospace] {
|
||||
|
||||
font = Font.semiboldMonospace(baseFontSize)
|
||||
} else if fontAttributes == [.italic, .monospace] {
|
||||
|
||||
font = Font.italicMonospace(baseFontSize)
|
||||
} else if fontAttributes == [.bold] {
|
||||
font = Font.semibold(baseFontSize)
|
||||
} else if fontAttributes == [.italic] {
|
||||
|
@ -6,13 +6,14 @@ import Postbox
|
||||
import TelegramCore
|
||||
|
||||
private final class ChatTextLinkEditInputFieldNode: ASDisplayNode, ASEditableTextNodeDelegate {
|
||||
private let theme: PresentationTheme
|
||||
private var theme: PresentationTheme
|
||||
private let backgroundNode: ASImageNode
|
||||
private let textInputNode: EditableTextNode
|
||||
private let placeholderNode: ASTextNode
|
||||
|
||||
var updateHeight: (() -> Void)?
|
||||
var complete: (() -> Void)?
|
||||
var textChanged: ((String) -> Void)?
|
||||
|
||||
private let backgroundInsets = UIEdgeInsets(top: 8.0, left: 16.0, bottom: 15.0, right: 16.0)
|
||||
private let inputInsets = UIEdgeInsets(top: 5.0, left: 12.0, bottom: 5.0, right: 12.0)
|
||||
@ -41,7 +42,7 @@ private final class ChatTextLinkEditInputFieldNode: ASDisplayNode, ASEditableTex
|
||||
self.backgroundNode.isLayerBacked = true
|
||||
self.backgroundNode.displaysAsynchronously = false
|
||||
self.backgroundNode.displayWithoutProcessing = true
|
||||
self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 33.0, color: theme.actionSheet.itemBackgroundColor.withAlphaComponent(1.0), strokeColor: theme.actionSheet.inputBackgroundColor, strokeWidth: 1.0)
|
||||
self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 33.0, color: theme.actionSheet.inputHollowBackgroundColor, strokeColor: theme.actionSheet.inputBorderColor, strokeWidth: 1.0)
|
||||
|
||||
self.textInputNode = EditableTextNode()
|
||||
self.textInputNode.typingAttributes = [NSAttributedStringKey.font.rawValue: Font.regular(17.0), NSAttributedStringKey.foregroundColor.rawValue: theme.actionSheet.inputTextColor]
|
||||
@ -52,6 +53,7 @@ private final class ChatTextLinkEditInputFieldNode: ASDisplayNode, ASEditableTex
|
||||
self.textInputNode.keyboardType = .URL
|
||||
self.textInputNode.autocapitalizationType = .none
|
||||
self.textInputNode.returnKeyType = .done
|
||||
self.textInputNode.autocorrectionType = .no
|
||||
|
||||
self.placeholderNode = ASTextNode()
|
||||
self.placeholderNode.isUserInteractionEnabled = false
|
||||
@ -67,6 +69,14 @@ private final class ChatTextLinkEditInputFieldNode: ASDisplayNode, ASEditableTex
|
||||
self.addSubnode(self.placeholderNode)
|
||||
}
|
||||
|
||||
func updateTheme(_ theme: PresentationTheme) {
|
||||
self.theme = theme
|
||||
|
||||
self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 33.0, color: theme.actionSheet.inputHollowBackgroundColor, strokeColor: theme.actionSheet.inputBorderColor, strokeWidth: 1.0)
|
||||
self.textInputNode.keyboardAppearance = theme.chatList.searchBarKeyboardColor.keyboardAppearance
|
||||
self.placeholderNode.attributedText = NSAttributedString(string: self.placeholderNode.attributedText?.string ?? "", font: Font.regular(17.0), textColor: self.theme.actionSheet.inputPlaceholderColor)
|
||||
}
|
||||
|
||||
func updateLayout(width: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat {
|
||||
let backgroundInsets = self.backgroundInsets
|
||||
let inputInsets = self.inputInsets
|
||||
@ -79,7 +89,7 @@ private final class ChatTextLinkEditInputFieldNode: ASDisplayNode, ASEditableTex
|
||||
transition.updateFrame(node: self.backgroundNode, frame: backgroundFrame)
|
||||
|
||||
let placeholderSize = self.placeholderNode.measure(backgroundFrame.size)
|
||||
transition.updateFrame(node: self.placeholderNode, frame: CGRect(origin: CGPoint(x: backgroundFrame.minX + floor((backgroundFrame.size.width - placeholderSize.width) / 2.0), y: backgroundFrame.minY + floor((backgroundFrame.size.height - placeholderSize.height) / 2.0)), size: placeholderSize))
|
||||
transition.updateFrame(node: self.placeholderNode, frame: CGRect(origin: CGPoint(x: backgroundFrame.minX + inputInsets.left, y: backgroundFrame.minY + floor((backgroundFrame.size.height - placeholderSize.height) / 2.0)), size: placeholderSize))
|
||||
|
||||
transition.updateFrame(node: self.textInputNode, frame: CGRect(origin: CGPoint(x: backgroundFrame.minX + inputInsets.left, y: backgroundFrame.minY), size: CGSize(width: backgroundFrame.size.width - inputInsets.left - inputInsets.right - accessoryButtonsWidth, height: backgroundFrame.size.height)))
|
||||
|
||||
@ -96,13 +106,7 @@ private final class ChatTextLinkEditInputFieldNode: ASDisplayNode, ASEditableTex
|
||||
|
||||
@objc func editableTextNodeDidUpdateText(_ editableTextNode: ASEditableTextNode) {
|
||||
self.updateTextNodeText(animated: true)
|
||||
}
|
||||
|
||||
func editableTextNodeDidBeginEditing(_ editableTextNode: ASEditableTextNode) {
|
||||
self.placeholderNode.isHidden = true
|
||||
}
|
||||
|
||||
func editableTextNodeDidFinishEditing(_ editableTextNode: ASEditableTextNode) {
|
||||
self.textChanged?(editableTextNode.textView.text)
|
||||
self.placeholderNode.isHidden = !(editableTextNode.textView.text ?? "").isEmpty
|
||||
}
|
||||
|
||||
@ -143,17 +147,19 @@ private final class ChatTextLinkEditInputFieldNode: ASDisplayNode, ASEditableTex
|
||||
|
||||
|
||||
private final class ChatTextLinkEditContentActionNode: HighlightableButtonNode {
|
||||
private let backgroundNode: ASDisplayNode
|
||||
|
||||
private var theme: AlertControllerTheme
|
||||
let action: TextAlertAction
|
||||
|
||||
private let backgroundNode: ASDisplayNode
|
||||
|
||||
init(theme: AlertControllerTheme, action: TextAlertAction) {
|
||||
self.theme = theme
|
||||
self.action = action
|
||||
|
||||
self.backgroundNode = ASDisplayNode()
|
||||
self.backgroundNode.isLayerBacked = true
|
||||
self.backgroundNode.alpha = 0.0
|
||||
|
||||
self.action = action
|
||||
|
||||
super.init()
|
||||
|
||||
self.titleNode.maximumNumberOfLines = 2
|
||||
@ -176,16 +182,27 @@ private final class ChatTextLinkEditContentActionNode: HighlightableButtonNode {
|
||||
self.updateTheme(theme)
|
||||
}
|
||||
|
||||
var actionEnabled: Bool = true {
|
||||
didSet {
|
||||
self.isUserInteractionEnabled = self.actionEnabled
|
||||
self.updateTitle()
|
||||
}
|
||||
}
|
||||
|
||||
func updateTheme(_ theme: AlertControllerTheme) {
|
||||
self.theme = theme
|
||||
self.backgroundNode.backgroundColor = theme.highlightedItemColor
|
||||
|
||||
self.updateTitle()
|
||||
}
|
||||
|
||||
private func updateTitle() {
|
||||
var font = Font.regular(17.0)
|
||||
var color = theme.accentColor
|
||||
var color: UIColor
|
||||
switch self.action.type {
|
||||
case .defaultAction, .genericAction:
|
||||
break
|
||||
color = self.actionEnabled ? self.theme.accentColor : self.theme.disabledColor
|
||||
case .destructiveAction:
|
||||
color = theme.destructiveColor
|
||||
color = self.actionEnabled ? self.theme.destructiveColor : self.theme.disabledColor
|
||||
}
|
||||
switch self.action.type {
|
||||
case .defaultAction:
|
||||
@ -219,7 +236,7 @@ private final class ChatTextLinkEditAlertContentNode: AlertContentNode {
|
||||
|
||||
private let titleNode: ASTextNode
|
||||
private let textNode: ASTextNode
|
||||
private let inputFieldNode: ChatTextLinkEditInputFieldNode
|
||||
let inputFieldNode: ChatTextLinkEditInputFieldNode
|
||||
|
||||
private let actionNodesSeparator: ASDisplayNode
|
||||
private let actionNodes: [ChatTextLinkEditContentActionNode]
|
||||
@ -250,9 +267,8 @@ private final class ChatTextLinkEditAlertContentNode: AlertContentNode {
|
||||
self.textNode = ASTextNode()
|
||||
self.textNode.maximumNumberOfLines = 2
|
||||
|
||||
self.inputFieldNode = ChatTextLinkEditInputFieldNode(theme: ptheme, placeholder: "")
|
||||
self.inputFieldNode = ChatTextLinkEditInputFieldNode(theme: ptheme, placeholder: strings.TextFormat_AddLinkPlaceholder)
|
||||
self.inputFieldNode.text = link ?? ""
|
||||
self.inputFieldNode.placeholder = strings.TextFormat_AddLinkPlaceholder
|
||||
|
||||
self.actionNodesSeparator = ASDisplayNode()
|
||||
self.actionNodesSeparator.isLayerBacked = true
|
||||
@ -283,6 +299,7 @@ private final class ChatTextLinkEditAlertContentNode: AlertContentNode {
|
||||
for actionNode in self.actionNodes {
|
||||
self.addSubnode(actionNode)
|
||||
}
|
||||
self.actionNodes.last?.actionEnabled = !(link ?? "").isEmpty
|
||||
|
||||
for separatorNode in self.actionVerticalSeparators {
|
||||
self.addSubnode(separatorNode)
|
||||
@ -296,6 +313,12 @@ private final class ChatTextLinkEditAlertContentNode: AlertContentNode {
|
||||
}
|
||||
}
|
||||
|
||||
self.inputFieldNode.textChanged = { [weak self] text in
|
||||
if let strongSelf = self, let lastNode = strongSelf.actionNodes.last {
|
||||
lastNode.actionEnabled = !text.isEmpty
|
||||
}
|
||||
}
|
||||
|
||||
self.updateTheme(theme)
|
||||
}
|
||||
|
||||
@ -306,7 +329,7 @@ private final class ChatTextLinkEditAlertContentNode: AlertContentNode {
|
||||
var link: String {
|
||||
return self.inputFieldNode.text
|
||||
}
|
||||
|
||||
|
||||
override func updateTheme(_ theme: AlertControllerTheme) {
|
||||
self.titleNode.attributedText = NSAttributedString(string: self.strings.TextFormat_AddLinkTitle, font: Font.bold(17.0), textColor: theme.primaryColor, paragraphAlignment: .center)
|
||||
self.textNode.attributedText = NSAttributedString(string: self.strings.TextFormat_AddLinkText(self.text).0, font: Font.regular(13.0), textColor: theme.primaryColor, paragraphAlignment: .center)
|
||||
@ -445,8 +468,6 @@ private final class ChatTextLinkEditAlertContentNode: AlertContentNode {
|
||||
|
||||
func chatTextLinkEditController(sharedContext: SharedAccountContext, account: Account, text: String, link: String?, apply: @escaping (String?) -> Void) -> AlertController {
|
||||
let presentationData = sharedContext.currentPresentationData.with { $0 }
|
||||
let theme = presentationData.theme
|
||||
let strings = presentationData.strings
|
||||
|
||||
var dismissImpl: ((Bool) -> Void)?
|
||||
var applyImpl: (() -> Void)?
|
||||
@ -458,7 +479,7 @@ func chatTextLinkEditController(sharedContext: SharedAccountContext, account: Ac
|
||||
applyImpl?()
|
||||
})]
|
||||
|
||||
let contentNode = ChatTextLinkEditAlertContentNode(theme: AlertControllerTheme(presentationTheme: theme), ptheme: theme, strings: strings, actions: actions, text: text, link: link)
|
||||
let contentNode = ChatTextLinkEditAlertContentNode(theme: AlertControllerTheme(presentationTheme: presentationData.theme), ptheme: presentationData.theme, strings: presentationData.strings, actions: actions, text: text, link: link)
|
||||
contentNode.complete = {
|
||||
applyImpl?()
|
||||
}
|
||||
@ -470,14 +491,7 @@ func chatTextLinkEditController(sharedContext: SharedAccountContext, account: Ac
|
||||
if !updatedLink.hasPrefix("http") && !updatedLink.hasPrefix("https") {
|
||||
updatedLink = "http://\(updatedLink)"
|
||||
}
|
||||
if updatedLink.isEmpty {
|
||||
if let _ = link {
|
||||
dismissImpl?(true)
|
||||
apply(updatedLink)
|
||||
} else {
|
||||
contentNode.animateError()
|
||||
}
|
||||
} else if isValidUrl(updatedLink) {
|
||||
if !updatedLink.isEmpty && isValidUrl(updatedLink) {
|
||||
dismissImpl?(true)
|
||||
apply(updatedLink)
|
||||
} else {
|
||||
@ -485,7 +499,14 @@ func chatTextLinkEditController(sharedContext: SharedAccountContext, account: Ac
|
||||
}
|
||||
}
|
||||
|
||||
let controller = AlertController(theme: AlertControllerTheme(presentationTheme: theme), contentNode: contentNode)
|
||||
let controller = AlertController(theme: AlertControllerTheme(presentationTheme: presentationData.theme), contentNode: contentNode)
|
||||
let presentationDataDisposable = sharedContext.presentationData.start(next: { [weak controller, weak contentNode] presentationData in
|
||||
controller?.theme = AlertControllerTheme(presentationTheme: presentationData.theme)
|
||||
contentNode?.inputFieldNode.updateTheme(presentationData.theme)
|
||||
})
|
||||
controller.dismissed = {
|
||||
presentationDataDisposable.dispose()
|
||||
}
|
||||
dismissImpl = { [weak controller] animated in
|
||||
if animated {
|
||||
controller?.dismissAnimated()
|
||||
|
@ -43,7 +43,7 @@ extension ActionSheetController {
|
||||
public extension AlertControllerTheme {
|
||||
convenience init(presentationTheme: PresentationTheme) {
|
||||
let actionSheet = presentationTheme.actionSheet
|
||||
self.init(backgroundColor: actionSheet.opaqueItemBackgroundColor, separatorColor: actionSheet.opaqueItemSeparatorColor, highlightedItemColor: actionSheet.opaqueItemHighlightedBackgroundColor, primaryColor: actionSheet.primaryTextColor, secondaryColor: actionSheet.secondaryTextColor, accentColor: actionSheet.controlAccentColor, destructiveColor: actionSheet.destructiveActionTextColor)
|
||||
self.init(backgroundColor: actionSheet.opaqueItemBackgroundColor, separatorColor: actionSheet.opaqueItemSeparatorColor, highlightedItemColor: actionSheet.opaqueItemHighlightedBackgroundColor, primaryColor: actionSheet.primaryTextColor, secondaryColor: actionSheet.secondaryTextColor, accentColor: actionSheet.controlAccentColor, destructiveColor: actionSheet.destructiveActionTextColor, disabledColor: actionSheet.disabledActionTextColor)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -310,6 +310,8 @@ private let actionSheet = PresentationThemeActionSheet(
|
||||
secondaryTextColor: UIColor(white: 1.0, alpha: 0.5), //!!!
|
||||
controlAccentColor: accentColor,
|
||||
inputBackgroundColor: UIColor(rgb: 0x182330), //!!!
|
||||
inputHollowBackgroundColor: UIColor(rgb: 0x182330),
|
||||
inputBorderColor: UIColor(rgb: 0x182330),
|
||||
inputPlaceholderColor: UIColor(rgb: 0x8B9197), //!!!
|
||||
inputTextColor: .white,
|
||||
inputClearButtonColor: UIColor(rgb: 0x8B9197),
|
||||
|
@ -308,6 +308,8 @@ private let actionSheet = PresentationThemeActionSheet(
|
||||
secondaryTextColor: UIColor(rgb: 0x5e5e5e), //!!!
|
||||
controlAccentColor: accentColor,
|
||||
inputBackgroundColor: UIColor(rgb: 0x545454), //!!!
|
||||
inputHollowBackgroundColor: UIColor(rgb: 0x545454),
|
||||
inputBorderColor: UIColor(rgb: 0x545454),
|
||||
inputPlaceholderColor: UIColor(rgb: 0xaaaaaa), //!!!
|
||||
inputTextColor: .white,
|
||||
inputClearButtonColor: UIColor(rgb: 0xaaaaaa),
|
||||
|
@ -414,11 +414,13 @@ private func makeDefaultPresentationTheme(accentColor: UIColor, serviceBackgroun
|
||||
standardActionTextColor: accentColor,
|
||||
opaqueItemSeparatorColor: UIColor(white: 0.9, alpha: 1.0),
|
||||
destructiveActionTextColor: destructiveColor,
|
||||
disabledActionTextColor: UIColor(rgb: 0x4d4d4d),
|
||||
disabledActionTextColor: UIColor(rgb: 0xb3b3b3),
|
||||
primaryTextColor: .black,
|
||||
secondaryTextColor: UIColor(rgb: 0x5e5e5e),
|
||||
controlAccentColor: accentColor,
|
||||
inputBackgroundColor: UIColor(rgb: 0xe9e9e9),
|
||||
inputHollowBackgroundColor: .white,
|
||||
inputBorderColor: UIColor(rgb: 0xe4e4e6),
|
||||
inputPlaceholderColor: UIColor(rgb: 0x818086),
|
||||
inputTextColor: .black,
|
||||
inputClearButtonColor: UIColor(rgb: 0x7b7b81),
|
||||
|
@ -199,12 +199,14 @@ public final class PresentationThemeActionSheet {
|
||||
public let secondaryTextColor: UIColor
|
||||
public let controlAccentColor: UIColor
|
||||
public let inputBackgroundColor: UIColor
|
||||
public let inputHollowBackgroundColor: UIColor
|
||||
public let inputBorderColor: UIColor
|
||||
public let inputPlaceholderColor: UIColor
|
||||
public let inputTextColor: UIColor
|
||||
public let inputClearButtonColor: UIColor
|
||||
public let checkContentColor: UIColor
|
||||
|
||||
init(dimColor: UIColor, backgroundType: PresentationThemeActionSheetBackgroundType, opaqueItemBackgroundColor: UIColor, itemBackgroundColor: UIColor, opaqueItemHighlightedBackgroundColor: UIColor, itemHighlightedBackgroundColor: UIColor, standardActionTextColor: UIColor, opaqueItemSeparatorColor: UIColor, destructiveActionTextColor: UIColor, disabledActionTextColor: UIColor, primaryTextColor: UIColor, secondaryTextColor: UIColor, controlAccentColor: UIColor, inputBackgroundColor: UIColor, inputPlaceholderColor: UIColor, inputTextColor: UIColor, inputClearButtonColor: UIColor, checkContentColor: UIColor) {
|
||||
init(dimColor: UIColor, backgroundType: PresentationThemeActionSheetBackgroundType, opaqueItemBackgroundColor: UIColor, itemBackgroundColor: UIColor, opaqueItemHighlightedBackgroundColor: UIColor, itemHighlightedBackgroundColor: UIColor, standardActionTextColor: UIColor, opaqueItemSeparatorColor: UIColor, destructiveActionTextColor: UIColor, disabledActionTextColor: UIColor, primaryTextColor: UIColor, secondaryTextColor: UIColor, controlAccentColor: UIColor, inputBackgroundColor: UIColor, inputHollowBackgroundColor: UIColor, inputBorderColor: UIColor, inputPlaceholderColor: UIColor, inputTextColor: UIColor, inputClearButtonColor: UIColor, checkContentColor: UIColor) {
|
||||
self.dimColor = dimColor
|
||||
self.backgroundType = backgroundType
|
||||
self.opaqueItemBackgroundColor = opaqueItemBackgroundColor
|
||||
@ -219,6 +221,8 @@ public final class PresentationThemeActionSheet {
|
||||
self.secondaryTextColor = secondaryTextColor
|
||||
self.controlAccentColor = controlAccentColor
|
||||
self.inputBackgroundColor = inputBackgroundColor
|
||||
self.inputHollowBackgroundColor = inputHollowBackgroundColor
|
||||
self.inputBorderColor = inputBorderColor
|
||||
self.inputPlaceholderColor = inputPlaceholderColor
|
||||
self.inputTextColor = inputTextColor
|
||||
self.inputClearButtonColor = inputClearButtonColor
|
||||
|
@ -4,9 +4,8 @@ import TelegramCore
|
||||
|
||||
public func textAlertController(context: AccountContext, title: String?, text: String, actions: [TextAlertAction], actionLayout: TextAlertContentActionLayout = .horizontal) -> AlertController {
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
let theme = presentationData.theme
|
||||
|
||||
let controller = standardTextAlertController(theme: AlertControllerTheme(presentationTheme: theme), title: title, text: text, actions: actions)
|
||||
let controller = standardTextAlertController(theme: AlertControllerTheme(presentationTheme: presentationData.theme), title: title, text: text, actions: actions)
|
||||
let presentationDataDisposable = context.sharedContext.presentationData.start(next: { [weak controller] presentationData in
|
||||
controller?.theme = AlertControllerTheme(presentationTheme: presentationData.theme)
|
||||
})
|
||||
|
Loading…
x
Reference in New Issue
Block a user