Various Improvements

This commit is contained in:
Ilya Laktyushin 2021-12-18 02:35:50 +04:00
parent 0cc01c5280
commit 835ce9adae
39 changed files with 1886 additions and 165 deletions

Binary file not shown.

View File

@ -7145,3 +7145,7 @@ Sorry for the inconvenience.";
"ChatList.Archive" = "Archive"; "ChatList.Archive" = "Archive";
"TextFormat.Spoiler" = "Spoiler"; "TextFormat.Spoiler" = "Spoiler";
"Conversation.ContextMenuTranslate" = "Translate";
"ClearCache.ClearDescription" = "All media will stay in the Telegram cloud and can be re-downloaded if you need it again.";

View File

@ -61,6 +61,7 @@ swift_library(
"//submodules/TelegramStringFormatting:TelegramStringFormatting", "//submodules/TelegramStringFormatting:TelegramStringFormatting",
"//submodules/TelegramCallsUI:TelegramCallsUI", "//submodules/TelegramCallsUI:TelegramCallsUI",
"//submodules/StickerResources:StickerResources", "//submodules/StickerResources:StickerResources",
"//submodules/TextFormat:TextFormat",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -17,6 +17,8 @@ import PhotoResources
import ChatListSearchItemNode import ChatListSearchItemNode
import ContextUI import ContextUI
import ChatInterfaceState import ChatInterfaceState
import TextFormat
import InvisibleInkDustNode
public enum ChatListItemContent { public enum ChatListItemContent {
public final class DraftState: Equatable { public final class DraftState: Equatable {
@ -427,6 +429,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
let measureNode: TextNode let measureNode: TextNode
private var currentItemHeight: CGFloat? private var currentItemHeight: CGFloat?
let textNode: TextNode let textNode: TextNode
var dustNode: InvisibleInkDustNode?
let inputActivitiesNode: ChatListInputActivitiesNode let inputActivitiesNode: ChatListInputActivitiesNode
let dateNode: TextNode let dateNode: TextNode
let separatorNode: ASDisplayNode let separatorNode: ASDisplayNode
@ -1049,12 +1052,26 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
attributedText = NSAttributedString(string: foldLineBreaks(draftText.replacingOccurrences(of: "\n\n", with: " ")), font: textFont, textColor: theme.messageTextColor) attributedText = NSAttributedString(string: foldLineBreaks(draftText.replacingOccurrences(of: "\n\n", with: " ")), font: textFont, textColor: theme.messageTextColor)
} else if let message = messages.last { } else if let message = messages.last {
var composedString: NSMutableAttributedString var composedString: NSMutableAttributedString
let entities = (message._asMessage().textEntitiesAttribute?.entities ?? []).filter { entity in
if case .Spoiler = entity.type {
return true
} else {
return false
}
}
let messageString: NSAttributedString
if !message.text.isEmpty {
messageString = stringWithAppliedEntities(message.text, entities: entities, baseColor: theme.messageTextColor, linkColor: theme.messageTextColor, baseFont: textFont, linkFont: textFont, boldFont: textFont, italicFont: textFont, boldItalicFont: textFont, fixedFont: textFont, blockQuoteFont: textFont, underlineLinks: false)
} else {
messageString = NSAttributedString(string: messageText, font: textFont, textColor: theme.messageTextColor)
}
if let inlineAuthorPrefix = inlineAuthorPrefix { if let inlineAuthorPrefix = inlineAuthorPrefix {
composedString = NSMutableAttributedString() composedString = NSMutableAttributedString()
composedString.append(NSAttributedString(string: "\(inlineAuthorPrefix): ", font: textFont, textColor: theme.titleColor)) composedString.append(NSAttributedString(string: "\(inlineAuthorPrefix): ", font: textFont, textColor: theme.titleColor))
composedString.append(NSAttributedString(string: messageText, font: textFont, textColor: theme.messageTextColor)) composedString.append(messageString)
} else { } else {
composedString = NSMutableAttributedString(string: messageText, font: textFont, textColor: theme.messageTextColor) composedString = NSMutableAttributedString(attributedString: messageString)
} }
if let searchQuery = item.interaction.searchTextHighightState { if let searchQuery = item.interaction.searchTextHighightState {
@ -1395,7 +1412,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
textCutout = TextNodeCutout(topLeft: CGSize(width: textLeftCutout, height: 10.0), topRight: nil, bottomRight: nil) textCutout = TextNodeCutout(topLeft: CGSize(width: textLeftCutout, height: 10.0), topRight: nil, bottomRight: nil)
} }
let (textLayout, textApply) = textLayout(TextNodeLayoutArguments(attributedString: textAttributedString, backgroundColor: nil, maximumNumberOfLines: authorAttributedString == nil ? 2 : 1, truncationType: .end, constrainedSize: CGSize(width: rawContentWidth - badgeSize, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: textCutout, insets: UIEdgeInsets(top: 2.0, left: 1.0, bottom: 2.0, right: 1.0))) let (textLayout, textApply) = textLayout(TextNodeLayoutArguments(attributedString: textAttributedString, backgroundColor: nil, maximumNumberOfLines: authorAttributedString == nil ? 2 : 1, truncationType: .end, constrainedSize: CGSize(width: rawContentWidth - badgeSize, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: textCutout, insets: UIEdgeInsets(top: 2.0, left: 1.0, bottom: 2.0, right: 1.0)))
let titleRectWidth = rawContentWidth - dateLayout.size.width - 10.0 - statusWidth - titleIconsWidth let titleRectWidth = rawContentWidth - dateLayout.size.width - 10.0 - statusWidth - titleIconsWidth
let (titleLayout, titleApply) = titleLayout(TextNodeLayoutArguments(attributedString: titleAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: titleRectWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) let (titleLayout, titleApply) = titleLayout(TextNodeLayoutArguments(attributedString: titleAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: titleRectWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
@ -1730,6 +1747,24 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
let textNodeFrame = CGRect(origin: CGPoint(x: contentRect.origin.x, y: contentRect.minY + titleLayout.size.height - 1.0 + UIScreenPixel + (authorLayout.size.height.isZero ? 0.0 : (authorLayout.size.height - 3.0))), size: textLayout.size) let textNodeFrame = CGRect(origin: CGPoint(x: contentRect.origin.x, y: contentRect.minY + titleLayout.size.height - 1.0 + UIScreenPixel + (authorLayout.size.height.isZero ? 0.0 : (authorLayout.size.height - 3.0))), size: textLayout.size)
strongSelf.textNode.frame = textNodeFrame strongSelf.textNode.frame = textNodeFrame
if !textLayout.spoilers.isEmpty {
let dustNode: InvisibleInkDustNode
if let current = strongSelf.dustNode {
dustNode = current
} else {
dustNode = InvisibleInkDustNode(textNode: nil)
dustNode.isUserInteractionEnabled = false
strongSelf.dustNode = dustNode
strongSelf.contextContainer.insertSubnode(dustNode, aboveSubnode: strongSelf.textNode)
}
dustNode.update(size: textNodeFrame.size, color: theme.messageTextColor, rects: textLayout.spoilers.map { $0.offsetBy(dx: 3.0, dy: 3.0).insetBy(dx: 0.0, dy: 1.0) })
dustNode.frame = textNodeFrame.insetBy(dx: -3.0, dy: -3.0).offsetBy(dx: 0.0, dy: 3.0)
} else if let dustNode = strongSelf.dustNode {
strongSelf.dustNode = nil
dustNode.removeFromSupernode()
}
var animateInputActivitiesFrame = false var animateInputActivitiesFrame = false
let inputActivities = inputActivities?.filter({ let inputActivities = inputActivities?.filter({
switch $0.1 { switch $0.1 {

View File

@ -81,6 +81,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
case enableDebugDataDisplay(Bool) case enableDebugDataDisplay(Bool)
case acceleratedStickers(Bool) case acceleratedStickers(Bool)
case experimentalBackground(Bool) case experimentalBackground(Bool)
case snow(Bool)
case playerEmbedding(Bool) case playerEmbedding(Bool)
case playlistPlayback(Bool) case playlistPlayback(Bool)
case voiceConference case voiceConference
@ -102,7 +103,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
return DebugControllerSection.logging.rawValue return DebugControllerSection.logging.rawValue
case .enableRaiseToSpeak, .keepChatNavigationStack, .skipReadHistory, .crashOnSlowQueries: case .enableRaiseToSpeak, .keepChatNavigationStack, .skipReadHistory, .crashOnSlowQueries:
return DebugControllerSection.experiments.rawValue return DebugControllerSection.experiments.rawValue
case .clearTips, .crash, .resetData, .resetDatabase, .resetDatabaseAndCache, .resetHoles, .reindexUnread, .resetBiometricsData, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .playerEmbedding, .playlistPlayback, .voiceConference, .experimentalCompatibility, .enableDebugDataDisplay, .acceleratedStickers, .experimentalBackground: case .clearTips, .crash, .resetData, .resetDatabase, .resetDatabaseAndCache, .resetHoles, .reindexUnread, .resetBiometricsData, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .playerEmbedding, .playlistPlayback, .voiceConference, .experimentalCompatibility, .enableDebugDataDisplay, .acceleratedStickers, .experimentalBackground, .snow:
return DebugControllerSection.experiments.rawValue return DebugControllerSection.experiments.rawValue
case .preferredVideoCodec: case .preferredVideoCodec:
return DebugControllerSection.videoExperiments.rawValue return DebugControllerSection.videoExperiments.rawValue
@ -173,14 +174,16 @@ private enum DebugControllerEntry: ItemListNodeEntry {
return 29 return 29
case .experimentalBackground: case .experimentalBackground:
return 30 return 30
case .playerEmbedding: case .snow:
return 31 return 31
case .playlistPlayback: case .playerEmbedding:
return 32 return 32
case .voiceConference: case .playlistPlayback:
return 33 return 33
case .voiceConference:
return 34
case let .preferredVideoCodec(index, _, _, _): case let .preferredVideoCodec(index, _, _, _):
return 34 + index return 35 + index
case .disableVideoAspectScaling: case .disableVideoAspectScaling:
return 100 return 100
case .enableVoipTcp: case .enableVoipTcp:
@ -814,6 +817,16 @@ private enum DebugControllerEntry: ItemListNodeEntry {
}) })
}).start() }).start()
}) })
case let .snow(value):
return ItemListSwitchItem(presentationData: presentationData, title: "Snow", value: value, sectionId: self.section, style: .blocks, updated: { value in
let _ = arguments.sharedContext.accountManager.transaction ({ transaction in
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in
var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings
settings.snow = value
return PreferencesEntry(settings)
})
}).start()
})
case let .playerEmbedding(value): case let .playerEmbedding(value):
return ItemListSwitchItem(presentationData: presentationData, title: "Player Embedding", value: value, sectionId: self.section, style: .blocks, updated: { value in return ItemListSwitchItem(presentationData: presentationData, title: "Player Embedding", value: value, sectionId: self.section, style: .blocks, updated: { value in
let _ = arguments.sharedContext.accountManager.transaction ({ transaction in let _ = arguments.sharedContext.accountManager.transaction ({ transaction in
@ -927,6 +940,7 @@ private func debugControllerEntries(sharedContext: SharedAccountContext, present
entries.append(.enableDebugDataDisplay(experimentalSettings.enableDebugDataDisplay)) entries.append(.enableDebugDataDisplay(experimentalSettings.enableDebugDataDisplay))
entries.append(.acceleratedStickers(experimentalSettings.acceleratedStickers)) entries.append(.acceleratedStickers(experimentalSettings.acceleratedStickers))
entries.append(.experimentalBackground(experimentalSettings.experimentalBackground)) entries.append(.experimentalBackground(experimentalSettings.experimentalBackground))
entries.append(.snow(experimentalSettings.snow))
entries.append(.playerEmbedding(experimentalSettings.playerEmbedding)) entries.append(.playerEmbedding(experimentalSettings.playerEmbedding))
entries.append(.playlistPlayback(experimentalSettings.playlistPlayback)) entries.append(.playlistPlayback(experimentalSettings.playlistPlayback))
} }

View File

@ -1005,6 +1005,10 @@ public class TextNode: ASDisplayNode {
} }
let lineRange = CFRange(location: lastLineCharacterIndex, length: stringLength - lastLineCharacterIndex) let lineRange = CFRange(location: lastLineCharacterIndex, length: stringLength - lastLineCharacterIndex)
var brokenLineRange = CFRange(location: lastLineCharacterIndex, length: lineCharacterCount)
if brokenLineRange.location + brokenLineRange.length > attributedString.length {
brokenLineRange.length = attributedString.length - brokenLineRange.location
}
if lineRange.length == 0 { if lineRange.length == 0 {
break break
} }
@ -1033,7 +1037,7 @@ public class TextNode: ASDisplayNode {
} }
var headIndent: CGFloat = 0.0 var headIndent: CGFloat = 0.0
attributedString.enumerateAttributes(in: NSMakeRange(lineRange.location, lineRange.length), options: []) { attributes, range, _ in attributedString.enumerateAttributes(in: NSMakeRange(brokenLineRange.location, brokenLineRange.length), options: []) { attributes, range, _ in
if let _ = attributes[NSAttributedString.Key.init(rawValue: "TelegramSpoiler")] { if let _ = attributes[NSAttributedString.Key.init(rawValue: "TelegramSpoiler")] {
var ascent: CGFloat = 0.0 var ascent: CGFloat = 0.0
var descent: CGFloat = 0.0 var descent: CGFloat = 0.0

View File

@ -39,6 +39,7 @@ swift_library(
"//submodules/Speak:Speak", "//submodules/Speak:Speak",
"//submodules/UndoUI:UndoUI", "//submodules/UndoUI:UndoUI",
"//submodules/InvisibleInkDustNode:InvisibleInkDustNode", "//submodules/InvisibleInkDustNode:InvisibleInkDustNode",
"//submodules/Translate:Translate",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -16,6 +16,7 @@ import PresentationDataUtils
import ImageContentAnalysis import ImageContentAnalysis
import TextSelectionNode import TextSelectionNode
import Speak import Speak
import Translate
import ShareController import ShareController
import UndoUI import UndoUI
@ -352,6 +353,8 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
} }
case .speak: case .speak:
speakText(string) speakText(string)
case .translate:
translateText(context: strongSelf.context, text: string)
} }
}) })
recognizedContentNode.barcodeAction = { [weak self] payload, rect in recognizedContentNode.barcodeAction = { [weak self] payload, rect in

View File

@ -203,6 +203,7 @@ public enum RecognizedTextSelectionAction {
case share case share
case lookup case lookup
case speak case speak
case translate
} }
public final class RecognizedTextSelectionNode: ASDisplayNode { public final class RecognizedTextSelectionNode: ASDisplayNode {
@ -509,6 +510,12 @@ public final class RecognizedTextSelectionNode: ASDisplayNode {
self?.performAction(selectedText, .lookup) self?.performAction(selectedText, .lookup)
let _ = self?.dismissSelection() let _ = self?.dismissSelection()
})) }))
// if #available(iOS 15.0, *) {
// actions.append(ContextMenuAction(content: .text(title: self.strings.Conversation_ContextMenuTranslate, accessibilityLabel: self.strings.Conversation_ContextMenuTranslate), action: { [weak self] in
// self?.performAction(selectedText, .translate)
// let _ = self?.dismissSelection()
// }))
// }
if isSpeakSelectionEnabled() { if isSpeakSelectionEnabled() {
actions.append(ContextMenuAction(content: .text(title: self.strings.Conversation_ContextMenuSpeak, accessibilityLabel: self.strings.Conversation_ContextMenuSpeak), action: { [weak self] in actions.append(ContextMenuAction(content: .text(title: self.strings.Conversation_ContextMenuSpeak, accessibilityLabel: self.strings.Conversation_ContextMenuSpeak), action: { [weak self] in
self?.performAction(selectedText, .speak) self?.performAction(selectedText, .speak)

View File

@ -62,7 +62,7 @@ public class InvisibleInkDustNode: ASDisplayNode {
public var isRevealed = false public var isRevealed = false
public init(textNode: TextNode) { public init(textNode: TextNode?) {
self.textNode = textNode self.textNode = textNode
self.emitterNode = ASDisplayNode() self.emitterNode = ASDisplayNode()
@ -144,7 +144,7 @@ public class InvisibleInkDustNode: ASDisplayNode {
} }
@objc private func tap(_ gestureRecognizer: UITapGestureRecognizer) { @objc private func tap(_ gestureRecognizer: UITapGestureRecognizer) {
guard let (size, _, _) = self.currentParams, !self.isRevealed else { guard let (size, _, _) = self.currentParams, let textNode = self.textNode, !self.isRevealed else {
return return
} }
@ -155,15 +155,15 @@ public class InvisibleInkDustNode: ASDisplayNode {
self.emitterLayer?.setValue(position, forKeyPath: "emitterBehaviors.fingerAttractor.position") self.emitterLayer?.setValue(position, forKeyPath: "emitterBehaviors.fingerAttractor.position")
Queue.mainQueue().after(0.1 * UIView.animationDurationFactor()) { Queue.mainQueue().after(0.1 * UIView.animationDurationFactor()) {
self.textNode?.view.mask = self.textMaskNode.view textNode.view.mask = self.textMaskNode.view
self.textNode?.alpha = 1.0 textNode.alpha = 1.0
let radius = max(size.width, size.height) let radius = max(size.width, size.height)
self.textSpotNode.frame = CGRect(x: position.x - radius / 2.0, y: position.y - radius / 2.0, width: radius, height: radius) self.textSpotNode.frame = CGRect(x: position.x - radius / 2.0, y: position.y - radius / 2.0, width: radius, height: radius)
self.textSpotNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15) self.textSpotNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
self.textSpotNode.layer.animateScale(from: 0.1, to: 3.5, duration: 0.71, removeOnCompletion: false, completion: { [weak self] _ in self.textSpotNode.layer.animateScale(from: 0.1, to: 3.5, duration: 0.71, removeOnCompletion: false, completion: { _ in
self?.textNode?.view.mask = nil textNode.view.mask = nil
}) })
self.emitterNode.view.mask = self.emitterMaskNode.view self.emitterNode.view.mask = self.emitterMaskNode.view
@ -187,7 +187,7 @@ public class InvisibleInkDustNode: ASDisplayNode {
} }
let textLength = CGFloat((self.textNode?.cachedLayout?.attributedString?.string ?? "").count) let textLength = CGFloat((textNode.cachedLayout?.attributedString?.string ?? "").count)
let timeToRead = min(45.0, ceil(max(4.0, textLength * 0.04))) let timeToRead = min(45.0, ceil(max(4.0, textLength * 0.04)))
Queue.mainQueue().after(timeToRead * UIView.animationDurationFactor()) { Queue.mainQueue().after(timeToRead * UIView.animationDurationFactor()) {
self.isRevealed = false self.isRevealed = false
@ -195,9 +195,7 @@ public class InvisibleInkDustNode: ASDisplayNode {
let transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .linear) let transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .linear)
transition.updateAlpha(node: self, alpha: 1.0) transition.updateAlpha(node: self, alpha: 1.0)
if let textNode = self.textNode { transition.updateAlpha(node: textNode, alpha: 0.0)
transition.updateAlpha(node: textNode, alpha: 0.0)
}
} }
} }

View File

@ -267,8 +267,7 @@ public final class QrCodeScreen: ViewController {
self.qrImageNode.clipsToBounds = true self.qrImageNode.clipsToBounds = true
self.qrImageNode.cornerRadius = 16.0 self.qrImageNode.cornerRadius = 16.0
self.qrIconNode = AnimatedStickerNode() self.qrIconNode = AnimatedStickerNode()
self.qrIconNode.setup(source: AnimatedStickerNodeLocalFileSource(name: "PlaneLogo"), width: 240, height: 240, mode: .direct(cachePathPrefix: nil)) self.qrIconNode.setup(source: AnimatedStickerNodeLocalFileSource(name: "PlaneLogo"), width: 240, height: 240, mode: .direct(cachePathPrefix: nil))
self.qrIconNode.visibility = true self.qrIconNode.visibility = true
@ -434,7 +433,6 @@ public final class QrCodeScreen: ViewController {
let imageSide: CGFloat = 240.0 let imageSide: CGFloat = 240.0
let imageSize = CGSize(width: imageSide, height: imageSide) let imageSize = CGSize(width: imageSide, height: imageSide)
let imageApply = makeImageLayout(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets(), emptyColor: nil)) let imageApply = makeImageLayout(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets(), emptyColor: nil))
let _ = imageApply() let _ = imageApply()
let width = horizontalContainerFillingSizeForLayout(layout: layout, sideInset: 0.0) let width = horizontalContainerFillingSizeForLayout(layout: layout, sideInset: 0.0)

View File

@ -521,6 +521,8 @@ public func storageUsageController(context: AccountContext, cacheUsagePromise: P
var totalSize: Int64 = 0 var totalSize: Int64 = 0
items.append(ActionSheetTextItem(title: presentationData.strings.ClearCache_ClearDescription))
for categoryId in validCategories { for categoryId in validCategories {
if let (_, size) = sizeIndex[categoryId] { if let (_, size) = sizeIndex[categoryId] {
let categorySize: Int64 = size let categorySize: Int64 = size

View File

@ -150,7 +150,7 @@ class RecentSessionsHeaderItemNode: ListViewItemNode {
strongSelf.buttonNode.title = item.context.sharedContext.currentPresentationData.with { $0 }.strings.AuthSessions_LinkDesktopDevice strongSelf.buttonNode.title = item.context.sharedContext.currentPresentationData.with { $0 }.strings.AuthSessions_LinkDesktopDevice
if let _ = updatedTheme { if let _ = updatedTheme {
strongSelf.buttonNode.icon = generateTintedImage(image: UIImage(bundleImageName: "Settings/QrButtonIcon"), color: .white) strongSelf.buttonNode.icon = UIImage(bundleImageName: "Settings/QrButtonIcon")
strongSelf.buttonNode.updateTheme(SolidRoundedButtonTheme(theme: item.theme)) strongSelf.buttonNode.updateTheme(SolidRoundedButtonTheme(theme: item.theme))
} }

View File

@ -69,7 +69,7 @@ public final class SolidRoundedButtonNode: ASDisplayNode {
public var icon: UIImage? { public var icon: UIImage? {
didSet { didSet {
self.iconNode.image = generateTintedImage(image: self.iconNode.image, color: self.theme.foregroundColor) self.iconNode.image = generateTintedImage(image: self.icon, color: self.theme.foregroundColor)
} }
} }

View File

@ -322,6 +322,15 @@ public extension Message {
} }
return nil return nil
} }
var textEntitiesAttribute: TextEntitiesMessageAttribute? {
for attribute in self.attributes {
if let attribute = attribute as? TextEntitiesMessageAttribute {
return attribute
}
}
return nil
}
} }
public func _internal_parseMediaAttachment(data: Data) -> Media? { public func _internal_parseMediaAttachment(data: Data) -> Media? {

View File

@ -229,12 +229,13 @@ public func stringForMediaKind(_ kind: MessageContentKind, strings: Presentation
} }
} }
public func descriptionStringForMessage(contentSettings: ContentSettings, message: EngineMessage, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, dateTimeFormat: PresentationDateTimeFormat, accountPeerId: EnginePeer.Id) -> (String, Bool) { public func descriptionStringForMessage(contentSettings: ContentSettings, message: EngineMessage, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, dateTimeFormat: PresentationDateTimeFormat, accountPeerId: EnginePeer.Id) -> (String, Bool, Bool) {
let contentKind = messageContentKind(contentSettings: contentSettings, message: message, strings: strings, nameDisplayOrder: nameDisplayOrder, dateTimeFormat: dateTimeFormat, accountPeerId: accountPeerId) let contentKind = messageContentKind(contentSettings: contentSettings, message: message, strings: strings, nameDisplayOrder: nameDisplayOrder, dateTimeFormat: dateTimeFormat, accountPeerId: accountPeerId)
if !message.text.isEmpty && ![.expiredImage, .expiredVideo].contains(contentKind.key) { if !message.text.isEmpty && ![.expiredImage, .expiredVideo].contains(contentKind.key) {
return (foldLineBreaks(message.text), false) return (foldLineBreaks(message.text), false, true)
} }
return stringForMediaKind(contentKind, strings: strings) let result = stringForMediaKind(contentKind, strings: strings)
return (result.0, result.1, false)
} }
public func foldLineBreaks(_ text: String) -> String { public func foldLineBreaks(_ text: String) -> String {

View File

@ -251,6 +251,7 @@ swift_library(
"//submodules/InvisibleInkDustNode:InvisibleInkDustNode", "//submodules/InvisibleInkDustNode:InvisibleInkDustNode",
"//submodules/QrCodeUI:QrCodeUI", "//submodules/QrCodeUI:QrCodeUI",
"//submodules/Components/ReactionListContextMenuContent:ReactionListContextMenuContent", "//submodules/Components/ReactionListContextMenuContent:ReactionListContextMenuContent",
"//submodules/Translate:Translate",
] + select({ ] + select({
"@build_bazel_rules_apple//apple:ios_armv7": [], "@build_bazel_rules_apple//apple:ios_armv7": [],
"@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets, "@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets,

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "translate_24.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,127 @@
%PDF-1.7
1 0 obj
<< >>
endobj
2 0 obj
<< /Length 3 0 R >>
stream
/DeviceRGB CS
/DeviceRGB cs
q
1.000000 0.000000 -0.000000 1.000000 8.335022 7.270081 cm
0.000000 0.000000 0.000000 scn
5.135226 14.200185 m
4.875527 14.459883 4.454473 14.459883 4.194774 14.200185 c
3.935075 13.940486 3.935075 13.519431 4.194774 13.259732 c
6.194774 11.259732 l
6.454473 11.000034 6.875527 11.000034 7.135226 11.259732 c
7.394925 11.519431 7.394925 11.940486 7.135226 12.200185 c
5.135226 14.200185 l
h
11.665000 9.064959 m
10.317697 9.064959 l
10.212673 6.276149 9.432839 4.057345 7.885226 2.509732 c
7.853345 2.477852 7.821179 2.446297 7.788730 2.415067 c
9.029876 1.748239 10.640882 1.394958 12.665000 1.394958 c
13.032269 1.394958 13.330000 1.097227 13.330000 0.729958 c
13.330000 0.362688 13.032269 0.064959 12.665000 0.064959 c
10.248617 0.064959 8.228645 0.535731 6.665002 1.532337 c
5.101360 0.535731 3.081383 0.064959 0.665000 0.064959 c
0.297731 0.064959 0.000000 0.362688 0.000000 0.729958 c
0.000000 1.097227 0.297731 1.394958 0.665000 1.394958 c
2.689117 1.394958 4.300128 1.748238 5.541274 2.415066 c
5.508824 2.446296 5.476655 2.477852 5.444774 2.509732 c
3.897161 4.057345 3.117327 6.276149 3.012303 9.064959 c
1.665000 9.064959 l
1.297731 9.064959 1.000000 9.362689 1.000000 9.729959 c
1.000000 10.097228 1.297731 10.394958 1.665000 10.394958 c
3.665000 10.394958 l
9.665000 10.394958 l
11.665000 10.394958 l
12.032269 10.394958 12.330000 10.097228 12.330000 9.729959 c
12.330000 9.362689 12.032269 9.064959 11.665000 9.064959 c
h
4.343254 9.064959 m
8.986746 9.064959 l
8.882931 6.515748 8.171854 4.677263 6.944774 3.450184 c
6.854816 3.360226 6.761571 3.273041 6.665002 3.188664 c
6.568432 3.273041 6.475184 3.360226 6.385226 3.450184 c
5.158146 4.677263 4.447069 6.515748 4.343254 9.064959 c
h
f*
n
Q
q
1.000000 0.000000 -0.000000 1.000000 2.334839 3.245422 cm
0.000000 0.000000 0.000000 scn
5.780663 12.006347 m
5.678422 12.256270 5.435204 12.419556 5.165175 12.419556 c
4.895147 12.419556 4.651928 12.256270 4.549686 12.006347 c
0.049686 1.006347 l
-0.089374 0.666422 0.073459 0.278128 0.413384 0.139067 c
0.753309 0.000007 1.141604 0.162840 1.280664 0.502765 c
2.747988 4.089556 l
7.582363 4.089556 l
9.049686 0.502765 l
9.188747 0.162840 9.577042 0.000007 9.916966 0.139067 c
10.256891 0.278128 10.419724 0.666422 10.280664 1.006347 c
5.780663 12.006347 l
h
7.038272 5.419556 m
5.165175 9.998237 l
3.292078 5.419556 l
7.038272 5.419556 l
h
f*
n
Q
endstream
endobj
3 0 obj
2388
endobj
4 0 obj
<< /Annots []
/Type /Page
/MediaBox [ 0.000000 0.000000 24.000000 24.000000 ]
/Resources 1 0 R
/Contents 2 0 R
/Parent 5 0 R
>>
endobj
5 0 obj
<< /Kids [ 4 0 R ]
/Count 1
/Type /Pages
>>
endobj
6 0 obj
<< /Pages 5 0 R
/Type /Catalog
>>
endobj
xref
0 7
0000000000 65535 f
0000000010 00000 n
0000000034 00000 n
0000002478 00000 n
0000002501 00000 n
0000002674 00000 n
0000002748 00000 n
trailer
<< /ID [ (some) (id) ]
/Root 6 0 R
/Size 7
>>
startxref
2807
%%EOF

View File

@ -60,6 +60,7 @@ import InviteLinksUI
import Markdown import Markdown
import TelegramPermissionsUI import TelegramPermissionsUI
import Speak import Speak
import Translate
import UniversalMediaPlayer import UniversalMediaPlayer
import WallpaperBackgroundNode import WallpaperBackgroundNode
import ChatListUI import ChatListUI
@ -2759,6 +2760,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
} }
case .speak: case .speak:
speakText(text.string) speakText(text.string)
case .translate:
translateText(context: context, text: text.string)
} }
}, displayImportedMessageTooltip: { [weak self] _ in }, displayImportedMessageTooltip: { [weak self] _ in
guard let strongSelf = self else { guard let strongSelf = self else {

View File

@ -760,6 +760,15 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
f(.default) f(.default)
}))) })))
if #available(iOS 15.0, *) {
actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_ContextMenuTranslate, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Translate"), color: theme.actionSheet.primaryTextColor)
}, action: { _, f in
controllerInteraction.performTextSelectionAction(0, NSAttributedString(string: message.text), .translate)
f(.default)
})))
}
if isSpeakSelectionEnabled() && !message.text.isEmpty { if isSpeakSelectionEnabled() && !message.text.isEmpty {
actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_ContextMenuSpeak, icon: { theme in actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_ContextMenuSpeak, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Message"), color: theme.actionSheet.primaryTextColor) return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Message"), color: theme.actionSheet.primaryTextColor)

View File

@ -893,7 +893,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
} }
} }
if item.associatedData.isCopyProtectionEnabled { if item.associatedData.isCopyProtectionEnabled || item.message.isCopyProtected() {
needsShareButton = false needsShareButton = false
} }
} }

