mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Various Improvements
This commit is contained in:
parent
0cc01c5280
commit
835ce9adae
BIN
Telegram/Telegram-iOS/Resources/PlaneLogoPlain.tgs
Normal file
BIN
Telegram/Telegram-iOS/Resources/PlaneLogoPlain.tgs
Normal file
Binary file not shown.
@ -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.";
|
||||
|
@ -61,6 +61,7 @@ swift_library(
|
||||
"//submodules/TelegramStringFormatting:TelegramStringFormatting",
|
||||
"//submodules/TelegramCallsUI:TelegramCallsUI",
|
||||
"//submodules/StickerResources:StickerResources",
|
||||
"//submodules/TextFormat:TextFormat",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -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 {
|
||||
|
@ -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))
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -39,6 +39,7 @@ swift_library(
|
||||
"//submodules/Speak:Speak",
|
||||
"//submodules/UndoUI:UndoUI",
|
||||
"//submodules/InvisibleInkDustNode:InvisibleInkDustNode",
|
||||
"//submodules/Translate:Translate",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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))
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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? {
|
||||
|
@ -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 {
|
||||
|
@ -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,
|
||||
|
12
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Translate.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Translate.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "translate_24.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
127
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Translate.imageset/translate_24.pdf
vendored
Normal file
127
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Translate.imageset/translate_24.pdf
vendored
Normal 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
|
@ -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 {
|
||||
|
@ -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)
|
||||
|
@ -893,7 +893,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
}
|
||||
}
|
||||
|
||||
if item.associatedData.isCopyProtectionEnabled {
|
||||
if item.associatedData.isCopyProtectionEnabled || item.message.isCopyProtected() {
|
||||
needsShareButton = false
|
||||
}
|
||||
}
|
||||
|
@ -1161,7 +1161,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
||||
}
|
||||
}
|
||||
|
||||
if item.associatedData.isCopyProtectionEnabled {
|
||||
if item.associatedData.isCopyProtectionEnabled || item.message.isCopyProtected() {
|
||||
needsShareButton = false
|
||||
}
|
||||
}
|
||||
|
@ -351,7 +351,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD
|
||||
}
|
||||
}
|
||||
|
||||
if item.associatedData.isCopyProtectionEnabled {
|
||||
if item.associatedData.isCopyProtectionEnabled || item.message.isCopyProtected() {
|
||||
needsShareButton = false
|
||||
}
|
||||
}
|
||||
|
@ -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)))
|
||||
|
||||
|
@ -436,7 +436,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
|
||||
}
|
||||
}
|
||||
|
||||
if item.associatedData.isCopyProtectionEnabled {
|
||||
if item.associatedData.isCopyProtectionEnabled || item.message.isCopyProtected() {
|
||||
needsShareButton = false
|
||||
}
|
||||
}
|
||||
|
@ -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) {
|
||||
|
1322
submodules/TelegramUI/Sources/ChatQrCodeScreen.swift
Normal file
1322
submodules/TelegramUI/Sources/ChatQrCodeScreen.swift
Normal file
File diff suppressed because it is too large
Load Diff
@ -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
|
||||
|
@ -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?
|
||||
|
@ -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))
|
||||
|
@ -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?
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
|
19
submodules/Translate/BUILD
Normal file
19
submodules/Translate/BUILD
Normal 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",
|
||||
],
|
||||
)
|
27
submodules/Translate/Sources/Translate.swift
Normal file
27
submodules/Translate/Sources/Translate.swift
Normal 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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]
|
||||
}
|
||||
|
@ -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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user