From f2b219b3df9f38c223796e47c3d9463661ff4e07 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Tue, 28 May 2019 20:07:49 +0200 Subject: [PATCH] Fixed text link alert UI --- .../ChatInterfaceStateContextQueries.swift | 72 +++---- .../ChatMessageAnimatedStickerItemNode.swift | 70 ++++--- TelegramUI/ChatTextInputAttributes.swift | 187 +++++++++++++++--- TelegramUI/ChatTextLinkEditController.swift | 89 +++++---- TelegramUI/ComponentsThemes.swift | 2 +- .../DefaultDarkAccentPresentationTheme.swift | 2 + TelegramUI/DefaultDarkPresentationTheme.swift | 2 + TelegramUI/DefaultPresentationTheme.swift | 4 +- TelegramUI/PresentationTheme.swift | 6 +- TelegramUI/ThemedTextAlertController.swift | 3 +- 10 files changed, 308 insertions(+), 129 deletions(-) diff --git a/TelegramUI/ChatInterfaceStateContextQueries.swift b/TelegramUI/ChatInterfaceStateContextQueries.swift index 17330dde8b..888f2a8b16 100644 --- a/TelegramUI/ChatInterfaceStateContextQueries.swift +++ b/TelegramUI/ChatInterfaceStateContextQueries.swift @@ -216,50 +216,50 @@ private func updatedContextQueryResultStateForQuery(context: AccountContext, pee let chatPeer = peer let contextBot = resolvePeerByName(account: context.account, name: addressName) - |> mapToSignal { peerId -> Signal 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 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): diff --git a/TelegramUI/ChatMessageAnimatedStickerItemNode.swift b/TelegramUI/ChatMessageAnimatedStickerItemNode.swift index 2410aa0feb..b0cd12d254 100644 --- a/TelegramUI/ChatMessageAnimatedStickerItemNode.swift +++ b/TelegramUI/ChatMessageAnimatedStickerItemNode.swift @@ -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)) diff --git a/TelegramUI/ChatTextInputAttributes.swift b/TelegramUI/ChatTextInputAttributes.swift index 770fae8c63..e83d23c3b4 100644 --- a/TelegramUI/ChatTextInputAttributes.swift +++ b/TelegramUI/ChatTextInputAttributes.swift @@ -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] { diff --git a/TelegramUI/ChatTextLinkEditController.swift b/TelegramUI/ChatTextLinkEditController.swift index b5af5267dd..1f997774e7 100644 --- a/TelegramUI/ChatTextLinkEditController.swift +++ b/TelegramUI/ChatTextLinkEditController.swift @@ -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() diff --git a/TelegramUI/ComponentsThemes.swift b/TelegramUI/ComponentsThemes.swift index e235ef5662..fc536e9547 100644 --- a/TelegramUI/ComponentsThemes.swift +++ b/TelegramUI/ComponentsThemes.swift @@ -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) } } diff --git a/TelegramUI/DefaultDarkAccentPresentationTheme.swift b/TelegramUI/DefaultDarkAccentPresentationTheme.swift index 5250d9bd2d..3ee2c92bf1 100644 --- a/TelegramUI/DefaultDarkAccentPresentationTheme.swift +++ b/TelegramUI/DefaultDarkAccentPresentationTheme.swift @@ -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), diff --git a/TelegramUI/DefaultDarkPresentationTheme.swift b/TelegramUI/DefaultDarkPresentationTheme.swift index 881d4d83ac..872bce4f22 100644 --- a/TelegramUI/DefaultDarkPresentationTheme.swift +++ b/TelegramUI/DefaultDarkPresentationTheme.swift @@ -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), diff --git a/TelegramUI/DefaultPresentationTheme.swift b/TelegramUI/DefaultPresentationTheme.swift index 63bac098ab..fe92587ed9 100644 --- a/TelegramUI/DefaultPresentationTheme.swift +++ b/TelegramUI/DefaultPresentationTheme.swift @@ -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), diff --git a/TelegramUI/PresentationTheme.swift b/TelegramUI/PresentationTheme.swift index d83ebbbd6f..d87c4b48f6 100644 --- a/TelegramUI/PresentationTheme.swift +++ b/TelegramUI/PresentationTheme.swift @@ -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 diff --git a/TelegramUI/ThemedTextAlertController.swift b/TelegramUI/ThemedTextAlertController.swift index c9dc57e5a1..1ae4440a52 100644 --- a/TelegramUI/ThemedTextAlertController.swift +++ b/TelegramUI/ThemedTextAlertController.swift @@ -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) })