View File

@ -1161,7 +1161,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
} }
} }
if item.associatedData.isCopyProtectionEnabled { if item.associatedData.isCopyProtectionEnabled || item.message.isCopyProtected() {
needsShareButton = false needsShareButton = false
} }
} }

View File

@ -351,7 +351,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD
} }
} }
if item.associatedData.isCopyProtectionEnabled { if item.associatedData.isCopyProtectionEnabled || item.message.isCopyProtected() {
needsShareButton = false needsShareButton = false
} }
} }

View File

@ -10,6 +10,8 @@ import AccountContext
import LocalizedPeerData import LocalizedPeerData
import PhotoResources import PhotoResources
import TelegramStringFormatting import TelegramStringFormatting
import TextFormat
import InvisibleInkDustNode
enum ChatMessageReplyInfoType { enum ChatMessageReplyInfoType {
case bubble(incoming: Bool) case bubble(incoming: Bool)
@ -21,6 +23,7 @@ class ChatMessageReplyInfoNode: ASDisplayNode {
private let lineNode: ASImageNode private let lineNode: ASImageNode
private var titleNode: TextNode? private var titleNode: TextNode?
private var textNode: TextNode? private var textNode: TextNode?
private var dustNode: InvisibleInkDustNode?
private var imageNode: TransformImageNode? private var imageNode: TransformImageNode?
private var previousMediaReference: AnyMediaReference? private var previousMediaReference: AnyMediaReference?
@ -64,7 +67,7 @@ class ChatMessageReplyInfoNode: ASDisplayNode {
} }
} }
let (textString, isMedia) = descriptionStringForMessage(contentSettings: context.currentContentSettings.with { $0 }, message: EngineMessage(message), strings: strings, nameDisplayOrder: presentationData.nameDisplayOrder, dateTimeFormat: presentationData.dateTimeFormat, accountPeerId: context.account.peerId) let (textString, isMedia, isText) = descriptionStringForMessage(contentSettings: context.currentContentSettings.with { $0 }, message: EngineMessage(message), strings: strings, nameDisplayOrder: presentationData.nameDisplayOrder, dateTimeFormat: presentationData.dateTimeFormat, accountPeerId: context.account.peerId)
let placeholderColor: UIColor = message.effectivelyIncoming(context.account.peerId) ? presentationData.theme.theme.chat.message.incoming.mediaPlaceholderColor : presentationData.theme.theme.chat.message.outgoing.mediaPlaceholderColor let placeholderColor: UIColor = message.effectivelyIncoming(context.account.peerId) ? presentationData.theme.theme.chat.message.incoming.mediaPlaceholderColor : presentationData.theme.theme.chat.message.outgoing.mediaPlaceholderColor
let titleColor: UIColor let titleColor: UIColor
@ -89,6 +92,21 @@ class ChatMessageReplyInfoNode: ASDisplayNode {
textColor = titleColor textColor = titleColor
} }
let messageText: NSAttributedString
if isText {
let entities = (message.textEntitiesAttribute?.entities ?? []).filter { entity in
if case .Spoiler = entity.type {
return true
} else {
return false
}
}
messageText = stringWithAppliedEntities(message.text, entities: entities, baseColor: textColor, linkColor: textColor, baseFont: textFont, linkFont: textFont, boldFont: textFont, italicFont: textFont, boldItalicFont: textFont, fixedFont: textFont, blockQuoteFont: textFont, underlineLinks: false)
} else {
messageText = NSAttributedString(string: textString, font: textFont, textColor: textColor)
}
var leftInset: CGFloat = 11.0 var leftInset: CGFloat = 11.0
let spacing: CGFloat = 2.0 let spacing: CGFloat = 2.0
@ -131,7 +149,7 @@ class ChatMessageReplyInfoNode: ASDisplayNode {
let textInsets = UIEdgeInsets(top: 3.0, left: 0.0, bottom: 3.0, right: 0.0) let textInsets = UIEdgeInsets(top: 3.0, left: 0.0, bottom: 3.0, right: 0.0)
let (titleLayout, titleApply) = titleNodeLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: titleString, font: titleFont, textColor: titleColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: contrainedTextSize, alignment: .natural, cutout: nil, insets: textInsets)) let (titleLayout, titleApply) = titleNodeLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: titleString, font: titleFont, textColor: titleColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: contrainedTextSize, alignment: .natural, cutout: nil, insets: textInsets))
let (textLayout, textApply) = textNodeLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: textString, font: textFont, textColor: textColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: contrainedTextSize, alignment: .natural, cutout: nil, insets: textInsets)) let (textLayout, textApply) = textNodeLayout(TextNodeLayoutArguments(attributedString: messageText, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: contrainedTextSize, alignment: .natural, cutout: nil, insets: textInsets))
let imageSide = titleLayout.size.height + textLayout.size.height - 16.0 let imageSide = titleLayout.size.height + textLayout.size.height - 16.0
@ -218,8 +236,27 @@ class ChatMessageReplyInfoNode: ASDisplayNode {
node.imageNode?.captureProtected = message.isCopyProtected() node.imageNode?.captureProtected = message.isCopyProtected()
titleNode.frame = CGRect(origin: CGPoint(x: leftInset - textInsets.left, y: spacing - textInsets.top), size: titleLayout.size) titleNode.frame = CGRect(origin: CGPoint(x: leftInset - textInsets.left, y: spacing - textInsets.top), size: titleLayout.size)
textNode.frame = CGRect(origin: CGPoint(x: leftInset - textInsets.left, y: titleNode.frame.maxY - textInsets.bottom + spacing - textInsets.top), size: textLayout.size)
let textFrame = CGRect(origin: CGPoint(x: leftInset - textInsets.left, y: titleNode.frame.maxY - textInsets.bottom + spacing - textInsets.top), size: textLayout.size)
textNode.frame = textFrame
if !textLayout.spoilers.isEmpty {
let dustNode: InvisibleInkDustNode
if let current = node.dustNode {
dustNode = current
} else {
dustNode = InvisibleInkDustNode(textNode: nil)
dustNode.isUserInteractionEnabled = false
node.dustNode = dustNode
node.contentNode.insertSubnode(dustNode, aboveSubnode: textNode)
}
dustNode.update(size: textFrame.size, color: titleColor, rects: textLayout.spoilers.map { $0.offsetBy(dx: 3.0, dy: 3.0).insetBy(dx: 1.0, dy: 1.0) })
dustNode.frame = textFrame.insetBy(dx: -3.0, dy: -3.0).offsetBy(dx: 0.0, dy: 3.0)
} else if let dustNode = node.dustNode {
dustNode.removeFromSupernode()
node.dustNode = nil
}
node.lineNode.image = lineImage node.lineNode.image = lineImage
node.lineNode.frame = CGRect(origin: CGPoint(x: 1.0, y: 3.0), size: CGSize(width: 2.0, height: max(0.0, size.height - 5.0))) node.lineNode.frame = CGRect(origin: CGPoint(x: 1.0, y: 3.0), size: CGSize(width: 2.0, height: max(0.0, size.height - 5.0)))

View File

@ -436,7 +436,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
} }
} }
if item.associatedData.isCopyProtectionEnabled { if item.associatedData.isCopyProtectionEnabled || item.message.isCopyProtected() {
needsShareButton = false needsShareButton = false
} }
} }

