diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index da512bc6f7..0a83151415 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -14485,7 +14485,6 @@ Sorry for the inconvenience."; "Chat.Todo.Message.Title" = "Checklist"; "Chat.Todo.Message.TitleGroup" = "Group Checklist"; "Chat.Todo.Message.TitlePersonal" = "%@'s Checklist"; -"Chat.Todo.Message.CompletedPersonal" = "%@'s Checklist"; "Chat.Todo.ContextMenu.AddTask" = "Add a Task"; "Chat.Todo.ContextMenu.EditTask" = "Edit Item"; @@ -14655,3 +14654,20 @@ Sorry for the inconvenience."; "Chat.PostSuggestion.Suggest.UserMinAmountStars.Text" = "You cannot offer less than %@ Stars."; "Stars.SellGiftMinAmountToast.Text" = "You cannot sell gift for less than %@"; + +"Premium.Week.SignUp" = "Sign up for %@"; +"Premium.Week.SignUpInfo" = "Get Telegram Premium for 1 week"; + +"Settings.MyTon" = "My TON"; + +"Stars.Gift.Ton.Text" = "Use TON to submit post suggestions to channels on Telegram."; +"Stars.Ton.Title" = "TON"; +"Stars.Ton.Description" = "Use TON to submit post suggestions to channels on Telegram."; + +"Notification.Ton.Subtitle" = "Use TON to submit post suggestions to channels."; +"Notification.Ton.SubtitleYou" = "With TON, %@ will be able to submit post suggestions to channels."; + +"Chat.Todo.Message.Completed_1" = "%@ of {count} completed"; +"Chat.Todo.Message.Completed_any" = "%@ of {count} completed"; +"Chat.Todo.Message.CompletedBy_1" = "%@ of {count} completed by {name}"; +"Chat.Todo.Message.CompletedBy_any" = "%@ of {count} completed by {name}"; diff --git a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift index 3b75d02d07..d35676d1af 100644 --- a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift @@ -3702,8 +3702,8 @@ private final class PremiumIntroScreenComponent: CombinedComponent { let buttonTitle: String var buttonSubtitle: String? if case let .auth(price) = context.component.source { - buttonTitle = environment.strings.Auth_PremiumSignUp_ActionTitle("\(price)").string - buttonSubtitle = environment.strings.Auth_PremiumSignUp_ActionSubtitle + buttonTitle = environment.strings.Premium_Week_SignUp(price).string + buttonSubtitle = environment.strings.Premium_Week_SignUpInfo } else if isUnusedGift { buttonTitle = environment.strings.Premium_Gift_ApplyLink } else if state.isPremium == true && state.canUpgrade { diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/Sources/ChatMessageGiftBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/Sources/ChatMessageGiftBubbleContentNode.swift index 6bdd21ecc7..08856d1830 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/Sources/ChatMessageGiftBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/Sources/ChatMessageGiftBubbleContentNode.swift @@ -433,7 +433,7 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { let cryptoAmount = cryptoAmount ?? 0 title = "$ \(formatTonAmountText(cryptoAmount, dateTimeFormat: item.presentationData.dateTimeFormat, maxDecimalPositions: 3))" - text = incoming ? "Use TON to submit post suggestions to channels." : "With TON, \(peerName) will be able to submit post suggestions to channels." + text = incoming ? item.presentationData.strings.Notification_Ton_Subtitle : item.presentationData.strings.Notification_Ton_SubtitleYou(peerName).string buttonTitle = "" case let .prizeStars(count, _, channelId, _, _): if count <= 1000 { diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageTodoBubbleContentNode/Sources/ChatMessageTodoBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageTodoBubbleContentNode/Sources/ChatMessageTodoBubbleContentNode.swift index fe537f66c2..781a04f358 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageTodoBubbleContentNode/Sources/ChatMessageTodoBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageTodoBubbleContentNode/Sources/ChatMessageTodoBubbleContentNode.swift @@ -632,6 +632,60 @@ private final class ChatMessageTodoItemNode: ASDisplayNode { self.pressed?() } } + + func linkRectsAtPoint(_ point: CGPoint?) -> [CGRect]? { + guard let textNode = self.titleNode else { + return nil + } + var rects: [CGRect]? + if let point = point { + let textNodeFrame = textNode.textNode.frame + if let (index, attributes) = textNode.textNode.attributesAtPoint(CGPoint(x: point.x - textNodeFrame.minX, y: point.y - textNodeFrame.minY)) { + let possibleNames: [String] = [ + TelegramTextAttributes.URL, + TelegramTextAttributes.PeerMention, + TelegramTextAttributes.PeerTextMention, + TelegramTextAttributes.BotCommand, + TelegramTextAttributes.Hashtag, + TelegramTextAttributes.BankCard + ] + for name in possibleNames { + if let _ = attributes[NSAttributedString.Key(rawValue: name)], let textRects = textNode.textNode.attributeRects(name: name, at: index) { + rects = textRects.map { $0.offsetBy(dx: textNodeFrame.minX, dy: textNodeFrame.minY) } + break + } + } + } + } + return rects + } + + func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction { + guard let textNode = self.titleNode else { + return ChatMessageBubbleContentTapAction(content: .none) + } + let textNodeFrame = textNode.textNode.frame + if let (index, attributes) = textNode.textNode.attributesAtPoint(CGPoint(x: point.x - textNodeFrame.minX, y: point.y - textNodeFrame.minY)) { + if let url = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String { + var concealed = true + if let (attributeText, fullText) = textNode.textNode.attributeSubstring(name: TelegramTextAttributes.URL, index: index) { + concealed = !doesUrlMatchText(url: url, text: attributeText, fullText: fullText) + } + return ChatMessageBubbleContentTapAction(content: .url(ChatMessageBubbleContentTapAction.Url(url: url, concealed: concealed))) + } else if let peerMention = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerMention)] as? TelegramPeerMention { + return ChatMessageBubbleContentTapAction(content: .peerMention(peerId: peerMention.peerId, mention: peerMention.mention, openProfile: false)) + } else if let peerName = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerTextMention)] as? String { + return ChatMessageBubbleContentTapAction(content: .textMention(peerName)) + } else if let botCommand = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.BotCommand)] as? String { + return ChatMessageBubbleContentTapAction(content: .botCommand(botCommand)) + } else if let hashtag = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.Hashtag)] as? TelegramHashtag { + return ChatMessageBubbleContentTapAction(content: .hashtag(hashtag.peerName, hashtag.hashtag)) + } else { + return ChatMessageBubbleContentTapAction(content: .none) + } + } + return ChatMessageBubbleContentTapAction(content: .none) + } static func asyncLayout(_ maybeNode: ChatMessageTodoItemNode?) -> (_ context: AccountContext, _ presentationData: ChatPresentationData, _ presentationContext: ChatPresentationContext, _ message: Message, _ todo: TelegramMediaTodo, _ option: TelegramMediaTodo.Item, _ completion: TelegramMediaTodo.Completion?, _ translation: TranslationMessageAttribute.Additional?, _ constrainedWidth: CGFloat) -> (minimumWidth: CGFloat, layout: ((CGFloat) -> (CGSize, (Bool, Bool, Bool) -> ChatMessageTodoItemNode))) { let makeTitleLayout = TextNodeWithEntities.asyncLayout(maybeNode?.titleNode) @@ -663,12 +717,16 @@ private final class ChatMessageTodoItemNode: ASDisplayNode { optionEntities.append(MessageTextEntity(range: 0 ..< (optionText as NSString).length, type: .Strikethrough)) } - let optionTextColor: UIColor = messageTheme.primaryTextColor + var underlineLinks = true + if !messageTheme.primaryTextColor.isEqual(messageTheme.linkTextColor) { + underlineLinks = false + } + let optionAttributedText = stringWithAppliedEntities( optionText, entities: optionEntities, - baseColor: optionTextColor, - linkColor: optionTextColor, + baseColor: messageTheme.primaryTextColor, + linkColor: messageTheme.linkTextColor, baseFont: presentationData.messageFont, linkFont: presentationData.messageFont, boldFont: presentationData.messageFont, @@ -676,6 +734,7 @@ private final class ChatMessageTodoItemNode: ASDisplayNode { boldItalicFont: presentationData.messageFont, fixedFont: presentationData.messageFont, blockQuoteFont: presentationData.messageFont, + underlineLinks: underlineLinks, message: message ) @@ -921,6 +980,8 @@ public class ChatMessageTodoBubbleContentNode: ChatMessageBubbleContentNode { private var optionNodes: [ChatMessageTodoItemNode] = [] private var shimmeringNodes: [ShimmeringLinkNode] = [] + private var linkHighlightingNode: LinkHighlightingNode? + private var todo: TelegramMediaTodo? override public var visibility: ListViewItemNodeVisibility { @@ -1155,15 +1216,15 @@ public class ChatMessageTodoBubbleContentNode: ChatMessageBubbleContentNode { let (typeLayout, typeApply) = makeTypeLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: typeText, font: labelsFont, textColor: messageTheme.secondaryTextColor), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: textConstrainedSize, alignment: .natural, cutout: nil, insets: UIEdgeInsets())) - - //TODO:localize var bottomText: String = "" if let todo { + let format: String if let author = item.message.author, author.id != item.context.account.peerId && !todo.flags.contains(.othersCanComplete) { - bottomText = "\(todo.completions.count) of \(todo.items.count) completed by \(EnginePeer(author).compactDisplayTitle)" + format = item.presentationData.strings.Chat_Todo_Message_CompletedBy(Int32(todo.completions.count)).replacingOccurrences(of: "{name}", with: EnginePeer(author).compactDisplayTitle) } else { - bottomText = "\(todo.completions.count) of \(todo.items.count) completed" + format = item.presentationData.strings.Chat_Todo_Message_Completed(Int32(todo.completions.count)) } + bottomText = format.replacingOccurrences(of: "{count}", with: "\(todo.items.count)") } let (buttonViewResultsTextLayout, buttonViewResultsTextApply) = makeViewResultsTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: bottomText, font: labelsFont, textColor: messageTheme.secondaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: textConstrainedSize, alignment: .natural, cutout: nil, insets: textInsets)) @@ -1440,9 +1501,18 @@ public class ChatMessageTodoBubbleContentNode: ChatMessageBubbleContentNode { } } else { for optionNode in self.optionNodes { - if optionNode.frame.contains(point), case .tap = gesture { - if optionNode.isUserInteractionEnabled { - return ChatMessageBubbleContentTapAction(content: .ignore) + if optionNode.frame.contains(point) { + let optionAction = optionNode.tapActionAtPoint(self.view.convert(point, to: optionNode.view), gesture: gesture, isEstimating: isEstimating) + if case .none = optionAction.content { + if optionNode.isUserInteractionEnabled, case .tap = gesture { + return ChatMessageBubbleContentTapAction(content: .ignore) + } + } else { + var rects: [CGRect]? + if let optionRects = optionNode.linkRectsAtPoint(self.view.convert(point, to: optionNode.view)), let rect = optionRects.first { + rects = [rect.offsetBy(dx: optionNode.frame.minX - 11.0, dy: optionNode.frame.minY - 5.0)] + } + return ChatMessageBubbleContentTapAction(content: optionAction.content, rects: rects) } } } @@ -1452,6 +1522,56 @@ public class ChatMessageTodoBubbleContentNode: ChatMessageBubbleContentNode { return ChatMessageBubbleContentTapAction(content: .none) } } + + public override func updateTouchesAtPoint(_ point: CGPoint?) { + guard let item = self.item else { + return + } + var rects: [CGRect]? + if let point = point { + let textNodeFrame = self.textNode.textNode.frame + if let (index, attributes) = self.textNode.textNode.attributesAtPoint(CGPoint(x: point.x - textNodeFrame.minX, y: point.y - textNodeFrame.minY)) { + let possibleNames: [String] = [ + TelegramTextAttributes.URL, + TelegramTextAttributes.PeerMention, + TelegramTextAttributes.PeerTextMention, + TelegramTextAttributes.BotCommand, + TelegramTextAttributes.Hashtag, + TelegramTextAttributes.BankCard + ] + for name in possibleNames { + if let _ = attributes[NSAttributedString.Key(rawValue: name)], let textRects = self.textNode.textNode.attributeRects(name: name, at: index) { + rects = textRects.map { $0.offsetBy(dx: textNodeFrame.minX, dy: textNodeFrame.minY) } + break + } + } + } + + for optionNode in self.optionNodes { + if optionNode.frame.contains(point), let optionRects = optionNode.linkRectsAtPoint(CGPoint(x: point.x - optionNode.frame.minX, y: point.y - optionNode.frame.minY)) { + rects = optionRects.map { $0.offsetBy(dx: optionNode.frame.minX - 11.0, dy: optionNode.frame.minY - 4.0) } + } + } + } + + if let rects { + let linkHighlightingNode: LinkHighlightingNode + if let current = self.linkHighlightingNode { + linkHighlightingNode = current + } else { + linkHighlightingNode = LinkHighlightingNode(color: item.message.effectivelyIncoming(item.context.account.peerId) ? item.presentationData.theme.theme.chat.message.incoming.linkHighlightColor : item.presentationData.theme.theme.chat.message.outgoing.linkHighlightColor) + self.linkHighlightingNode = linkHighlightingNode + self.insertSubnode(linkHighlightingNode, belowSubnode: self.textNode.textNode) + } + linkHighlightingNode.frame = self.textNode.textNode.frame + linkHighlightingNode.updateRects(rects) + } else if let linkHighlightingNode = self.linkHighlightingNode { + self.linkHighlightingNode = nil + linkHighlightingNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.18, removeOnCompletion: false, completion: { [weak linkHighlightingNode] _ in + linkHighlightingNode?.removeFromSupernode() + }) + } + } override public func reactionTargetView(value: MessageReaction.Reaction) -> UIView? { if !self.statusNode.isHidden { diff --git a/submodules/TelegramUI/Components/Stars/StarsTransactionScreen/Sources/StarsTransactionScreen.swift b/submodules/TelegramUI/Components/Stars/StarsTransactionScreen/Sources/StarsTransactionScreen.swift index e3ac016d85..03ff9a4aee 100644 --- a/submodules/TelegramUI/Components/Stars/StarsTransactionScreen/Sources/StarsTransactionScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsTransactionScreen/Sources/StarsTransactionScreen.swift @@ -419,7 +419,7 @@ private final class StarsTransactionSheetContent: CombinedComponent { count = transaction.count if count.currency == .ton { - descriptionText = strings.Settings_Ton_Description + descriptionText = strings.Stars_Gift_Ton_Text } else { descriptionText = strings.Stars_Gift_Received_Text } diff --git a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift index 8d6673ccf4..ff8a3c7a0f 100644 --- a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift @@ -536,8 +536,8 @@ final class StarsTransactionsScreenComponent: Component { let titleString: String let descriptionString: String if component.starsContext.ton { - titleString = environment.strings.Settings_TransactionsTon_Title - descriptionString = environment.strings.Settings_TransactionsTon_Subtitle + titleString = environment.strings.Stars_Ton_Title + descriptionString = environment.strings.Stars_Ton_Description } else { titleString = environment.strings.Stars_Intro_Title descriptionString = environment.strings.Stars_Intro_Description