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 {
@ -1395,7 +1412,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
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 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()))
@ -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,9 +195,7 @@ 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)
}
transition.updateAlpha(node: textNode, alpha: 0.0)
}
}

View File

@ -267,8 +267,7 @@ public final class QrCodeScreen: ViewController {
self.qrImageNode.clipsToBounds = true
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.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,8 +236,27 @@ 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 {
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: .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,134 +1568,140 @@ public func themeIconImage(account: Account, accountManager: AccountManager<Tele
}
}
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 incomingColors = colors.1
if emoticon {
if large {
c.saveGState()
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)
let incomingColors = colors.1
if emoticon {
if large {
c.saveGState()
c.translateBy(x: 7.0, y: 27.0)
c.translateBy(x: 114.0, y: 32.0)
c.scaleBy(x: 1.0, y: -1.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 ")
if Set(incomingColors.map(\.rgb)).count > 1 {
c.clip()
c.translateBy(x: 7.0, y: 27.0)
c.translateBy(x: 114.0, y: 32.0)
c.scaleBy(x: 1.0, y: -1.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 ")
if Set(incomingColors.map(\.rgb)).count > 1 {
c.clip()
var colors: [CGColor] = []
var locations: [CGFloat] = []
for i in 0 ..< incomingColors.count {
let t = CGFloat(i) / CGFloat(incomingColors.count - 1)
locations.append(t)
colors.append(incomingColors[i].cgColor)
var colors: [CGColor] = []
var locations: [CGFloat] = []
for i in 0 ..< incomingColors.count {
let t = CGFloat(i) / CGFloat(incomingColors.count - 1)
locations.append(t)
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()
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())
c.restoreGState()
} else {
c.setFillColor(incomingColors[0].cgColor)
c.fillPath()
}
c.restoreGState()
} else {
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
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] = []
for i in 0 ..< incomingColors.count {
let t = CGFloat(i) / CGFloat(incomingColors.count - 1)
locations.append(t)
var locations: [CGFloat] = []
for i in 0 ..< incomingColors.count {
let t = CGFloat(i) / CGFloat(incomingColors.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 !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())
} else if !incomingColors.isEmpty {
c.setFillColor(incomingColors[0].cgColor)
c.fill(rect)
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 {
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()
}
} 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 {
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)
}
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))
}
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)