View File

@ -374,6 +374,7 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
if let (_, spoilerTextApply) = spoilerTextLayoutAndApply { if let (_, spoilerTextApply) = spoilerTextLayoutAndApply {
let spoilerTextNode = spoilerTextApply() let spoilerTextNode = spoilerTextApply()
if strongSelf.spoilerTextNode == nil { if strongSelf.spoilerTextNode == nil {
spoilerTextNode.alpha = 0.0
spoilerTextNode.isUserInteractionEnabled = false spoilerTextNode.isUserInteractionEnabled = false
spoilerTextNode.contentMode = .topLeft spoilerTextNode.contentMode = .topLeft
spoilerTextNode.contentsScale = UIScreenScale spoilerTextNode.contentsScale = UIScreenScale
@ -384,8 +385,6 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
} }
strongSelf.spoilerTextNode?.frame = textFrame strongSelf.spoilerTextNode?.frame = textFrame
strongSelf.spoilerTextNode?.isHidden = false
strongSelf.spoilerTextNode?.alpha = 0.0
let dustNode: InvisibleInkDustNode let dustNode: InvisibleInkDustNode
if let current = strongSelf.dustNode { if let current = strongSelf.dustNode {
@ -395,11 +394,16 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
strongSelf.dustNode = dustNode strongSelf.dustNode = dustNode
strongSelf.insertSubnode(dustNode, aboveSubnode: spoilerTextNode) strongSelf.insertSubnode(dustNode, aboveSubnode: spoilerTextNode)
} }
dustNode.update(size: textFrame.size, color: messageTheme.primaryTextColor, rects: textLayout.spoilers.map { $0.offsetBy(dx: 3.0, dy: 3.0).insetBy(dx: 0.0, dy: 1.0) }) dustNode.update(size: textFrame.size, color: messageTheme.secondaryTextColor, rects: textLayout.spoilers.map { $0.offsetBy(dx: 3.0, dy: 3.0).insetBy(dx: 0.0, dy: 1.0) })
dustNode.frame = textFrame.insetBy(dx: -3.0, dy: -3.0).offsetBy(dx: 0.0, dy: 3.0) dustNode.frame = textFrame.insetBy(dx: -3.0, dy: -3.0).offsetBy(dx: 0.0, dy: 3.0)
} else if let spoilerTextNode = strongSelf.spoilerTextNode { } else if let spoilerTextNode = strongSelf.spoilerTextNode {
strongSelf.spoilerTextNode = nil strongSelf.spoilerTextNode = nil
spoilerTextNode.removeFromSupernode() spoilerTextNode.removeFromSupernode()
if let dustNode = strongSelf.dustNode {
strongSelf.dustNode = nil
dustNode.removeFromSupernode()
}
} }
if let textSelectionNode = strongSelf.textSelectionNode { if let textSelectionNode = strongSelf.textSelectionNode {
@ -632,7 +636,7 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
override func updateIsExtractedToContextPreview(_ value: Bool) { override func updateIsExtractedToContextPreview(_ value: Bool) {
if value { if value {
if self.textSelectionNode == nil, let item = self.item, !item.associatedData.isCopyProtectionEnabled, let rootNode = item.controllerInteraction.chatControllerNode() { if self.textSelectionNode == nil, let item = self.item, !item.associatedData.isCopyProtectionEnabled && !item.message.isCopyProtected(), let rootNode = item.controllerInteraction.chatControllerNode() {
let selectionColor: UIColor let selectionColor: UIColor
let knobColor: UIColor let knobColor: UIColor
if item.message.effectivelyIncoming(item.context.account.peerId) { if item.message.effectivelyIncoming(item.context.account.peerId) {

File diff suppressed because it is too large Load Diff

View File

@ -394,6 +394,7 @@ private final class ThemeSettingsThemeItemIconNode : ListViewItemNode {
var updatedTheme = false var updatedTheme = false
var updatedWallpaper = false var updatedWallpaper = false
var updatedSelected = false var updatedSelected = false
var updatedNightMode = false
if currentItem?.emoticon != item.emoticon { if currentItem?.emoticon != item.emoticon {
updatedEmoticon = true updatedEmoticon = true
@ -410,6 +411,9 @@ private final class ThemeSettingsThemeItemIconNode : ListViewItemNode {
if currentItem?.selected != item.selected { if currentItem?.selected != item.selected {
updatedSelected = true updatedSelected = true
} }
if currentItem?.nightMode != item.nightMode {
updatedNightMode = true
}
let text = NSAttributedString(string: item.strings.Conversation_Theme_NoTheme, font: Font.semibold(15.0), textColor: item.theme.actionSheet.controlAccentColor) let text = NSAttributedString(string: item.strings.Conversation_Theme_NoTheme, font: Font.semibold(15.0), textColor: item.theme.actionSheet.controlAccentColor)
let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: text, backgroundColor: nil, maximumNumberOfLines: 2, truncationType: .end, constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets())) let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: text, backgroundColor: nil, maximumNumberOfLines: 2, truncationType: .end, constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
@ -423,7 +427,7 @@ private final class ThemeSettingsThemeItemIconNode : ListViewItemNode {
if let strongSelf = self { if let strongSelf = self {
strongSelf.item = item strongSelf.item = item
if updatedThemeReference || updatedWallpaper { if updatedThemeReference || updatedWallpaper || updatedNightMode {
if let themeReference = item.themeReference { if let themeReference = item.themeReference {
strongSelf.imageNode.setSignal(themeIconImage(account: item.context.account, accountManager: item.context.sharedContext.accountManager, theme: themeReference, color: nil, wallpaper: item.wallpaper, nightMode: item.nightMode, emoticon: true)) strongSelf.imageNode.setSignal(themeIconImage(account: item.context.account, accountManager: item.context.sharedContext.accountManager, theme: themeReference, color: nil, wallpaper: item.wallpaper, nightMode: item.nightMode, emoticon: true))
strongSelf.imageNode.backgroundColor = nil strongSelf.imageNode.backgroundColor = nil

View File

@ -169,7 +169,7 @@ final class EditAccessoryPanelNode: AccessoryPanelNode {
if let currentEditMediaReference = self.currentEditMediaReference { if let currentEditMediaReference = self.currentEditMediaReference {
effectiveMessage = effectiveMessage.withUpdatedMedia([currentEditMediaReference.media]) effectiveMessage = effectiveMessage.withUpdatedMedia([currentEditMediaReference.media])
} }
(text, _) = descriptionStringForMessage(contentSettings: context.currentContentSettings.with { $0 }, message: EngineMessage(effectiveMessage), strings: self.strings, nameDisplayOrder: self.nameDisplayOrder, dateTimeFormat: self.dateTimeFormat, accountPeerId: self.context.account.peerId) (text, _, _) = descriptionStringForMessage(contentSettings: context.currentContentSettings.with { $0 }, message: EngineMessage(effectiveMessage), strings: self.strings, nameDisplayOrder: self.nameDisplayOrder, dateTimeFormat: self.dateTimeFormat, accountPeerId: self.context.account.peerId)
} }
var updatedMediaReference: AnyMediaReference? var updatedMediaReference: AnyMediaReference?

View File

@ -5394,7 +5394,35 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
guard let data = self.data, let peer = data.peer, let controller = self.controller else { guard let data = self.data, let peer = data.peer, let controller = self.controller else {
return return
} }
controller.present(QrCodeScreen(context: self.context, updatedPresentationData: controller.updatedPresentationData, subject: .peer(peer: EnginePeer(peer))), in: .window(.root))
let animatedEmojiStickers = context.engine.stickers.loadedStickerPack(reference: .animatedEmoji, forceActualized: false)
|> map { animatedEmoji -> [String: [StickerPackItem]] in
var animatedEmojiStickers: [String: [StickerPackItem]] = [:]
switch animatedEmoji {
case let .result(_, items, _):
for item in items {
if let emoji = item.getStringRepresentationsOfIndexKeys().first {
animatedEmojiStickers[emoji.basicEmoji.0] = [item]
let strippedEmoji = emoji.basicEmoji.0.strippedEmoji
if animatedEmojiStickers[strippedEmoji] == nil {
animatedEmojiStickers[strippedEmoji] = [item]
}
}
}
default:
break
}
return animatedEmojiStickers
}
let _ = (animatedEmojiStickers
|> deliverOnMainQueue).start(next: { [weak self, weak controller] animatedEmojiStickers in
if let strongSelf = self, let controller = controller {
controller.present(ChatQrCodeScreen(context: strongSelf.context, animatedEmojiStickers: animatedEmojiStickers, peer: peer), in: .window(.root))
}
})
// controller.present(QrCodeScreen(context: self.context, updatedPresentationData: controller.updatedPresentationData, subject: .peer(peer: EnginePeer(peer))), in: .window(.root))
} }
fileprivate func openSettings(section: PeerInfoSettingsSection) { fileprivate func openSettings(section: PeerInfoSettingsSection) {
@ -6639,7 +6667,9 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
rightNavigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .done, isForExpandedView: false)) rightNavigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .done, isForExpandedView: false))
} else { } else {
if self.isSettings { if self.isSettings {
leftNavigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .qrCode, isForExpandedView: false)) if let addressName = self.data?.peer?.addressName, !addressName.isEmpty {
leftNavigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .qrCode, isForExpandedView: false))
}
rightNavigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .edit, isForExpandedView: false)) rightNavigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .edit, isForExpandedView: false))
rightNavigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .search, isForExpandedView: true)) rightNavigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .search, isForExpandedView: true))

View File

@ -106,7 +106,7 @@ final class ReplyAccessoryPanelNode: AccessoryPanelNode {
authorName = EnginePeer(author).displayTitle(strings: strings, displayOrder: nameDisplayOrder) authorName = EnginePeer(author).displayTitle(strings: strings, displayOrder: nameDisplayOrder)
} }
if let message = message { if let message = message {
(text, _) = descriptionStringForMessage(contentSettings: context.currentContentSettings.with { $0 }, message: EngineMessage(message), strings: strings, nameDisplayOrder: nameDisplayOrder, dateTimeFormat: dateTimeFormat, accountPeerId: context.account.peerId) (text, _, _) = descriptionStringForMessage(contentSettings: context.currentContentSettings.with { $0 }, message: EngineMessage(message), strings: strings, nameDisplayOrder: nameDisplayOrder, dateTimeFormat: dateTimeFormat, accountPeerId: context.account.peerId)
} }
var updatedMediaReference: AnyMediaReference? var updatedMediaReference: AnyMediaReference?

View File

@ -19,6 +19,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
public var enableDebugDataDisplay: Bool public var enableDebugDataDisplay: Bool
public var acceleratedStickers: Bool public var acceleratedStickers: Bool
public var experimentalBackground: Bool public var experimentalBackground: Bool
public var snow: Bool
public static var defaultSettings: ExperimentalUISettings { public static var defaultSettings: ExperimentalUISettings {
return ExperimentalUISettings( return ExperimentalUISettings(
@ -36,7 +37,8 @@ public struct ExperimentalUISettings: Codable, Equatable {
experimentalCompatibility: false, experimentalCompatibility: false,
enableDebugDataDisplay: false, enableDebugDataDisplay: false,
acceleratedStickers: false, acceleratedStickers: false,
experimentalBackground: false experimentalBackground: false,
snow: false
) )
} }
@ -55,7 +57,8 @@ public struct ExperimentalUISettings: Codable, Equatable {
experimentalCompatibility: Bool, experimentalCompatibility: Bool,
enableDebugDataDisplay: Bool, enableDebugDataDisplay: Bool,
acceleratedStickers: Bool, acceleratedStickers: Bool,
experimentalBackground: Bool experimentalBackground: Bool,
snow: Bool
) { ) {
self.keepChatNavigationStack = keepChatNavigationStack self.keepChatNavigationStack = keepChatNavigationStack
self.skipReadHistory = skipReadHistory self.skipReadHistory = skipReadHistory
@ -72,6 +75,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
self.enableDebugDataDisplay = enableDebugDataDisplay self.enableDebugDataDisplay = enableDebugDataDisplay
self.acceleratedStickers = acceleratedStickers self.acceleratedStickers = acceleratedStickers
self.experimentalBackground = experimentalBackground self.experimentalBackground = experimentalBackground
self.snow = snow
} }
public init(from decoder: Decoder) throws { public init(from decoder: Decoder) throws {
@ -92,6 +96,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
self.enableDebugDataDisplay = (try container.decodeIfPresent(Int32.self, forKey: "enableDebugDataDisplay") ?? 0) != 0 self.enableDebugDataDisplay = (try container.decodeIfPresent(Int32.self, forKey: "enableDebugDataDisplay") ?? 0) != 0
self.acceleratedStickers = (try container.decodeIfPresent(Int32.self, forKey: "acceleratedStickers") ?? 0) != 0 self.acceleratedStickers = (try container.decodeIfPresent(Int32.self, forKey: "acceleratedStickers") ?? 0) != 0
self.experimentalBackground = (try container.decodeIfPresent(Int32.self, forKey: "experimentalBackground") ?? 0) != 0 self.experimentalBackground = (try container.decodeIfPresent(Int32.self, forKey: "experimentalBackground") ?? 0) != 0
self.snow = (try container.decodeIfPresent(Int32.self, forKey: "snow") ?? 0) != 0
} }
public func encode(to encoder: Encoder) throws { public func encode(to encoder: Encoder) throws {
@ -112,6 +117,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
try container.encode((self.enableDebugDataDisplay ? 1 : 0) as Int32, forKey: "enableDebugDataDisplay") try container.encode((self.enableDebugDataDisplay ? 1 : 0) as Int32, forKey: "enableDebugDataDisplay")
try container.encode((self.acceleratedStickers ? 1 : 0) as Int32, forKey: "acceleratedStickers") try container.encode((self.acceleratedStickers ? 1 : 0) as Int32, forKey: "acceleratedStickers")
try container.encode((self.experimentalBackground ? 1 : 0) as Int32, forKey: "experimentalBackground") try container.encode((self.experimentalBackground ? 1 : 0) as Int32, forKey: "experimentalBackground")
try container.encode((self.snow ? 1 : 0) as Int32, forKey: "snow")
} }
} }

View File

@ -698,7 +698,7 @@ public func breakChatInputText(_ text: NSAttributedString) -> [NSAttributedStrin
} }
} }
private let markdownRegexFormat = "(^|\\s|\\n)(````?)([\\s\\S]+?)(````?)([\\s\\n\\.,:?!;]|$)|(^|\\s)(`|\\*\\*|__|~~)([^\\n]+?)\\7([\\s\\.,:?!;]|$)|@(\\d+)\\s*\\((.+?)\\)" private let markdownRegexFormat = "(^|\\s|\\n)(````?)([\\s\\S]+?)(````?)([\\s\\n\\.,:?!;]|$)|(^|\\s)(`|\\*\\*|__|~~|\\|\\|)([^\\n]+?)\\7([\\s\\.,:?!;]|$)|@(\\d+)\\s*\\((.+?)\\)"
private let markdownRegex = try? NSRegularExpression(pattern: markdownRegexFormat, options: [.caseInsensitive, .anchorsMatchLines]) private let markdownRegex = try? NSRegularExpression(pattern: markdownRegexFormat, options: [.caseInsensitive, .anchorsMatchLines])
public func convertMarkdownToAttributes(_ text: NSAttributedString) -> NSAttributedString { public func convertMarkdownToAttributes(_ text: NSAttributedString) -> NSAttributedString {

View File

@ -188,6 +188,7 @@ public enum TextSelectionAction {
case share case share
case lookup case lookup
case speak case speak
case translate
} }
public final class TextSelectionNode: ASDisplayNode { public final class TextSelectionNode: ASDisplayNode {
@ -501,6 +502,12 @@ public final class TextSelectionNode: ASDisplayNode {
self?.performAction(attributedText, .lookup) self?.performAction(attributedText, .lookup)
self?.dismissSelection() self?.dismissSelection()
})) }))
// if #available(iOS 15.0, *) {
// actions.append(ContextMenuAction(content: .text(title: self.strings.Conversation_ContextMenuTranslate, accessibilityLabel: self.strings.Conversation_ContextMenuTranslate), action: { [weak self] in
// self?.performAction(attributedText, .translate)
// self?.dismissSelection()
// }))
// }
if isSpeakSelectionEnabled() { if isSpeakSelectionEnabled() {
actions.append(ContextMenuAction(content: .text(title: self.strings.Conversation_ContextMenuSpeak, accessibilityLabel: self.strings.Conversation_ContextMenuSpeak), action: { [weak self] in actions.append(ContextMenuAction(content: .text(title: self.strings.Conversation_ContextMenuSpeak, accessibilityLabel: self.strings.Conversation_ContextMenuSpeak), action: { [weak self] in
self?.performAction(attributedText, .speak) self?.performAction(attributedText, .speak)

View File

@ -0,0 +1,19 @@
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
swift_library(
name = "Translate",
module_name = "Translate",
srcs = glob([
"Sources/**/*.swift",
]),
copts = [
"-warnings-as-errors",
],
deps = [
"//submodules/Display:Display",
"//submodules/AccountContext:AccountContext",
],
visibility = [
"//visibility:public",
],
)

View File

@ -0,0 +1,27 @@
import Foundation
import UIKit
import Display
import AccountContext
// Incuding at least one Objective-C class in a swift file ensures that it doesn't get stripped by the linker
private final class LinkHelperClass: NSObject {
}
public func translateText(context: AccountContext, text: String) {
guard !text.isEmpty else {
return
}
if #available(iOS 15.0, *) {
let textField = UITextField()
textField.text = text
if let navigationController = context.sharedContext.mainWindow?.viewController as? NavigationController, let topController = navigationController.topViewController as? ViewController {
topController.view.addSubview(textField)
textField.selectAll(nil)
textField.perform(NSSelectorFromString(["_", "trans", "late:"].joined(separator: "")), with: nil)
DispatchQueue.main.async {
textField.removeFromSuperview()
}
}
}
}

View File

@ -56,6 +56,8 @@ public protocol WallpaperBackgroundNode: ASDisplayNode {
func updateBubbleTheme(bubbleTheme: PresentationTheme, bubbleCorners: PresentationChatBubbleCorners) func updateBubbleTheme(bubbleTheme: PresentationTheme, bubbleCorners: PresentationChatBubbleCorners)
func hasBubbleBackground(for type: WallpaperBubbleType) -> Bool func hasBubbleBackground(for type: WallpaperBubbleType) -> Bool
func makeBubbleBackground(for type: WallpaperBubbleType) -> WallpaperBubbleBackgroundNode? func makeBubbleBackground(for type: WallpaperBubbleType) -> WallpaperBubbleBackgroundNode?
func makeDimmedNode() -> ASDisplayNode?
} }
final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode { final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode {
@ -799,7 +801,7 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode
if isFirstLayout && !self.frame.isEmpty { if isFirstLayout && !self.frame.isEmpty {
self.updateScale() self.updateScale()
if false, self.newYearNode == nil { if self.context.sharedContext.immediateExperimentalUISettings.snow, self.newYearNode == nil {
let newYearNode = WallpaperNewYearNode() let newYearNode = WallpaperNewYearNode()
self.addSubnode(newYearNode) self.addSubnode(newYearNode)
self.newYearNode = newYearNode self.newYearNode = newYearNode
@ -897,6 +899,14 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode
node.updateContents() node.updateContents()
return node return node
} }
func makeDimmedNode() -> ASDisplayNode? {
if let gradientBackgroundNode = self.gradientBackgroundNode {
return GradientBackgroundNode.CloneNode(parentNode: gradientBackgroundNode)
} else {
return nil
}
}
} }
private protocol WallpaperComponentView: AnyObject { private protocol WallpaperComponentView: AnyObject {
@ -1695,6 +1705,10 @@ final class WallpaperBackgroundNodeMergedImpl: ASDisplayNode, WallpaperBackgroun
node.updateContents() node.updateContents()
return node return node
} }
func makeDimmedNode() -> ASDisplayNode? {
return nil
}
} }
private let sharedStorage = WallpaperBackgroundNodeMergedImpl.SharedStorage() private let sharedStorage = WallpaperBackgroundNodeMergedImpl.SharedStorage()
@ -1738,7 +1752,7 @@ private class WallpaperNewYearNode: ASDisplayNode {
cell1.scale = 0.04 cell1.scale = 0.04
cell1.scaleRange = 0.15 cell1.scaleRange = 0.15
cell1.color = UIColor.white.withAlphaComponent(0.88).cgColor cell1.color = UIColor.white.withAlphaComponent(0.88).cgColor
cell1.alphaRange = -0.2 // cell1.alphaRange = -0.2
particlesLayer.emitterCells = [cell1] particlesLayer.emitterCells = [cell1]
} }

View File

@ -1300,7 +1300,23 @@ public func themeImage(account: Account, accountManager: AccountManager<Telegram
} }
} }
public func themeIconImage(account: Account, accountManager: AccountManager<TelegramAccountManagerTypes>, theme: PresentationThemeReference, color: PresentationThemeAccentColor?, wallpaper: TelegramWallpaper? = nil, nightMode: Bool? = nil, emoticon: Bool = false, large: Bool = false) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { private let qrIconImage: UIImage = {
return generateImage(CGSize(width: 36.0, height: 36.0), rotatedContext: { size, context in
let bounds = CGRect(origin: CGPoint(), size: size)
context.clear(bounds)
context.setFillColor(UIColor.white.cgColor)
context.addPath(UIBezierPath(roundedRect: CGRect(origin: CGPoint(), size: size), cornerRadius: 9.0).cgPath)
context.fillPath()
if let image = UIImage(bundleImageName: "Settings/QrButtonIcon")?.cgImage {
context.clip(to: CGRect(x: 6.0, y: 6.0, width: 24.0, height: 24.0), mask: image)
context.clear(bounds)
}
})!
}()
public func themeIconImage(account: Account, accountManager: AccountManager<TelegramAccountManagerTypes>, theme: PresentationThemeReference, color: PresentationThemeAccentColor?, wallpaper: TelegramWallpaper? = nil, nightMode: Bool? = nil, emoticon: Bool = false, large: Bool = false, qr: Bool = false) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> {
let colorsSignal: Signal<((UIColor, UIColor?, [UInt32]), [UIColor], [UIColor], UIImage?, Bool, Bool, CGFloat, Int32?), NoError> let colorsSignal: Signal<((UIColor, UIColor?, [UInt32]), [UIColor], [UIColor], UIImage?, Bool, Bool, CGFloat, Int32?), NoError>
var reference: MediaResourceReference? var reference: MediaResourceReference?
@ -1552,134 +1568,140 @@ public func themeIconImage(account: Account, accountManager: AccountManager<Tele
} }
} }
c.translateBy(x: drawingRect.width / 2.0, y: drawingRect.height / 2.0) if qr {
c.scaleBy(x: 1.0, y: -1.0) if let image = qrIconImage.cgImage {
c.translateBy(x: -drawingRect.width / 2.0, y: -drawingRect.height / 2.0) c.draw(image, in: CGRect(x: floor((drawingRect.width - 36.0) / 2.0), y: floor((drawingRect.height - 36.0) / 2.0), width: 36.0, height: 36.0))
}
let incomingColors = colors.1 } else {
if emoticon { c.translateBy(x: drawingRect.width / 2.0, y: drawingRect.height / 2.0)
if large { c.scaleBy(x: 1.0, y: -1.0)
c.saveGState() c.translateBy(x: -drawingRect.width / 2.0, y: -drawingRect.height / 2.0)
let incomingColors = colors.1
if emoticon {
if large {
c.saveGState()
c.translateBy(x: 7.0, y: 27.0) c.translateBy(x: 7.0, y: 27.0)
c.translateBy(x: 114.0, y: 32.0) c.translateBy(x: 114.0, y: 32.0)
c.scaleBy(x: 1.0, y: -1.0) c.scaleBy(x: 1.0, y: -1.0)
c.translateBy(x: -114.0, y: -32.0) c.translateBy(x: -114.0, y: -32.0)
let _ = try? drawSvgPath(c, path: "M12.8304,29.8712 C10.0551,31.8416 6.6628,33 2.99998,33 C1.98426,33 0.989361,32.9109 0.022644,32.7402 C2.97318,31.9699 5.24596,29.5785 5.84625,26.5607 C5.99996,25.7879 5.99996,24.8586 5.99996,23 V16.0 H6.00743 C6.27176,7.11861 13.5546,0 22.5,0 H61.5 C70.6127,0 78,7.3873 78,16.5 C78,25.6127 70.6127,33 61.5,33 H22.5 C18.8883,33 15.5476,31.8396 12.8304,29.8712 ") let _ = try? drawSvgPath(c, path: "M12.8304,29.8712 C10.0551,31.8416 6.6628,33 2.99998,33 C1.98426,33 0.989361,32.9109 0.022644,32.7402 C2.97318,31.9699 5.24596,29.5785 5.84625,26.5607 C5.99996,25.7879 5.99996,24.8586 5.99996,23 V16.0 H6.00743 C6.27176,7.11861 13.5546,0 22.5,0 H61.5 C70.6127,0 78,7.3873 78,16.5 C78,25.6127 70.6127,33 61.5,33 H22.5 C18.8883,33 15.5476,31.8396 12.8304,29.8712 ")
if Set(incomingColors.map(\.rgb)).count > 1 { if Set(incomingColors.map(\.rgb)).count > 1 {
c.clip() c.clip()
var colors: [CGColor] = [] var colors: [CGColor] = []
var locations: [CGFloat] = [] var locations: [CGFloat] = []
for i in 0 ..< incomingColors.count { for i in 0 ..< incomingColors.count {
let t = CGFloat(i) / CGFloat(incomingColors.count - 1) let t = CGFloat(i) / CGFloat(incomingColors.count - 1)
locations.append(t) locations.append(t)
colors.append(incomingColors[i].cgColor) colors.append(incomingColors[i].cgColor)
}
let colorSpace = CGColorSpaceCreateDeviceRGB()
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as NSArray, locations: &locations)!
c.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: 34.0), options: CGGradientDrawingOptions())
} else {
c.setFillColor(incomingColors[0].cgColor)
c.fillPath()
} }
let colorSpace = CGColorSpaceCreateDeviceRGB() c.restoreGState()
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as NSArray, locations: &locations)!
c.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: 34.0), options: CGGradientDrawingOptions())
} else { } else {
c.setFillColor(incomingColors[0].cgColor) let rect = CGRect(x: 8.0, y: 44.0, width: 48.0, height: 24.0)
c.fillPath() c.addPath(UIBezierPath(roundedRect: rect, cornerRadius: 12.0).cgPath)
} c.clip()
c.restoreGState() if incomingColors.count >= 2 {
} else { let gradientColors = incomingColors.reversed().map { $0.cgColor } as CFArray
let rect = CGRect(x: 8.0, y: 44.0, width: 48.0, height: 24.0)
c.addPath(UIBezierPath(roundedRect: rect, cornerRadius: 12.0).cgPath)
c.clip()
if incomingColors.count >= 2 {
let gradientColors = incomingColors.reversed().map { $0.cgColor } as CFArray
var locations: [CGFloat] = [] var locations: [CGFloat] = []
for i in 0 ..< incomingColors.count { for i in 0 ..< incomingColors.count {
let t = CGFloat(i) / CGFloat(incomingColors.count - 1) let t = CGFloat(i) / CGFloat(incomingColors.count - 1)
locations.append(t) locations.append(t)
}
let colorSpace = CGColorSpaceCreateDeviceRGB()
let gradient = CGGradient(colorsSpace: colorSpace, colors: gradientColors, locations: &locations)!
c.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: rect.minY), end: CGPoint(x: 0.0, y: rect.maxY), options: CGGradientDrawingOptions())
} else if !incomingColors.isEmpty {
c.setFillColor(incomingColors[0].cgColor)
c.fill(rect)
} }
let colorSpace = CGColorSpaceCreateDeviceRGB()
let gradient = CGGradient(colorsSpace: colorSpace, colors: gradientColors, locations: &locations)! c.resetClip()
}
} else {
let incoming = generateGradientTintedImage(image: UIImage(bundleImageName: "Settings/ThemeBubble"), colors: incomingColors)
c.draw(incoming!.cgImage!, in: CGRect(x: 9.0, y: 34.0, width: 57.0, height: 16.0))
}
if !(emoticon && large) {
c.translateBy(x: drawingRect.width / 2.0, y: drawingRect.height / 2.0)
c.scaleBy(x: -1.0, y: 1.0)
c.translateBy(x: -drawingRect.width / 2.0, y: -drawingRect.height / 2.0)
}
let outgoingColors = colors.2
if emoticon {
if large {
c.saveGState()
c.translateBy(x: (drawingRect.width - 120) - 71, y: 66.0)
c.translateBy(x: 114.0, y: 32.0)
c.scaleBy(x: 1.0, y: -1.0)
c.translateBy(x: 0.0, y: -32.0)
let _ = try? drawSvgPath(c, path: "M57.1696,29.8712 C59.9449,31.8416 63.3372,33 67,33 C68.0157,33 69.0106,32.9109 69.9773,32.7402 C67.0268,31.9699 64.754,29.5786 64.1537,26.5607 C64,25.7879 64,24.8586 64,23 V16.5 V16 H63.9926 C63.7282,7.11861 56.4454,0 47.5,0 H16.5 C7.3873,0 0,7.3873 0,16.5 C0,25.6127 7.3873,33 16.5,33 H47.5 C51.1117,33 54.4524,31.8396 57.1696,29.8712 ")
if Set(outgoingColors.map(\.rgb)).count > 1 {
c.clip()
c.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: rect.minY), end: CGPoint(x: 0.0, y: rect.maxY), options: CGGradientDrawingOptions()) var colors: [CGColor] = []
} else if !incomingColors.isEmpty { var locations: [CGFloat] = []
c.setFillColor(incomingColors[0].cgColor) for i in 0 ..< outgoingColors.count {
c.fill(rect) let t = CGFloat(i) / CGFloat(outgoingColors.count - 1)
locations.append(t)
colors.append(outgoingColors[i].cgColor)
}
let colorSpace = CGColorSpaceCreateDeviceRGB()
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as NSArray, locations: &locations)!
c.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: 34.0), options: CGGradientDrawingOptions())
} else {
c.setFillColor(outgoingColors[0].cgColor)
c.fillPath()
}
c.restoreGState()
} else {
let rect = CGRect(x: 8.0, y: 72.0, width: 48.0, height: 24.0)
c.addPath(UIBezierPath(roundedRect: rect, cornerRadius: 12.0).cgPath)
c.clip()
if outgoingColors.count >= 2 {
let gradientColors = outgoingColors.reversed().map { $0.cgColor } as CFArray
var locations: [CGFloat] = []
for i in 0 ..< outgoingColors.count {
let t = CGFloat(i) / CGFloat(outgoingColors.count - 1)
locations.append(t)
}
let colorSpace = CGColorSpaceCreateDeviceRGB()
let gradient = CGGradient(colorsSpace: colorSpace, colors: gradientColors, locations: &locations)!
c.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: rect.minY), end: CGPoint(x: 0.0, y: rect.maxY), options: CGGradientDrawingOptions())
} else if !outgoingColors.isEmpty {
c.setFillColor(outgoingColors[0].cgColor)
c.fill(rect)
}
} }
c.resetClip() c.resetClip()
}
} else {
let incoming = generateGradientTintedImage(image: UIImage(bundleImageName: "Settings/ThemeBubble"), colors: incomingColors)
c.draw(incoming!.cgImage!, in: CGRect(x: 9.0, y: 34.0, width: 57.0, height: 16.0))
}
if !(emoticon && large) {
c.translateBy(x: drawingRect.width / 2.0, y: drawingRect.height / 2.0)
c.scaleBy(x: -1.0, y: 1.0)
c.translateBy(x: -drawingRect.width / 2.0, y: -drawingRect.height / 2.0)
}
let outgoingColors = colors.2
if emoticon {
if large {
c.saveGState()
c.translateBy(x: (drawingRect.width - 120) - 71, y: 66.0)
c.translateBy(x: 114.0, y: 32.0)
c.scaleBy(x: 1.0, y: -1.0)
c.translateBy(x: 0.0, y: -32.0)
let _ = try? drawSvgPath(c, path: "M57.1696,29.8712 C59.9449,31.8416 63.3372,33 67,33 C68.0157,33 69.0106,32.9109 69.9773,32.7402 C67.0268,31.9699 64.754,29.5786 64.1537,26.5607 C64,25.7879 64,24.8586 64,23 V16.5 V16 H63.9926 C63.7282,7.11861 56.4454,0 47.5,0 H16.5 C7.3873,0 0,7.3873 0,16.5 C0,25.6127 7.3873,33 16.5,33 H47.5 C51.1117,33 54.4524,31.8396 57.1696,29.8712 ")
if Set(outgoingColors.map(\.rgb)).count > 1 {
c.clip()
var colors: [CGColor] = []
var locations: [CGFloat] = []
for i in 0 ..< outgoingColors.count {
let t = CGFloat(i) / CGFloat(outgoingColors.count - 1)
locations.append(t)
colors.append(outgoingColors[i].cgColor)
}
let colorSpace = CGColorSpaceCreateDeviceRGB()
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as NSArray, locations: &locations)!
c.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: 34.0), options: CGGradientDrawingOptions())
} else {
c.setFillColor(outgoingColors[0].cgColor)
c.fillPath()
}
c.restoreGState()
} else { } else {
let rect = CGRect(x: 8.0, y: 72.0, width: 48.0, height: 24.0) let outgoing = generateGradientTintedImage(image: UIImage(bundleImageName: "Settings/ThemeBubble"), colors: outgoingColors)
c.addPath(UIBezierPath(roundedRect: rect, cornerRadius: 12.0).cgPath) c.draw(outgoing!.cgImage!, in: CGRect(x: 9.0, y: 12.0, width: 57.0, height: 16.0))
c.clip()
if outgoingColors.count >= 2 {
let gradientColors = outgoingColors.reversed().map { $0.cgColor } as CFArray
var locations: [CGFloat] = []
for i in 0 ..< outgoingColors.count {
let t = CGFloat(i) / CGFloat(outgoingColors.count - 1)
locations.append(t)
}
let colorSpace = CGColorSpaceCreateDeviceRGB()
let gradient = CGGradient(colorsSpace: colorSpace, colors: gradientColors, locations: &locations)!
c.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: rect.minY), end: CGPoint(x: 0.0, y: rect.maxY), options: CGGradientDrawingOptions())
} else if !outgoingColors.isEmpty {
c.setFillColor(outgoingColors[0].cgColor)
c.fill(rect)
}
} }
c.resetClip()
} else {
let outgoing = generateGradientTintedImage(image: UIImage(bundleImageName: "Settings/ThemeBubble"), colors: outgoingColors)
c.draw(outgoing!.cgImage!, in: CGRect(x: 9.0, y: 12.0, width: 57.0, height: 16.0))
} }
} }
addCorners(context, arguments: arguments) addCorners(context, arguments: arguments)