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";
"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/TelegramCallsUI:TelegramCallsUI",
"//submodules/StickerResources:StickerResources",
"//submodules/TextFormat:TextFormat",
],
visibility = [
"//visibility:public",

View File

@ -17,6 +17,8 @@ import PhotoResources
import ChatListSearchItemNode
import ContextUI
import ChatInterfaceState
import TextFormat
import InvisibleInkDustNode
public enum ChatListItemContent {
public final class DraftState: Equatable {
@ -427,6 +429,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
let measureNode: TextNode
private var currentItemHeight: CGFloat?
let textNode: TextNode
var dustNode: InvisibleInkDustNode?
let inputActivitiesNode: ChatListInputActivitiesNode
let dateNode: TextNode
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)
} else if let message = messages.last {
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 {
composedString = NSMutableAttributedString()
composedString.append(NSAttributedString(string: "\(inlineAuthorPrefix): ", font: textFont, textColor: theme.titleColor))
composedString.append(NSAttributedString(string: messageText, font: textFont, textColor: theme.messageTextColor))
composedString.append(messageString)
} else {
composedString = NSMutableAttributedString(string: messageText, font: textFont, textColor: theme.messageTextColor)
composedString = NSMutableAttributedString(attributedString: messageString)
}
if let searchQuery = item.interaction.searchTextHighightState {
@ -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)
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
let inputActivities = inputActivities?.filter({
switch $0.1 {

View File

@ -81,6 +81,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
case enableDebugDataDisplay(Bool)
case acceleratedStickers(Bool)
case experimentalBackground(Bool)
case snow(Bool)
case playerEmbedding(Bool)
case playlistPlayback(Bool)
case voiceConference
@ -102,7 +103,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
return DebugControllerSection.logging.rawValue
case .enableRaiseToSpeak, .keepChatNavigationStack, .skipReadHistory, .crashOnSlowQueries:
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
case .preferredVideoCodec:
return DebugControllerSection.videoExperiments.rawValue
@ -173,14 +174,16 @@ private enum DebugControllerEntry: ItemListNodeEntry {
return 29
case .experimentalBackground:
return 30
case .playerEmbedding:
case .snow:
return 31
case .playlistPlayback:
case .playerEmbedding:
return 32
case .voiceConference:
case .playlistPlayback:
return 33
case .voiceConference:
return 34
case let .preferredVideoCodec(index, _, _, _):
return 34 + index
return 35 + index
case .disableVideoAspectScaling:
return 100
case .enableVoipTcp:
@ -814,6 +817,16 @@ private enum DebugControllerEntry: ItemListNodeEntry {
})
}).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):
return ItemListSwitchItem(presentationData: presentationData, title: "Player Embedding", value: value, sectionId: self.section, style: .blocks, updated: { value 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(.acceleratedStickers(experimentalSettings.acceleratedStickers))
entries.append(.experimentalBackground(experimentalSettings.experimentalBackground))
entries.append(.snow(experimentalSettings.snow))
entries.append(.playerEmbedding(experimentalSettings.playerEmbedding))
entries.append(.playlistPlayback(experimentalSettings.playlistPlayback))
}

View File

@ -1005,6 +1005,10 @@ public class TextNode: ASDisplayNode {
}
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 {
break
}
@ -1033,7 +1037,7 @@ public class TextNode: ASDisplayNode {
}
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")] {
var ascent: CGFloat = 0.0
var descent: CGFloat = 0.0

View File

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

View File

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

View File

@ -203,6 +203,7 @@ public enum RecognizedTextSelectionAction {
case share
case lookup
case speak
case translate
}
public final class RecognizedTextSelectionNode: ASDisplayNode {
@ -509,6 +510,12 @@ public final class RecognizedTextSelectionNode: ASDisplayNode {
self?.performAction(selectedText, .lookup)
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() {
actions.append(ContextMenuAction(content: .text(title: self.strings.Conversation_ContextMenuSpeak, accessibilityLabel: self.strings.Conversation_ContextMenuSpeak), action: { [weak self] in
self?.performAction(selectedText, .speak)

View File

@ -62,7 +62,7 @@ public class InvisibleInkDustNode: ASDisplayNode {
public var isRevealed = false
public init(textNode: TextNode) {
public init(textNode: TextNode?) {
self.textNode = textNode
self.emitterNode = ASDisplayNode()
@ -144,7 +144,7 @@ public class InvisibleInkDustNode: ASDisplayNode {
}
@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
}
@ -155,15 +155,15 @@ public class InvisibleInkDustNode: ASDisplayNode {
self.emitterLayer?.setValue(position, forKeyPath: "emitterBehaviors.fingerAttractor.position")
Queue.mainQueue().after(0.1 * UIView.animationDurationFactor()) {
self.textNode?.view.mask = self.textMaskNode.view
self.textNode?.alpha = 1.0
textNode.view.mask = self.textMaskNode.view
textNode.alpha = 1.0
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.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?.textNode?.view.mask = nil
self.textSpotNode.layer.animateScale(from: 0.1, to: 3.5, duration: 0.71, removeOnCompletion: false, completion: { _ in
textNode.view.mask = nil
})
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)))
Queue.mainQueue().after(timeToRead * UIView.animationDurationFactor()) {
self.isRevealed = false
@ -195,11 +195,9 @@ public class InvisibleInkDustNode: ASDisplayNode {
let transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .linear)
transition.updateAlpha(node: self, alpha: 1.0)
if let textNode = self.textNode {
transition.updateAlpha(node: textNode, alpha: 0.0)
}
}
}
private func updateEmitter() {
guard let (size, color, rects) = self.currentParams else {

View File

@ -268,7 +268,6 @@ public final class QrCodeScreen: ViewController {
self.qrImageNode.cornerRadius = 16.0
self.qrIconNode = AnimatedStickerNode()
self.qrIconNode.setup(source: AnimatedStickerNodeLocalFileSource(name: "PlaneLogo"), width: 240, height: 240, mode: .direct(cachePathPrefix: nil))
self.qrIconNode.visibility = true
@ -434,7 +433,6 @@ public final class QrCodeScreen: ViewController {
let imageSide: CGFloat = 240.0
let imageSize = CGSize(width: imageSide, height: imageSide)
let imageApply = makeImageLayout(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets(), emptyColor: nil))
let _ = imageApply()
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
items.append(ActionSheetTextItem(title: presentationData.strings.ClearCache_ClearDescription))
for categoryId in validCategories {
if let (_, size) = sizeIndex[categoryId] {
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
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))
}

View File

@ -69,7 +69,7 @@ public final class SolidRoundedButtonNode: ASDisplayNode {
public var icon: UIImage? {
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
}
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? {

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)
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 {

View File

@ -251,6 +251,7 @@ swift_library(
"//submodules/InvisibleInkDustNode:InvisibleInkDustNode",
"//submodules/QrCodeUI:QrCodeUI",
"//submodules/Components/ReactionListContextMenuContent:ReactionListContextMenuContent",
"//submodules/Translate:Translate",
] + select({
"@build_bazel_rules_apple//apple:ios_armv7": [],
"@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 TelegramPermissionsUI
import Speak
import Translate
import UniversalMediaPlayer
import WallpaperBackgroundNode
import ChatListUI
@ -2759,6 +2760,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
case .speak:
speakText(text.string)
case .translate:
translateText(context: context, text: text.string)
}
}, displayImportedMessageTooltip: { [weak self] _ in
guard let strongSelf = self else {

View File

@ -760,6 +760,15 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
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 {
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)

View File

@ -893,7 +893,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
}
}
if item.associatedData.isCopyProtectionEnabled {
if item.associatedData.isCopyProtectionEnabled || item.message.isCopyProtected() {
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
}
}

View File

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

View File

@ -10,6 +10,8 @@ import AccountContext
import LocalizedPeerData
import PhotoResources
import TelegramStringFormatting
import TextFormat
import InvisibleInkDustNode
enum ChatMessageReplyInfoType {
case bubble(incoming: Bool)
@ -21,6 +23,7 @@ class ChatMessageReplyInfoNode: ASDisplayNode {
private let lineNode: ASImageNode
private var titleNode: TextNode?
private var textNode: TextNode?
private var dustNode: InvisibleInkDustNode?
private var imageNode: TransformImageNode?
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 titleColor: UIColor
@ -89,6 +92,21 @@ class ChatMessageReplyInfoNode: ASDisplayNode {
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
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 (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
@ -218,7 +236,26 @@ class ChatMessageReplyInfoNode: ASDisplayNode {
node.imageNode?.captureProtected = message.isCopyProtected()
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.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
}
}

View File

@ -374,6 +374,7 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
if let (_, spoilerTextApply) = spoilerTextLayoutAndApply {
let spoilerTextNode = spoilerTextApply()
if strongSelf.spoilerTextNode == nil {
spoilerTextNode.alpha = 0.0
spoilerTextNode.isUserInteractionEnabled = false
spoilerTextNode.contentMode = .topLeft
spoilerTextNode.contentsScale = UIScreenScale
@ -384,8 +385,6 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
}
strongSelf.spoilerTextNode?.frame = textFrame
strongSelf.spoilerTextNode?.isHidden = false
strongSelf.spoilerTextNode?.alpha = 0.0
let dustNode: InvisibleInkDustNode
if let current = strongSelf.dustNode {
@ -395,11 +394,16 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
strongSelf.dustNode = dustNode
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)
} else if let spoilerTextNode = strongSelf.spoilerTextNode {
strongSelf.spoilerTextNode = nil
spoilerTextNode.removeFromSupernode()
if let dustNode = strongSelf.dustNode {
strongSelf.dustNode = nil
dustNode.removeFromSupernode()
}
}
if let textSelectionNode = strongSelf.textSelectionNode {
@ -632,7 +636,7 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
override func updateIsExtractedToContextPreview(_ value: Bool) {
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 knobColor: UIColor
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 updatedWallpaper = false
var updatedSelected = false
var updatedNightMode = false
if currentItem?.emoticon != item.emoticon {
updatedEmoticon = true
@ -410,6 +411,9 @@ private final class ThemeSettingsThemeItemIconNode : ListViewItemNode {
if currentItem?.selected != item.selected {
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 (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 {
strongSelf.item = item
if updatedThemeReference || updatedWallpaper {
if updatedThemeReference || updatedWallpaper || updatedNightMode {
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.backgroundColor = nil

View File

@ -169,7 +169,7 @@ final class EditAccessoryPanelNode: AccessoryPanelNode {
if let currentEditMediaReference = self.currentEditMediaReference {
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?

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 {
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) {
@ -6639,7 +6667,9 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
rightNavigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .done, isForExpandedView: false))
} else {
if self.isSettings {
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: .search, isForExpandedView: true))

View File

@ -106,7 +106,7 @@ final class ReplyAccessoryPanelNode: AccessoryPanelNode {
authorName = EnginePeer(author).displayTitle(strings: strings, displayOrder: nameDisplayOrder)
}
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?

View File

@ -19,6 +19,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
public var enableDebugDataDisplay: Bool
public var acceleratedStickers: Bool
public var experimentalBackground: Bool
public var snow: Bool
public static var defaultSettings: ExperimentalUISettings {
return ExperimentalUISettings(
@ -36,7 +37,8 @@ public struct ExperimentalUISettings: Codable, Equatable {
experimentalCompatibility: false,
enableDebugDataDisplay: false,
acceleratedStickers: false,
experimentalBackground: false
experimentalBackground: false,
snow: false
)
}
@ -55,7 +57,8 @@ public struct ExperimentalUISettings: Codable, Equatable {
experimentalCompatibility: Bool,
enableDebugDataDisplay: Bool,
acceleratedStickers: Bool,
experimentalBackground: Bool
experimentalBackground: Bool,
snow: Bool
) {
self.keepChatNavigationStack = keepChatNavigationStack
self.skipReadHistory = skipReadHistory
@ -72,6 +75,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
self.enableDebugDataDisplay = enableDebugDataDisplay
self.acceleratedStickers = acceleratedStickers
self.experimentalBackground = experimentalBackground
self.snow = snow
}
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.acceleratedStickers = (try container.decodeIfPresent(Int32.self, forKey: "acceleratedStickers") ?? 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 {
@ -112,6 +117,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
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.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])
public func convertMarkdownToAttributes(_ text: NSAttributedString) -> NSAttributedString {

View File

@ -188,6 +188,7 @@ public enum TextSelectionAction {
case share
case lookup
case speak
case translate
}
public final class TextSelectionNode: ASDisplayNode {
@ -501,6 +502,12 @@ public final class TextSelectionNode: ASDisplayNode {
self?.performAction(attributedText, .lookup)
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() {
actions.append(ContextMenuAction(content: .text(title: self.strings.Conversation_ContextMenuSpeak, accessibilityLabel: self.strings.Conversation_ContextMenuSpeak), action: { [weak self] in
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 hasBubbleBackground(for type: WallpaperBubbleType) -> Bool
func makeBubbleBackground(for type: WallpaperBubbleType) -> WallpaperBubbleBackgroundNode?
func makeDimmedNode() -> ASDisplayNode?
}
final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode {
@ -799,7 +801,7 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode
if isFirstLayout && !self.frame.isEmpty {
self.updateScale()
if false, self.newYearNode == nil {
if self.context.sharedContext.immediateExperimentalUISettings.snow, self.newYearNode == nil {
let newYearNode = WallpaperNewYearNode()
self.addSubnode(newYearNode)
self.newYearNode = newYearNode
@ -897,6 +899,14 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode
node.updateContents()
return node
}
func makeDimmedNode() -> ASDisplayNode? {
if let gradientBackgroundNode = self.gradientBackgroundNode {
return GradientBackgroundNode.CloneNode(parentNode: gradientBackgroundNode)
} else {
return nil
}
}
}
private protocol WallpaperComponentView: AnyObject {
@ -1695,6 +1705,10 @@ final class WallpaperBackgroundNodeMergedImpl: ASDisplayNode, WallpaperBackgroun
node.updateContents()
return node
}
func makeDimmedNode() -> ASDisplayNode? {
return nil
}
}
private let sharedStorage = WallpaperBackgroundNodeMergedImpl.SharedStorage()
@ -1738,7 +1752,7 @@ private class WallpaperNewYearNode: ASDisplayNode {
cell1.scale = 0.04
cell1.scaleRange = 0.15
cell1.color = UIColor.white.withAlphaComponent(0.88).cgColor
cell1.alphaRange = -0.2
// cell1.alphaRange = -0.2
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>
var reference: MediaResourceReference?
@ -1552,6 +1568,11 @@ public func themeIconImage(account: Account, accountManager: AccountManager<Tele
}
}
if qr {
if let image = qrIconImage.cgImage {
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))
}
} else {
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)
@ -1682,6 +1703,7 @@ public func themeIconImage(account: Account, accountManager: AccountManager<Tele
c.draw(outgoing!.cgImage!, in: CGRect(x: 9.0, y: 12.0, width: 57.0, height: 16.0))
}
}
}
addCorners(context, arguments: arguments)
return context
}