mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-11-15 21:23:58 +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";
|
"ChatList.Archive" = "Archive";
|
||||||
|
|
||||||
"TextFormat.Spoiler" = "Spoiler";
|
"TextFormat.Spoiler" = "Spoiler";
|
||||||
|
|
||||||
|
"Conversation.ContextMenuTranslate" = "Translate";
|
||||||
|
|
||||||
|
"ClearCache.ClearDescription" = "All media will stay in the Telegram cloud and can be re-downloaded if you need it again.";
|
||||||
|
|||||||
@ -61,6 +61,7 @@ swift_library(
|
|||||||
"//submodules/TelegramStringFormatting:TelegramStringFormatting",
|
"//submodules/TelegramStringFormatting:TelegramStringFormatting",
|
||||||
"//submodules/TelegramCallsUI:TelegramCallsUI",
|
"//submodules/TelegramCallsUI:TelegramCallsUI",
|
||||||
"//submodules/StickerResources:StickerResources",
|
"//submodules/StickerResources:StickerResources",
|
||||||
|
"//submodules/TextFormat:TextFormat",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
|||||||
@ -17,6 +17,8 @@ import PhotoResources
|
|||||||
import ChatListSearchItemNode
|
import ChatListSearchItemNode
|
||||||
import ContextUI
|
import ContextUI
|
||||||
import ChatInterfaceState
|
import ChatInterfaceState
|
||||||
|
import TextFormat
|
||||||
|
import InvisibleInkDustNode
|
||||||
|
|
||||||
public enum ChatListItemContent {
|
public enum ChatListItemContent {
|
||||||
public final class DraftState: Equatable {
|
public final class DraftState: Equatable {
|
||||||
@ -427,6 +429,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||||||
let measureNode: TextNode
|
let measureNode: TextNode
|
||||||
private var currentItemHeight: CGFloat?
|
private var currentItemHeight: CGFloat?
|
||||||
let textNode: TextNode
|
let textNode: TextNode
|
||||||
|
var dustNode: InvisibleInkDustNode?
|
||||||
let inputActivitiesNode: ChatListInputActivitiesNode
|
let inputActivitiesNode: ChatListInputActivitiesNode
|
||||||
let dateNode: TextNode
|
let dateNode: TextNode
|
||||||
let separatorNode: ASDisplayNode
|
let separatorNode: ASDisplayNode
|
||||||
@ -1049,12 +1052,26 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||||||
attributedText = NSAttributedString(string: foldLineBreaks(draftText.replacingOccurrences(of: "\n\n", with: " ")), font: textFont, textColor: theme.messageTextColor)
|
attributedText = NSAttributedString(string: foldLineBreaks(draftText.replacingOccurrences(of: "\n\n", with: " ")), font: textFont, textColor: theme.messageTextColor)
|
||||||
} else if let message = messages.last {
|
} else if let message = messages.last {
|
||||||
var composedString: NSMutableAttributedString
|
var composedString: NSMutableAttributedString
|
||||||
|
|
||||||
|
let entities = (message._asMessage().textEntitiesAttribute?.entities ?? []).filter { entity in
|
||||||
|
if case .Spoiler = entity.type {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let messageString: NSAttributedString
|
||||||
|
if !message.text.isEmpty {
|
||||||
|
messageString = stringWithAppliedEntities(message.text, entities: entities, baseColor: theme.messageTextColor, linkColor: theme.messageTextColor, baseFont: textFont, linkFont: textFont, boldFont: textFont, italicFont: textFont, boldItalicFont: textFont, fixedFont: textFont, blockQuoteFont: textFont, underlineLinks: false)
|
||||||
|
} else {
|
||||||
|
messageString = NSAttributedString(string: messageText, font: textFont, textColor: theme.messageTextColor)
|
||||||
|
}
|
||||||
if let inlineAuthorPrefix = inlineAuthorPrefix {
|
if let inlineAuthorPrefix = inlineAuthorPrefix {
|
||||||
composedString = NSMutableAttributedString()
|
composedString = NSMutableAttributedString()
|
||||||
composedString.append(NSAttributedString(string: "\(inlineAuthorPrefix): ", font: textFont, textColor: theme.titleColor))
|
composedString.append(NSAttributedString(string: "\(inlineAuthorPrefix): ", font: textFont, textColor: theme.titleColor))
|
||||||
composedString.append(NSAttributedString(string: messageText, font: textFont, textColor: theme.messageTextColor))
|
composedString.append(messageString)
|
||||||
} else {
|
} else {
|
||||||
composedString = NSMutableAttributedString(string: messageText, font: textFont, textColor: theme.messageTextColor)
|
composedString = NSMutableAttributedString(attributedString: messageString)
|
||||||
}
|
}
|
||||||
|
|
||||||
if let searchQuery = item.interaction.searchTextHighightState {
|
if let searchQuery = item.interaction.searchTextHighightState {
|
||||||
@ -1395,7 +1412,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||||||
textCutout = TextNodeCutout(topLeft: CGSize(width: textLeftCutout, height: 10.0), topRight: nil, bottomRight: nil)
|
textCutout = TextNodeCutout(topLeft: CGSize(width: textLeftCutout, height: 10.0), topRight: nil, bottomRight: nil)
|
||||||
}
|
}
|
||||||
let (textLayout, textApply) = textLayout(TextNodeLayoutArguments(attributedString: textAttributedString, backgroundColor: nil, maximumNumberOfLines: authorAttributedString == nil ? 2 : 1, truncationType: .end, constrainedSize: CGSize(width: rawContentWidth - badgeSize, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: textCutout, insets: UIEdgeInsets(top: 2.0, left: 1.0, bottom: 2.0, right: 1.0)))
|
let (textLayout, textApply) = textLayout(TextNodeLayoutArguments(attributedString: textAttributedString, backgroundColor: nil, maximumNumberOfLines: authorAttributedString == nil ? 2 : 1, truncationType: .end, constrainedSize: CGSize(width: rawContentWidth - badgeSize, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: textCutout, insets: UIEdgeInsets(top: 2.0, left: 1.0, bottom: 2.0, right: 1.0)))
|
||||||
|
|
||||||
let titleRectWidth = rawContentWidth - dateLayout.size.width - 10.0 - statusWidth - titleIconsWidth
|
let titleRectWidth = rawContentWidth - dateLayout.size.width - 10.0 - statusWidth - titleIconsWidth
|
||||||
let (titleLayout, titleApply) = titleLayout(TextNodeLayoutArguments(attributedString: titleAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: titleRectWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
let (titleLayout, titleApply) = titleLayout(TextNodeLayoutArguments(attributedString: titleAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: titleRectWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||||
|
|
||||||
@ -1730,6 +1747,24 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||||||
let textNodeFrame = CGRect(origin: CGPoint(x: contentRect.origin.x, y: contentRect.minY + titleLayout.size.height - 1.0 + UIScreenPixel + (authorLayout.size.height.isZero ? 0.0 : (authorLayout.size.height - 3.0))), size: textLayout.size)
|
let textNodeFrame = CGRect(origin: CGPoint(x: contentRect.origin.x, y: contentRect.minY + titleLayout.size.height - 1.0 + UIScreenPixel + (authorLayout.size.height.isZero ? 0.0 : (authorLayout.size.height - 3.0))), size: textLayout.size)
|
||||||
strongSelf.textNode.frame = textNodeFrame
|
strongSelf.textNode.frame = textNodeFrame
|
||||||
|
|
||||||
|
if !textLayout.spoilers.isEmpty {
|
||||||
|
let dustNode: InvisibleInkDustNode
|
||||||
|
if let current = strongSelf.dustNode {
|
||||||
|
dustNode = current
|
||||||
|
} else {
|
||||||
|
dustNode = InvisibleInkDustNode(textNode: nil)
|
||||||
|
dustNode.isUserInteractionEnabled = false
|
||||||
|
strongSelf.dustNode = dustNode
|
||||||
|
strongSelf.contextContainer.insertSubnode(dustNode, aboveSubnode: strongSelf.textNode)
|
||||||
|
}
|
||||||
|
dustNode.update(size: textNodeFrame.size, color: theme.messageTextColor, rects: textLayout.spoilers.map { $0.offsetBy(dx: 3.0, dy: 3.0).insetBy(dx: 0.0, dy: 1.0) })
|
||||||
|
dustNode.frame = textNodeFrame.insetBy(dx: -3.0, dy: -3.0).offsetBy(dx: 0.0, dy: 3.0)
|
||||||
|
|
||||||
|
} else if let dustNode = strongSelf.dustNode {
|
||||||
|
strongSelf.dustNode = nil
|
||||||
|
dustNode.removeFromSupernode()
|
||||||
|
}
|
||||||
|
|
||||||
var animateInputActivitiesFrame = false
|
var animateInputActivitiesFrame = false
|
||||||
let inputActivities = inputActivities?.filter({
|
let inputActivities = inputActivities?.filter({
|
||||||
switch $0.1 {
|
switch $0.1 {
|
||||||
|
|||||||
@ -81,6 +81,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
|||||||
case enableDebugDataDisplay(Bool)
|
case enableDebugDataDisplay(Bool)
|
||||||
case acceleratedStickers(Bool)
|
case acceleratedStickers(Bool)
|
||||||
case experimentalBackground(Bool)
|
case experimentalBackground(Bool)
|
||||||
|
case snow(Bool)
|
||||||
case playerEmbedding(Bool)
|
case playerEmbedding(Bool)
|
||||||
case playlistPlayback(Bool)
|
case playlistPlayback(Bool)
|
||||||
case voiceConference
|
case voiceConference
|
||||||
@ -102,7 +103,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
|||||||
return DebugControllerSection.logging.rawValue
|
return DebugControllerSection.logging.rawValue
|
||||||
case .enableRaiseToSpeak, .keepChatNavigationStack, .skipReadHistory, .crashOnSlowQueries:
|
case .enableRaiseToSpeak, .keepChatNavigationStack, .skipReadHistory, .crashOnSlowQueries:
|
||||||
return DebugControllerSection.experiments.rawValue
|
return DebugControllerSection.experiments.rawValue
|
||||||
case .clearTips, .crash, .resetData, .resetDatabase, .resetDatabaseAndCache, .resetHoles, .reindexUnread, .resetBiometricsData, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .playerEmbedding, .playlistPlayback, .voiceConference, .experimentalCompatibility, .enableDebugDataDisplay, .acceleratedStickers, .experimentalBackground:
|
case .clearTips, .crash, .resetData, .resetDatabase, .resetDatabaseAndCache, .resetHoles, .reindexUnread, .resetBiometricsData, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .playerEmbedding, .playlistPlayback, .voiceConference, .experimentalCompatibility, .enableDebugDataDisplay, .acceleratedStickers, .experimentalBackground, .snow:
|
||||||
return DebugControllerSection.experiments.rawValue
|
return DebugControllerSection.experiments.rawValue
|
||||||
case .preferredVideoCodec:
|
case .preferredVideoCodec:
|
||||||
return DebugControllerSection.videoExperiments.rawValue
|
return DebugControllerSection.videoExperiments.rawValue
|
||||||
@ -173,14 +174,16 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
|||||||
return 29
|
return 29
|
||||||
case .experimentalBackground:
|
case .experimentalBackground:
|
||||||
return 30
|
return 30
|
||||||
case .playerEmbedding:
|
case .snow:
|
||||||
return 31
|
return 31
|
||||||
case .playlistPlayback:
|
case .playerEmbedding:
|
||||||
return 32
|
return 32
|
||||||
case .voiceConference:
|
case .playlistPlayback:
|
||||||
return 33
|
return 33
|
||||||
|
case .voiceConference:
|
||||||
|
return 34
|
||||||
case let .preferredVideoCodec(index, _, _, _):
|
case let .preferredVideoCodec(index, _, _, _):
|
||||||
return 34 + index
|
return 35 + index
|
||||||
case .disableVideoAspectScaling:
|
case .disableVideoAspectScaling:
|
||||||
return 100
|
return 100
|
||||||
case .enableVoipTcp:
|
case .enableVoipTcp:
|
||||||
@ -814,6 +817,16 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
|||||||
})
|
})
|
||||||
}).start()
|
}).start()
|
||||||
})
|
})
|
||||||
|
case let .snow(value):
|
||||||
|
return ItemListSwitchItem(presentationData: presentationData, title: "Snow", value: value, sectionId: self.section, style: .blocks, updated: { value in
|
||||||
|
let _ = arguments.sharedContext.accountManager.transaction ({ transaction in
|
||||||
|
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in
|
||||||
|
var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings
|
||||||
|
settings.snow = value
|
||||||
|
return PreferencesEntry(settings)
|
||||||
|
})
|
||||||
|
}).start()
|
||||||
|
})
|
||||||
case let .playerEmbedding(value):
|
case let .playerEmbedding(value):
|
||||||
return ItemListSwitchItem(presentationData: presentationData, title: "Player Embedding", value: value, sectionId: self.section, style: .blocks, updated: { value in
|
return ItemListSwitchItem(presentationData: presentationData, title: "Player Embedding", value: value, sectionId: self.section, style: .blocks, updated: { value in
|
||||||
let _ = arguments.sharedContext.accountManager.transaction ({ transaction in
|
let _ = arguments.sharedContext.accountManager.transaction ({ transaction in
|
||||||
@ -927,6 +940,7 @@ private func debugControllerEntries(sharedContext: SharedAccountContext, present
|
|||||||
entries.append(.enableDebugDataDisplay(experimentalSettings.enableDebugDataDisplay))
|
entries.append(.enableDebugDataDisplay(experimentalSettings.enableDebugDataDisplay))
|
||||||
entries.append(.acceleratedStickers(experimentalSettings.acceleratedStickers))
|
entries.append(.acceleratedStickers(experimentalSettings.acceleratedStickers))
|
||||||
entries.append(.experimentalBackground(experimentalSettings.experimentalBackground))
|
entries.append(.experimentalBackground(experimentalSettings.experimentalBackground))
|
||||||
|
entries.append(.snow(experimentalSettings.snow))
|
||||||
entries.append(.playerEmbedding(experimentalSettings.playerEmbedding))
|
entries.append(.playerEmbedding(experimentalSettings.playerEmbedding))
|
||||||
entries.append(.playlistPlayback(experimentalSettings.playlistPlayback))
|
entries.append(.playlistPlayback(experimentalSettings.playlistPlayback))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1005,6 +1005,10 @@ public class TextNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let lineRange = CFRange(location: lastLineCharacterIndex, length: stringLength - lastLineCharacterIndex)
|
let lineRange = CFRange(location: lastLineCharacterIndex, length: stringLength - lastLineCharacterIndex)
|
||||||
|
var brokenLineRange = CFRange(location: lastLineCharacterIndex, length: lineCharacterCount)
|
||||||
|
if brokenLineRange.location + brokenLineRange.length > attributedString.length {
|
||||||
|
brokenLineRange.length = attributedString.length - brokenLineRange.location
|
||||||
|
}
|
||||||
if lineRange.length == 0 {
|
if lineRange.length == 0 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -1033,7 +1037,7 @@ public class TextNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var headIndent: CGFloat = 0.0
|
var headIndent: CGFloat = 0.0
|
||||||
attributedString.enumerateAttributes(in: NSMakeRange(lineRange.location, lineRange.length), options: []) { attributes, range, _ in
|
attributedString.enumerateAttributes(in: NSMakeRange(brokenLineRange.location, brokenLineRange.length), options: []) { attributes, range, _ in
|
||||||
if let _ = attributes[NSAttributedString.Key.init(rawValue: "TelegramSpoiler")] {
|
if let _ = attributes[NSAttributedString.Key.init(rawValue: "TelegramSpoiler")] {
|
||||||
var ascent: CGFloat = 0.0
|
var ascent: CGFloat = 0.0
|
||||||
var descent: CGFloat = 0.0
|
var descent: CGFloat = 0.0
|
||||||
|
|||||||
@ -39,6 +39,7 @@ swift_library(
|
|||||||
"//submodules/Speak:Speak",
|
"//submodules/Speak:Speak",
|
||||||
"//submodules/UndoUI:UndoUI",
|
"//submodules/UndoUI:UndoUI",
|
||||||
"//submodules/InvisibleInkDustNode:InvisibleInkDustNode",
|
"//submodules/InvisibleInkDustNode:InvisibleInkDustNode",
|
||||||
|
"//submodules/Translate:Translate",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
|||||||
@ -16,6 +16,7 @@ import PresentationDataUtils
|
|||||||
import ImageContentAnalysis
|
import ImageContentAnalysis
|
||||||
import TextSelectionNode
|
import TextSelectionNode
|
||||||
import Speak
|
import Speak
|
||||||
|
import Translate
|
||||||
import ShareController
|
import ShareController
|
||||||
import UndoUI
|
import UndoUI
|
||||||
|
|
||||||
@ -352,6 +353,8 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||||||
}
|
}
|
||||||
case .speak:
|
case .speak:
|
||||||
speakText(string)
|
speakText(string)
|
||||||
|
case .translate:
|
||||||
|
translateText(context: strongSelf.context, text: string)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
recognizedContentNode.barcodeAction = { [weak self] payload, rect in
|
recognizedContentNode.barcodeAction = { [weak self] payload, rect in
|
||||||
|
|||||||
@ -203,6 +203,7 @@ public enum RecognizedTextSelectionAction {
|
|||||||
case share
|
case share
|
||||||
case lookup
|
case lookup
|
||||||
case speak
|
case speak
|
||||||
|
case translate
|
||||||
}
|
}
|
||||||
|
|
||||||
public final class RecognizedTextSelectionNode: ASDisplayNode {
|
public final class RecognizedTextSelectionNode: ASDisplayNode {
|
||||||
@ -509,6 +510,12 @@ public final class RecognizedTextSelectionNode: ASDisplayNode {
|
|||||||
self?.performAction(selectedText, .lookup)
|
self?.performAction(selectedText, .lookup)
|
||||||
let _ = self?.dismissSelection()
|
let _ = self?.dismissSelection()
|
||||||
}))
|
}))
|
||||||
|
// if #available(iOS 15.0, *) {
|
||||||
|
// actions.append(ContextMenuAction(content: .text(title: self.strings.Conversation_ContextMenuTranslate, accessibilityLabel: self.strings.Conversation_ContextMenuTranslate), action: { [weak self] in
|
||||||
|
// self?.performAction(selectedText, .translate)
|
||||||
|
// let _ = self?.dismissSelection()
|
||||||
|
// }))
|
||||||
|
// }
|
||||||
if isSpeakSelectionEnabled() {
|
if isSpeakSelectionEnabled() {
|
||||||
actions.append(ContextMenuAction(content: .text(title: self.strings.Conversation_ContextMenuSpeak, accessibilityLabel: self.strings.Conversation_ContextMenuSpeak), action: { [weak self] in
|
actions.append(ContextMenuAction(content: .text(title: self.strings.Conversation_ContextMenuSpeak, accessibilityLabel: self.strings.Conversation_ContextMenuSpeak), action: { [weak self] in
|
||||||
self?.performAction(selectedText, .speak)
|
self?.performAction(selectedText, .speak)
|
||||||
|
|||||||
@ -62,7 +62,7 @@ public class InvisibleInkDustNode: ASDisplayNode {
|
|||||||
|
|
||||||
public var isRevealed = false
|
public var isRevealed = false
|
||||||
|
|
||||||
public init(textNode: TextNode) {
|
public init(textNode: TextNode?) {
|
||||||
self.textNode = textNode
|
self.textNode = textNode
|
||||||
|
|
||||||
self.emitterNode = ASDisplayNode()
|
self.emitterNode = ASDisplayNode()
|
||||||
@ -144,7 +144,7 @@ public class InvisibleInkDustNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@objc private func tap(_ gestureRecognizer: UITapGestureRecognizer) {
|
@objc private func tap(_ gestureRecognizer: UITapGestureRecognizer) {
|
||||||
guard let (size, _, _) = self.currentParams, !self.isRevealed else {
|
guard let (size, _, _) = self.currentParams, let textNode = self.textNode, !self.isRevealed else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -155,15 +155,15 @@ public class InvisibleInkDustNode: ASDisplayNode {
|
|||||||
self.emitterLayer?.setValue(position, forKeyPath: "emitterBehaviors.fingerAttractor.position")
|
self.emitterLayer?.setValue(position, forKeyPath: "emitterBehaviors.fingerAttractor.position")
|
||||||
|
|
||||||
Queue.mainQueue().after(0.1 * UIView.animationDurationFactor()) {
|
Queue.mainQueue().after(0.1 * UIView.animationDurationFactor()) {
|
||||||
self.textNode?.view.mask = self.textMaskNode.view
|
textNode.view.mask = self.textMaskNode.view
|
||||||
self.textNode?.alpha = 1.0
|
textNode.alpha = 1.0
|
||||||
|
|
||||||
let radius = max(size.width, size.height)
|
let radius = max(size.width, size.height)
|
||||||
self.textSpotNode.frame = CGRect(x: position.x - radius / 2.0, y: position.y - radius / 2.0, width: radius, height: radius)
|
self.textSpotNode.frame = CGRect(x: position.x - radius / 2.0, y: position.y - radius / 2.0, width: radius, height: radius)
|
||||||
|
|
||||||
self.textSpotNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
|
self.textSpotNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
|
||||||
self.textSpotNode.layer.animateScale(from: 0.1, to: 3.5, duration: 0.71, removeOnCompletion: false, completion: { [weak self] _ in
|
self.textSpotNode.layer.animateScale(from: 0.1, to: 3.5, duration: 0.71, removeOnCompletion: false, completion: { _ in
|
||||||
self?.textNode?.view.mask = nil
|
textNode.view.mask = nil
|
||||||
})
|
})
|
||||||
|
|
||||||
self.emitterNode.view.mask = self.emitterMaskNode.view
|
self.emitterNode.view.mask = self.emitterMaskNode.view
|
||||||
@ -187,7 +187,7 @@ public class InvisibleInkDustNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
let textLength = CGFloat((self.textNode?.cachedLayout?.attributedString?.string ?? "").count)
|
let textLength = CGFloat((textNode.cachedLayout?.attributedString?.string ?? "").count)
|
||||||
let timeToRead = min(45.0, ceil(max(4.0, textLength * 0.04)))
|
let timeToRead = min(45.0, ceil(max(4.0, textLength * 0.04)))
|
||||||
Queue.mainQueue().after(timeToRead * UIView.animationDurationFactor()) {
|
Queue.mainQueue().after(timeToRead * UIView.animationDurationFactor()) {
|
||||||
self.isRevealed = false
|
self.isRevealed = false
|
||||||
@ -195,9 +195,7 @@ public class InvisibleInkDustNode: ASDisplayNode {
|
|||||||
|
|
||||||
let transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .linear)
|
let transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .linear)
|
||||||
transition.updateAlpha(node: self, alpha: 1.0)
|
transition.updateAlpha(node: self, alpha: 1.0)
|
||||||
if let textNode = self.textNode {
|
transition.updateAlpha(node: textNode, alpha: 0.0)
|
||||||
transition.updateAlpha(node: textNode, alpha: 0.0)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -267,8 +267,7 @@ public final class QrCodeScreen: ViewController {
|
|||||||
self.qrImageNode.clipsToBounds = true
|
self.qrImageNode.clipsToBounds = true
|
||||||
self.qrImageNode.cornerRadius = 16.0
|
self.qrImageNode.cornerRadius = 16.0
|
||||||
|
|
||||||
self.qrIconNode = AnimatedStickerNode()
|
self.qrIconNode = AnimatedStickerNode()
|
||||||
|
|
||||||
self.qrIconNode.setup(source: AnimatedStickerNodeLocalFileSource(name: "PlaneLogo"), width: 240, height: 240, mode: .direct(cachePathPrefix: nil))
|
self.qrIconNode.setup(source: AnimatedStickerNodeLocalFileSource(name: "PlaneLogo"), width: 240, height: 240, mode: .direct(cachePathPrefix: nil))
|
||||||
self.qrIconNode.visibility = true
|
self.qrIconNode.visibility = true
|
||||||
|
|
||||||
@ -434,7 +433,6 @@ public final class QrCodeScreen: ViewController {
|
|||||||
let imageSide: CGFloat = 240.0
|
let imageSide: CGFloat = 240.0
|
||||||
let imageSize = CGSize(width: imageSide, height: imageSide)
|
let imageSize = CGSize(width: imageSide, height: imageSide)
|
||||||
let imageApply = makeImageLayout(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets(), emptyColor: nil))
|
let imageApply = makeImageLayout(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets(), emptyColor: nil))
|
||||||
|
|
||||||
let _ = imageApply()
|
let _ = imageApply()
|
||||||
|
|
||||||
let width = horizontalContainerFillingSizeForLayout(layout: layout, sideInset: 0.0)
|
let width = horizontalContainerFillingSizeForLayout(layout: layout, sideInset: 0.0)
|
||||||
|
|||||||
@ -521,6 +521,8 @@ public func storageUsageController(context: AccountContext, cacheUsagePromise: P
|
|||||||
|
|
||||||
var totalSize: Int64 = 0
|
var totalSize: Int64 = 0
|
||||||
|
|
||||||
|
items.append(ActionSheetTextItem(title: presentationData.strings.ClearCache_ClearDescription))
|
||||||
|
|
||||||
for categoryId in validCategories {
|
for categoryId in validCategories {
|
||||||
if let (_, size) = sizeIndex[categoryId] {
|
if let (_, size) = sizeIndex[categoryId] {
|
||||||
let categorySize: Int64 = size
|
let categorySize: Int64 = size
|
||||||
|
|||||||
@ -150,7 +150,7 @@ class RecentSessionsHeaderItemNode: ListViewItemNode {
|
|||||||
|
|
||||||
strongSelf.buttonNode.title = item.context.sharedContext.currentPresentationData.with { $0 }.strings.AuthSessions_LinkDesktopDevice
|
strongSelf.buttonNode.title = item.context.sharedContext.currentPresentationData.with { $0 }.strings.AuthSessions_LinkDesktopDevice
|
||||||
if let _ = updatedTheme {
|
if let _ = updatedTheme {
|
||||||
strongSelf.buttonNode.icon = generateTintedImage(image: UIImage(bundleImageName: "Settings/QrButtonIcon"), color: .white)
|
strongSelf.buttonNode.icon = UIImage(bundleImageName: "Settings/QrButtonIcon")
|
||||||
strongSelf.buttonNode.updateTheme(SolidRoundedButtonTheme(theme: item.theme))
|
strongSelf.buttonNode.updateTheme(SolidRoundedButtonTheme(theme: item.theme))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -69,7 +69,7 @@ public final class SolidRoundedButtonNode: ASDisplayNode {
|
|||||||
|
|
||||||
public var icon: UIImage? {
|
public var icon: UIImage? {
|
||||||
didSet {
|
didSet {
|
||||||
self.iconNode.image = generateTintedImage(image: self.iconNode.image, color: self.theme.foregroundColor)
|
self.iconNode.image = generateTintedImage(image: self.icon, color: self.theme.foregroundColor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -322,6 +322,15 @@ public extension Message {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var textEntitiesAttribute: TextEntitiesMessageAttribute? {
|
||||||
|
for attribute in self.attributes {
|
||||||
|
if let attribute = attribute as? TextEntitiesMessageAttribute {
|
||||||
|
return attribute
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func _internal_parseMediaAttachment(data: Data) -> Media? {
|
public func _internal_parseMediaAttachment(data: Data) -> Media? {
|
||||||
|
|||||||
@ -229,12 +229,13 @@ public func stringForMediaKind(_ kind: MessageContentKind, strings: Presentation
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func descriptionStringForMessage(contentSettings: ContentSettings, message: EngineMessage, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, dateTimeFormat: PresentationDateTimeFormat, accountPeerId: EnginePeer.Id) -> (String, Bool) {
|
public func descriptionStringForMessage(contentSettings: ContentSettings, message: EngineMessage, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, dateTimeFormat: PresentationDateTimeFormat, accountPeerId: EnginePeer.Id) -> (String, Bool, Bool) {
|
||||||
let contentKind = messageContentKind(contentSettings: contentSettings, message: message, strings: strings, nameDisplayOrder: nameDisplayOrder, dateTimeFormat: dateTimeFormat, accountPeerId: accountPeerId)
|
let contentKind = messageContentKind(contentSettings: contentSettings, message: message, strings: strings, nameDisplayOrder: nameDisplayOrder, dateTimeFormat: dateTimeFormat, accountPeerId: accountPeerId)
|
||||||
if !message.text.isEmpty && ![.expiredImage, .expiredVideo].contains(contentKind.key) {
|
if !message.text.isEmpty && ![.expiredImage, .expiredVideo].contains(contentKind.key) {
|
||||||
return (foldLineBreaks(message.text), false)
|
return (foldLineBreaks(message.text), false, true)
|
||||||
}
|
}
|
||||||
return stringForMediaKind(contentKind, strings: strings)
|
let result = stringForMediaKind(contentKind, strings: strings)
|
||||||
|
return (result.0, result.1, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func foldLineBreaks(_ text: String) -> String {
|
public func foldLineBreaks(_ text: String) -> String {
|
||||||
|
|||||||
@ -251,6 +251,7 @@ swift_library(
|
|||||||
"//submodules/InvisibleInkDustNode:InvisibleInkDustNode",
|
"//submodules/InvisibleInkDustNode:InvisibleInkDustNode",
|
||||||
"//submodules/QrCodeUI:QrCodeUI",
|
"//submodules/QrCodeUI:QrCodeUI",
|
||||||
"//submodules/Components/ReactionListContextMenuContent:ReactionListContextMenuContent",
|
"//submodules/Components/ReactionListContextMenuContent:ReactionListContextMenuContent",
|
||||||
|
"//submodules/Translate:Translate",
|
||||||
] + select({
|
] + select({
|
||||||
"@build_bazel_rules_apple//apple:ios_armv7": [],
|
"@build_bazel_rules_apple//apple:ios_armv7": [],
|
||||||
"@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets,
|
"@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets,
|
||||||
|
|||||||
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 Markdown
|
||||||
import TelegramPermissionsUI
|
import TelegramPermissionsUI
|
||||||
import Speak
|
import Speak
|
||||||
|
import Translate
|
||||||
import UniversalMediaPlayer
|
import UniversalMediaPlayer
|
||||||
import WallpaperBackgroundNode
|
import WallpaperBackgroundNode
|
||||||
import ChatListUI
|
import ChatListUI
|
||||||
@ -2759,6 +2760,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
}
|
}
|
||||||
case .speak:
|
case .speak:
|
||||||
speakText(text.string)
|
speakText(text.string)
|
||||||
|
case .translate:
|
||||||
|
translateText(context: context, text: text.string)
|
||||||
}
|
}
|
||||||
}, displayImportedMessageTooltip: { [weak self] _ in
|
}, displayImportedMessageTooltip: { [weak self] _ in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
|
|||||||
@ -760,6 +760,15 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
|
|||||||
f(.default)
|
f(.default)
|
||||||
})))
|
})))
|
||||||
|
|
||||||
|
if #available(iOS 15.0, *) {
|
||||||
|
actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_ContextMenuTranslate, icon: { theme in
|
||||||
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Translate"), color: theme.actionSheet.primaryTextColor)
|
||||||
|
}, action: { _, f in
|
||||||
|
controllerInteraction.performTextSelectionAction(0, NSAttributedString(string: message.text), .translate)
|
||||||
|
f(.default)
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
|
||||||
if isSpeakSelectionEnabled() && !message.text.isEmpty {
|
if isSpeakSelectionEnabled() && !message.text.isEmpty {
|
||||||
actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_ContextMenuSpeak, icon: { theme in
|
actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_ContextMenuSpeak, icon: { theme in
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Message"), color: theme.actionSheet.primaryTextColor)
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Message"), color: theme.actionSheet.primaryTextColor)
|
||||||
|
|||||||
@ -893,7 +893,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if item.associatedData.isCopyProtectionEnabled {
|
if item.associatedData.isCopyProtectionEnabled || item.message.isCopyProtected() {
|
||||||
needsShareButton = false
|
needsShareButton = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1161,7 +1161,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if item.associatedData.isCopyProtectionEnabled {
|
if item.associatedData.isCopyProtectionEnabled || item.message.isCopyProtected() {
|
||||||
needsShareButton = false
|
needsShareButton = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -351,7 +351,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if item.associatedData.isCopyProtectionEnabled {
|
if item.associatedData.isCopyProtectionEnabled || item.message.isCopyProtected() {
|
||||||
needsShareButton = false
|
needsShareButton = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,6 +10,8 @@ import AccountContext
|
|||||||
import LocalizedPeerData
|
import LocalizedPeerData
|
||||||
import PhotoResources
|
import PhotoResources
|
||||||
import TelegramStringFormatting
|
import TelegramStringFormatting
|
||||||
|
import TextFormat
|
||||||
|
import InvisibleInkDustNode
|
||||||
|
|
||||||
enum ChatMessageReplyInfoType {
|
enum ChatMessageReplyInfoType {
|
||||||
case bubble(incoming: Bool)
|
case bubble(incoming: Bool)
|
||||||
@ -21,6 +23,7 @@ class ChatMessageReplyInfoNode: ASDisplayNode {
|
|||||||
private let lineNode: ASImageNode
|
private let lineNode: ASImageNode
|
||||||
private var titleNode: TextNode?
|
private var titleNode: TextNode?
|
||||||
private var textNode: TextNode?
|
private var textNode: TextNode?
|
||||||
|
private var dustNode: InvisibleInkDustNode?
|
||||||
private var imageNode: TransformImageNode?
|
private var imageNode: TransformImageNode?
|
||||||
private var previousMediaReference: AnyMediaReference?
|
private var previousMediaReference: AnyMediaReference?
|
||||||
|
|
||||||
@ -64,7 +67,7 @@ class ChatMessageReplyInfoNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let (textString, isMedia) = descriptionStringForMessage(contentSettings: context.currentContentSettings.with { $0 }, message: EngineMessage(message), strings: strings, nameDisplayOrder: presentationData.nameDisplayOrder, dateTimeFormat: presentationData.dateTimeFormat, accountPeerId: context.account.peerId)
|
let (textString, isMedia, isText) = descriptionStringForMessage(contentSettings: context.currentContentSettings.with { $0 }, message: EngineMessage(message), strings: strings, nameDisplayOrder: presentationData.nameDisplayOrder, dateTimeFormat: presentationData.dateTimeFormat, accountPeerId: context.account.peerId)
|
||||||
|
|
||||||
let placeholderColor: UIColor = message.effectivelyIncoming(context.account.peerId) ? presentationData.theme.theme.chat.message.incoming.mediaPlaceholderColor : presentationData.theme.theme.chat.message.outgoing.mediaPlaceholderColor
|
let placeholderColor: UIColor = message.effectivelyIncoming(context.account.peerId) ? presentationData.theme.theme.chat.message.incoming.mediaPlaceholderColor : presentationData.theme.theme.chat.message.outgoing.mediaPlaceholderColor
|
||||||
let titleColor: UIColor
|
let titleColor: UIColor
|
||||||
@ -89,6 +92,21 @@ class ChatMessageReplyInfoNode: ASDisplayNode {
|
|||||||
textColor = titleColor
|
textColor = titleColor
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
let messageText: NSAttributedString
|
||||||
|
if isText {
|
||||||
|
let entities = (message.textEntitiesAttribute?.entities ?? []).filter { entity in
|
||||||
|
if case .Spoiler = entity.type {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
messageText = stringWithAppliedEntities(message.text, entities: entities, baseColor: textColor, linkColor: textColor, baseFont: textFont, linkFont: textFont, boldFont: textFont, italicFont: textFont, boldItalicFont: textFont, fixedFont: textFont, blockQuoteFont: textFont, underlineLinks: false)
|
||||||
|
} else {
|
||||||
|
messageText = NSAttributedString(string: textString, font: textFont, textColor: textColor)
|
||||||
|
}
|
||||||
|
|
||||||
var leftInset: CGFloat = 11.0
|
var leftInset: CGFloat = 11.0
|
||||||
let spacing: CGFloat = 2.0
|
let spacing: CGFloat = 2.0
|
||||||
|
|
||||||
@ -131,7 +149,7 @@ class ChatMessageReplyInfoNode: ASDisplayNode {
|
|||||||
let textInsets = UIEdgeInsets(top: 3.0, left: 0.0, bottom: 3.0, right: 0.0)
|
let textInsets = UIEdgeInsets(top: 3.0, left: 0.0, bottom: 3.0, right: 0.0)
|
||||||
|
|
||||||
let (titleLayout, titleApply) = titleNodeLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: titleString, font: titleFont, textColor: titleColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: contrainedTextSize, alignment: .natural, cutout: nil, insets: textInsets))
|
let (titleLayout, titleApply) = titleNodeLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: titleString, font: titleFont, textColor: titleColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: contrainedTextSize, alignment: .natural, cutout: nil, insets: textInsets))
|
||||||
let (textLayout, textApply) = textNodeLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: textString, font: textFont, textColor: textColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: contrainedTextSize, alignment: .natural, cutout: nil, insets: textInsets))
|
let (textLayout, textApply) = textNodeLayout(TextNodeLayoutArguments(attributedString: messageText, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: contrainedTextSize, alignment: .natural, cutout: nil, insets: textInsets))
|
||||||
|
|
||||||
let imageSide = titleLayout.size.height + textLayout.size.height - 16.0
|
let imageSide = titleLayout.size.height + textLayout.size.height - 16.0
|
||||||
|
|
||||||
@ -218,8 +236,27 @@ class ChatMessageReplyInfoNode: ASDisplayNode {
|
|||||||
node.imageNode?.captureProtected = message.isCopyProtected()
|
node.imageNode?.captureProtected = message.isCopyProtected()
|
||||||
|
|
||||||
titleNode.frame = CGRect(origin: CGPoint(x: leftInset - textInsets.left, y: spacing - textInsets.top), size: titleLayout.size)
|
titleNode.frame = CGRect(origin: CGPoint(x: leftInset - textInsets.left, y: spacing - textInsets.top), size: titleLayout.size)
|
||||||
textNode.frame = CGRect(origin: CGPoint(x: leftInset - textInsets.left, y: titleNode.frame.maxY - textInsets.bottom + spacing - textInsets.top), size: textLayout.size)
|
|
||||||
|
|
||||||
|
let textFrame = CGRect(origin: CGPoint(x: leftInset - textInsets.left, y: titleNode.frame.maxY - textInsets.bottom + spacing - textInsets.top), size: textLayout.size)
|
||||||
|
textNode.frame = textFrame
|
||||||
|
|
||||||
|
if !textLayout.spoilers.isEmpty {
|
||||||
|
let dustNode: InvisibleInkDustNode
|
||||||
|
if let current = node.dustNode {
|
||||||
|
dustNode = current
|
||||||
|
} else {
|
||||||
|
dustNode = InvisibleInkDustNode(textNode: nil)
|
||||||
|
dustNode.isUserInteractionEnabled = false
|
||||||
|
node.dustNode = dustNode
|
||||||
|
node.contentNode.insertSubnode(dustNode, aboveSubnode: textNode)
|
||||||
|
}
|
||||||
|
dustNode.update(size: textFrame.size, color: titleColor, rects: textLayout.spoilers.map { $0.offsetBy(dx: 3.0, dy: 3.0).insetBy(dx: 1.0, dy: 1.0) })
|
||||||
|
dustNode.frame = textFrame.insetBy(dx: -3.0, dy: -3.0).offsetBy(dx: 0.0, dy: 3.0)
|
||||||
|
} else if let dustNode = node.dustNode {
|
||||||
|
dustNode.removeFromSupernode()
|
||||||
|
node.dustNode = nil
|
||||||
|
}
|
||||||
|
|
||||||
node.lineNode.image = lineImage
|
node.lineNode.image = lineImage
|
||||||
node.lineNode.frame = CGRect(origin: CGPoint(x: 1.0, y: 3.0), size: CGSize(width: 2.0, height: max(0.0, size.height - 5.0)))
|
node.lineNode.frame = CGRect(origin: CGPoint(x: 1.0, y: 3.0), size: CGSize(width: 2.0, height: max(0.0, size.height - 5.0)))
|
||||||
|
|
||||||
|
|||||||
@ -436,7 +436,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if item.associatedData.isCopyProtectionEnabled {
|
if item.associatedData.isCopyProtectionEnabled || item.message.isCopyProtected() {
|
||||||
needsShareButton = false
|
needsShareButton = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -374,6 +374,7 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
if let (_, spoilerTextApply) = spoilerTextLayoutAndApply {
|
if let (_, spoilerTextApply) = spoilerTextLayoutAndApply {
|
||||||
let spoilerTextNode = spoilerTextApply()
|
let spoilerTextNode = spoilerTextApply()
|
||||||
if strongSelf.spoilerTextNode == nil {
|
if strongSelf.spoilerTextNode == nil {
|
||||||
|
spoilerTextNode.alpha = 0.0
|
||||||
spoilerTextNode.isUserInteractionEnabled = false
|
spoilerTextNode.isUserInteractionEnabled = false
|
||||||
spoilerTextNode.contentMode = .topLeft
|
spoilerTextNode.contentMode = .topLeft
|
||||||
spoilerTextNode.contentsScale = UIScreenScale
|
spoilerTextNode.contentsScale = UIScreenScale
|
||||||
@ -384,8 +385,6 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
strongSelf.spoilerTextNode?.frame = textFrame
|
strongSelf.spoilerTextNode?.frame = textFrame
|
||||||
strongSelf.spoilerTextNode?.isHidden = false
|
|
||||||
strongSelf.spoilerTextNode?.alpha = 0.0
|
|
||||||
|
|
||||||
let dustNode: InvisibleInkDustNode
|
let dustNode: InvisibleInkDustNode
|
||||||
if let current = strongSelf.dustNode {
|
if let current = strongSelf.dustNode {
|
||||||
@ -395,11 +394,16 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
strongSelf.dustNode = dustNode
|
strongSelf.dustNode = dustNode
|
||||||
strongSelf.insertSubnode(dustNode, aboveSubnode: spoilerTextNode)
|
strongSelf.insertSubnode(dustNode, aboveSubnode: spoilerTextNode)
|
||||||
}
|
}
|
||||||
dustNode.update(size: textFrame.size, color: messageTheme.primaryTextColor, rects: textLayout.spoilers.map { $0.offsetBy(dx: 3.0, dy: 3.0).insetBy(dx: 0.0, dy: 1.0) })
|
dustNode.update(size: textFrame.size, color: messageTheme.secondaryTextColor, rects: textLayout.spoilers.map { $0.offsetBy(dx: 3.0, dy: 3.0).insetBy(dx: 0.0, dy: 1.0) })
|
||||||
dustNode.frame = textFrame.insetBy(dx: -3.0, dy: -3.0).offsetBy(dx: 0.0, dy: 3.0)
|
dustNode.frame = textFrame.insetBy(dx: -3.0, dy: -3.0).offsetBy(dx: 0.0, dy: 3.0)
|
||||||
} else if let spoilerTextNode = strongSelf.spoilerTextNode {
|
} else if let spoilerTextNode = strongSelf.spoilerTextNode {
|
||||||
strongSelf.spoilerTextNode = nil
|
strongSelf.spoilerTextNode = nil
|
||||||
spoilerTextNode.removeFromSupernode()
|
spoilerTextNode.removeFromSupernode()
|
||||||
|
|
||||||
|
if let dustNode = strongSelf.dustNode {
|
||||||
|
strongSelf.dustNode = nil
|
||||||
|
dustNode.removeFromSupernode()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let textSelectionNode = strongSelf.textSelectionNode {
|
if let textSelectionNode = strongSelf.textSelectionNode {
|
||||||
@ -632,7 +636,7 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
|
|
||||||
override func updateIsExtractedToContextPreview(_ value: Bool) {
|
override func updateIsExtractedToContextPreview(_ value: Bool) {
|
||||||
if value {
|
if value {
|
||||||
if self.textSelectionNode == nil, let item = self.item, !item.associatedData.isCopyProtectionEnabled, let rootNode = item.controllerInteraction.chatControllerNode() {
|
if self.textSelectionNode == nil, let item = self.item, !item.associatedData.isCopyProtectionEnabled && !item.message.isCopyProtected(), let rootNode = item.controllerInteraction.chatControllerNode() {
|
||||||
let selectionColor: UIColor
|
let selectionColor: UIColor
|
||||||
let knobColor: UIColor
|
let knobColor: UIColor
|
||||||
if item.message.effectivelyIncoming(item.context.account.peerId) {
|
if item.message.effectivelyIncoming(item.context.account.peerId) {
|
||||||
|
|||||||
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 updatedTheme = false
|
||||||
var updatedWallpaper = false
|
var updatedWallpaper = false
|
||||||
var updatedSelected = false
|
var updatedSelected = false
|
||||||
|
var updatedNightMode = false
|
||||||
|
|
||||||
if currentItem?.emoticon != item.emoticon {
|
if currentItem?.emoticon != item.emoticon {
|
||||||
updatedEmoticon = true
|
updatedEmoticon = true
|
||||||
@ -410,6 +411,9 @@ private final class ThemeSettingsThemeItemIconNode : ListViewItemNode {
|
|||||||
if currentItem?.selected != item.selected {
|
if currentItem?.selected != item.selected {
|
||||||
updatedSelected = true
|
updatedSelected = true
|
||||||
}
|
}
|
||||||
|
if currentItem?.nightMode != item.nightMode {
|
||||||
|
updatedNightMode = true
|
||||||
|
}
|
||||||
|
|
||||||
let text = NSAttributedString(string: item.strings.Conversation_Theme_NoTheme, font: Font.semibold(15.0), textColor: item.theme.actionSheet.controlAccentColor)
|
let text = NSAttributedString(string: item.strings.Conversation_Theme_NoTheme, font: Font.semibold(15.0), textColor: item.theme.actionSheet.controlAccentColor)
|
||||||
let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: text, backgroundColor: nil, maximumNumberOfLines: 2, truncationType: .end, constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
|
let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: text, backgroundColor: nil, maximumNumberOfLines: 2, truncationType: .end, constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
|
||||||
@ -423,7 +427,7 @@ private final class ThemeSettingsThemeItemIconNode : ListViewItemNode {
|
|||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.item = item
|
strongSelf.item = item
|
||||||
|
|
||||||
if updatedThemeReference || updatedWallpaper {
|
if updatedThemeReference || updatedWallpaper || updatedNightMode {
|
||||||
if let themeReference = item.themeReference {
|
if let themeReference = item.themeReference {
|
||||||
strongSelf.imageNode.setSignal(themeIconImage(account: item.context.account, accountManager: item.context.sharedContext.accountManager, theme: themeReference, color: nil, wallpaper: item.wallpaper, nightMode: item.nightMode, emoticon: true))
|
strongSelf.imageNode.setSignal(themeIconImage(account: item.context.account, accountManager: item.context.sharedContext.accountManager, theme: themeReference, color: nil, wallpaper: item.wallpaper, nightMode: item.nightMode, emoticon: true))
|
||||||
strongSelf.imageNode.backgroundColor = nil
|
strongSelf.imageNode.backgroundColor = nil
|
||||||
|
|||||||
@ -169,7 +169,7 @@ final class EditAccessoryPanelNode: AccessoryPanelNode {
|
|||||||
if let currentEditMediaReference = self.currentEditMediaReference {
|
if let currentEditMediaReference = self.currentEditMediaReference {
|
||||||
effectiveMessage = effectiveMessage.withUpdatedMedia([currentEditMediaReference.media])
|
effectiveMessage = effectiveMessage.withUpdatedMedia([currentEditMediaReference.media])
|
||||||
}
|
}
|
||||||
(text, _) = descriptionStringForMessage(contentSettings: context.currentContentSettings.with { $0 }, message: EngineMessage(effectiveMessage), strings: self.strings, nameDisplayOrder: self.nameDisplayOrder, dateTimeFormat: self.dateTimeFormat, accountPeerId: self.context.account.peerId)
|
(text, _, _) = descriptionStringForMessage(contentSettings: context.currentContentSettings.with { $0 }, message: EngineMessage(effectiveMessage), strings: self.strings, nameDisplayOrder: self.nameDisplayOrder, dateTimeFormat: self.dateTimeFormat, accountPeerId: self.context.account.peerId)
|
||||||
}
|
}
|
||||||
|
|
||||||
var updatedMediaReference: AnyMediaReference?
|
var updatedMediaReference: AnyMediaReference?
|
||||||
|
|||||||
@ -5394,7 +5394,35 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
|||||||
guard let data = self.data, let peer = data.peer, let controller = self.controller else {
|
guard let data = self.data, let peer = data.peer, let controller = self.controller else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
controller.present(QrCodeScreen(context: self.context, updatedPresentationData: controller.updatedPresentationData, subject: .peer(peer: EnginePeer(peer))), in: .window(.root))
|
|
||||||
|
let animatedEmojiStickers = context.engine.stickers.loadedStickerPack(reference: .animatedEmoji, forceActualized: false)
|
||||||
|
|> map { animatedEmoji -> [String: [StickerPackItem]] in
|
||||||
|
var animatedEmojiStickers: [String: [StickerPackItem]] = [:]
|
||||||
|
switch animatedEmoji {
|
||||||
|
case let .result(_, items, _):
|
||||||
|
for item in items {
|
||||||
|
if let emoji = item.getStringRepresentationsOfIndexKeys().first {
|
||||||
|
animatedEmojiStickers[emoji.basicEmoji.0] = [item]
|
||||||
|
let strippedEmoji = emoji.basicEmoji.0.strippedEmoji
|
||||||
|
if animatedEmojiStickers[strippedEmoji] == nil {
|
||||||
|
animatedEmojiStickers[strippedEmoji] = [item]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return animatedEmojiStickers
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = (animatedEmojiStickers
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak self, weak controller] animatedEmojiStickers in
|
||||||
|
if let strongSelf = self, let controller = controller {
|
||||||
|
controller.present(ChatQrCodeScreen(context: strongSelf.context, animatedEmojiStickers: animatedEmojiStickers, peer: peer), in: .window(.root))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// controller.present(QrCodeScreen(context: self.context, updatedPresentationData: controller.updatedPresentationData, subject: .peer(peer: EnginePeer(peer))), in: .window(.root))
|
||||||
}
|
}
|
||||||
|
|
||||||
fileprivate func openSettings(section: PeerInfoSettingsSection) {
|
fileprivate func openSettings(section: PeerInfoSettingsSection) {
|
||||||
@ -6639,7 +6667,9 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
|||||||
rightNavigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .done, isForExpandedView: false))
|
rightNavigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .done, isForExpandedView: false))
|
||||||
} else {
|
} else {
|
||||||
if self.isSettings {
|
if self.isSettings {
|
||||||
leftNavigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .qrCode, isForExpandedView: false))
|
if let addressName = self.data?.peer?.addressName, !addressName.isEmpty {
|
||||||
|
leftNavigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .qrCode, isForExpandedView: false))
|
||||||
|
}
|
||||||
|
|
||||||
rightNavigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .edit, isForExpandedView: false))
|
rightNavigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .edit, isForExpandedView: false))
|
||||||
rightNavigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .search, isForExpandedView: true))
|
rightNavigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .search, isForExpandedView: true))
|
||||||
|
|||||||
@ -106,7 +106,7 @@ final class ReplyAccessoryPanelNode: AccessoryPanelNode {
|
|||||||
authorName = EnginePeer(author).displayTitle(strings: strings, displayOrder: nameDisplayOrder)
|
authorName = EnginePeer(author).displayTitle(strings: strings, displayOrder: nameDisplayOrder)
|
||||||
}
|
}
|
||||||
if let message = message {
|
if let message = message {
|
||||||
(text, _) = descriptionStringForMessage(contentSettings: context.currentContentSettings.with { $0 }, message: EngineMessage(message), strings: strings, nameDisplayOrder: nameDisplayOrder, dateTimeFormat: dateTimeFormat, accountPeerId: context.account.peerId)
|
(text, _, _) = descriptionStringForMessage(contentSettings: context.currentContentSettings.with { $0 }, message: EngineMessage(message), strings: strings, nameDisplayOrder: nameDisplayOrder, dateTimeFormat: dateTimeFormat, accountPeerId: context.account.peerId)
|
||||||
}
|
}
|
||||||
|
|
||||||
var updatedMediaReference: AnyMediaReference?
|
var updatedMediaReference: AnyMediaReference?
|
||||||
|
|||||||
@ -19,6 +19,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
|
|||||||
public var enableDebugDataDisplay: Bool
|
public var enableDebugDataDisplay: Bool
|
||||||
public var acceleratedStickers: Bool
|
public var acceleratedStickers: Bool
|
||||||
public var experimentalBackground: Bool
|
public var experimentalBackground: Bool
|
||||||
|
public var snow: Bool
|
||||||
|
|
||||||
public static var defaultSettings: ExperimentalUISettings {
|
public static var defaultSettings: ExperimentalUISettings {
|
||||||
return ExperimentalUISettings(
|
return ExperimentalUISettings(
|
||||||
@ -36,7 +37,8 @@ public struct ExperimentalUISettings: Codable, Equatable {
|
|||||||
experimentalCompatibility: false,
|
experimentalCompatibility: false,
|
||||||
enableDebugDataDisplay: false,
|
enableDebugDataDisplay: false,
|
||||||
acceleratedStickers: false,
|
acceleratedStickers: false,
|
||||||
experimentalBackground: false
|
experimentalBackground: false,
|
||||||
|
snow: false
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,7 +57,8 @@ public struct ExperimentalUISettings: Codable, Equatable {
|
|||||||
experimentalCompatibility: Bool,
|
experimentalCompatibility: Bool,
|
||||||
enableDebugDataDisplay: Bool,
|
enableDebugDataDisplay: Bool,
|
||||||
acceleratedStickers: Bool,
|
acceleratedStickers: Bool,
|
||||||
experimentalBackground: Bool
|
experimentalBackground: Bool,
|
||||||
|
snow: Bool
|
||||||
) {
|
) {
|
||||||
self.keepChatNavigationStack = keepChatNavigationStack
|
self.keepChatNavigationStack = keepChatNavigationStack
|
||||||
self.skipReadHistory = skipReadHistory
|
self.skipReadHistory = skipReadHistory
|
||||||
@ -72,6 +75,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
|
|||||||
self.enableDebugDataDisplay = enableDebugDataDisplay
|
self.enableDebugDataDisplay = enableDebugDataDisplay
|
||||||
self.acceleratedStickers = acceleratedStickers
|
self.acceleratedStickers = acceleratedStickers
|
||||||
self.experimentalBackground = experimentalBackground
|
self.experimentalBackground = experimentalBackground
|
||||||
|
self.snow = snow
|
||||||
}
|
}
|
||||||
|
|
||||||
public init(from decoder: Decoder) throws {
|
public init(from decoder: Decoder) throws {
|
||||||
@ -92,6 +96,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
|
|||||||
self.enableDebugDataDisplay = (try container.decodeIfPresent(Int32.self, forKey: "enableDebugDataDisplay") ?? 0) != 0
|
self.enableDebugDataDisplay = (try container.decodeIfPresent(Int32.self, forKey: "enableDebugDataDisplay") ?? 0) != 0
|
||||||
self.acceleratedStickers = (try container.decodeIfPresent(Int32.self, forKey: "acceleratedStickers") ?? 0) != 0
|
self.acceleratedStickers = (try container.decodeIfPresent(Int32.self, forKey: "acceleratedStickers") ?? 0) != 0
|
||||||
self.experimentalBackground = (try container.decodeIfPresent(Int32.self, forKey: "experimentalBackground") ?? 0) != 0
|
self.experimentalBackground = (try container.decodeIfPresent(Int32.self, forKey: "experimentalBackground") ?? 0) != 0
|
||||||
|
self.snow = (try container.decodeIfPresent(Int32.self, forKey: "snow") ?? 0) != 0
|
||||||
}
|
}
|
||||||
|
|
||||||
public func encode(to encoder: Encoder) throws {
|
public func encode(to encoder: Encoder) throws {
|
||||||
@ -112,6 +117,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
|
|||||||
try container.encode((self.enableDebugDataDisplay ? 1 : 0) as Int32, forKey: "enableDebugDataDisplay")
|
try container.encode((self.enableDebugDataDisplay ? 1 : 0) as Int32, forKey: "enableDebugDataDisplay")
|
||||||
try container.encode((self.acceleratedStickers ? 1 : 0) as Int32, forKey: "acceleratedStickers")
|
try container.encode((self.acceleratedStickers ? 1 : 0) as Int32, forKey: "acceleratedStickers")
|
||||||
try container.encode((self.experimentalBackground ? 1 : 0) as Int32, forKey: "experimentalBackground")
|
try container.encode((self.experimentalBackground ? 1 : 0) as Int32, forKey: "experimentalBackground")
|
||||||
|
try container.encode((self.snow ? 1 : 0) as Int32, forKey: "snow")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -698,7 +698,7 @@ public func breakChatInputText(_ text: NSAttributedString) -> [NSAttributedStrin
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private let markdownRegexFormat = "(^|\\s|\\n)(````?)([\\s\\S]+?)(````?)([\\s\\n\\.,:?!;]|$)|(^|\\s)(`|\\*\\*|__|~~)([^\\n]+?)\\7([\\s\\.,:?!;]|$)|@(\\d+)\\s*\\((.+?)\\)"
|
private let markdownRegexFormat = "(^|\\s|\\n)(````?)([\\s\\S]+?)(````?)([\\s\\n\\.,:?!;]|$)|(^|\\s)(`|\\*\\*|__|~~|\\|\\|)([^\\n]+?)\\7([\\s\\.,:?!;]|$)|@(\\d+)\\s*\\((.+?)\\)"
|
||||||
private let markdownRegex = try? NSRegularExpression(pattern: markdownRegexFormat, options: [.caseInsensitive, .anchorsMatchLines])
|
private let markdownRegex = try? NSRegularExpression(pattern: markdownRegexFormat, options: [.caseInsensitive, .anchorsMatchLines])
|
||||||
|
|
||||||
public func convertMarkdownToAttributes(_ text: NSAttributedString) -> NSAttributedString {
|
public func convertMarkdownToAttributes(_ text: NSAttributedString) -> NSAttributedString {
|
||||||
|
|||||||
@ -188,6 +188,7 @@ public enum TextSelectionAction {
|
|||||||
case share
|
case share
|
||||||
case lookup
|
case lookup
|
||||||
case speak
|
case speak
|
||||||
|
case translate
|
||||||
}
|
}
|
||||||
|
|
||||||
public final class TextSelectionNode: ASDisplayNode {
|
public final class TextSelectionNode: ASDisplayNode {
|
||||||
@ -501,6 +502,12 @@ public final class TextSelectionNode: ASDisplayNode {
|
|||||||
self?.performAction(attributedText, .lookup)
|
self?.performAction(attributedText, .lookup)
|
||||||
self?.dismissSelection()
|
self?.dismissSelection()
|
||||||
}))
|
}))
|
||||||
|
// if #available(iOS 15.0, *) {
|
||||||
|
// actions.append(ContextMenuAction(content: .text(title: self.strings.Conversation_ContextMenuTranslate, accessibilityLabel: self.strings.Conversation_ContextMenuTranslate), action: { [weak self] in
|
||||||
|
// self?.performAction(attributedText, .translate)
|
||||||
|
// self?.dismissSelection()
|
||||||
|
// }))
|
||||||
|
// }
|
||||||
if isSpeakSelectionEnabled() {
|
if isSpeakSelectionEnabled() {
|
||||||
actions.append(ContextMenuAction(content: .text(title: self.strings.Conversation_ContextMenuSpeak, accessibilityLabel: self.strings.Conversation_ContextMenuSpeak), action: { [weak self] in
|
actions.append(ContextMenuAction(content: .text(title: self.strings.Conversation_ContextMenuSpeak, accessibilityLabel: self.strings.Conversation_ContextMenuSpeak), action: { [weak self] in
|
||||||
self?.performAction(attributedText, .speak)
|
self?.performAction(attributedText, .speak)
|
||||||
|
|||||||
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 updateBubbleTheme(bubbleTheme: PresentationTheme, bubbleCorners: PresentationChatBubbleCorners)
|
||||||
func hasBubbleBackground(for type: WallpaperBubbleType) -> Bool
|
func hasBubbleBackground(for type: WallpaperBubbleType) -> Bool
|
||||||
func makeBubbleBackground(for type: WallpaperBubbleType) -> WallpaperBubbleBackgroundNode?
|
func makeBubbleBackground(for type: WallpaperBubbleType) -> WallpaperBubbleBackgroundNode?
|
||||||
|
|
||||||
|
func makeDimmedNode() -> ASDisplayNode?
|
||||||
}
|
}
|
||||||
|
|
||||||
final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode {
|
final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode {
|
||||||
@ -799,7 +801,7 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode
|
|||||||
if isFirstLayout && !self.frame.isEmpty {
|
if isFirstLayout && !self.frame.isEmpty {
|
||||||
self.updateScale()
|
self.updateScale()
|
||||||
|
|
||||||
if false, self.newYearNode == nil {
|
if self.context.sharedContext.immediateExperimentalUISettings.snow, self.newYearNode == nil {
|
||||||
let newYearNode = WallpaperNewYearNode()
|
let newYearNode = WallpaperNewYearNode()
|
||||||
self.addSubnode(newYearNode)
|
self.addSubnode(newYearNode)
|
||||||
self.newYearNode = newYearNode
|
self.newYearNode = newYearNode
|
||||||
@ -897,6 +899,14 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode
|
|||||||
node.updateContents()
|
node.updateContents()
|
||||||
return node
|
return node
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func makeDimmedNode() -> ASDisplayNode? {
|
||||||
|
if let gradientBackgroundNode = self.gradientBackgroundNode {
|
||||||
|
return GradientBackgroundNode.CloneNode(parentNode: gradientBackgroundNode)
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private protocol WallpaperComponentView: AnyObject {
|
private protocol WallpaperComponentView: AnyObject {
|
||||||
@ -1695,6 +1705,10 @@ final class WallpaperBackgroundNodeMergedImpl: ASDisplayNode, WallpaperBackgroun
|
|||||||
node.updateContents()
|
node.updateContents()
|
||||||
return node
|
return node
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func makeDimmedNode() -> ASDisplayNode? {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private let sharedStorage = WallpaperBackgroundNodeMergedImpl.SharedStorage()
|
private let sharedStorage = WallpaperBackgroundNodeMergedImpl.SharedStorage()
|
||||||
@ -1738,7 +1752,7 @@ private class WallpaperNewYearNode: ASDisplayNode {
|
|||||||
cell1.scale = 0.04
|
cell1.scale = 0.04
|
||||||
cell1.scaleRange = 0.15
|
cell1.scaleRange = 0.15
|
||||||
cell1.color = UIColor.white.withAlphaComponent(0.88).cgColor
|
cell1.color = UIColor.white.withAlphaComponent(0.88).cgColor
|
||||||
cell1.alphaRange = -0.2
|
// cell1.alphaRange = -0.2
|
||||||
|
|
||||||
particlesLayer.emitterCells = [cell1]
|
particlesLayer.emitterCells = [cell1]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1300,7 +1300,23 @@ public func themeImage(account: Account, accountManager: AccountManager<Telegram
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func themeIconImage(account: Account, accountManager: AccountManager<TelegramAccountManagerTypes>, theme: PresentationThemeReference, color: PresentationThemeAccentColor?, wallpaper: TelegramWallpaper? = nil, nightMode: Bool? = nil, emoticon: Bool = false, large: Bool = false) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> {
|
private let qrIconImage: UIImage = {
|
||||||
|
return generateImage(CGSize(width: 36.0, height: 36.0), rotatedContext: { size, context in
|
||||||
|
let bounds = CGRect(origin: CGPoint(), size: size)
|
||||||
|
context.clear(bounds)
|
||||||
|
|
||||||
|
context.setFillColor(UIColor.white.cgColor)
|
||||||
|
context.addPath(UIBezierPath(roundedRect: CGRect(origin: CGPoint(), size: size), cornerRadius: 9.0).cgPath)
|
||||||
|
context.fillPath()
|
||||||
|
|
||||||
|
if let image = UIImage(bundleImageName: "Settings/QrButtonIcon")?.cgImage {
|
||||||
|
context.clip(to: CGRect(x: 6.0, y: 6.0, width: 24.0, height: 24.0), mask: image)
|
||||||
|
context.clear(bounds)
|
||||||
|
}
|
||||||
|
})!
|
||||||
|
}()
|
||||||
|
|
||||||
|
public func themeIconImage(account: Account, accountManager: AccountManager<TelegramAccountManagerTypes>, theme: PresentationThemeReference, color: PresentationThemeAccentColor?, wallpaper: TelegramWallpaper? = nil, nightMode: Bool? = nil, emoticon: Bool = false, large: Bool = false, qr: Bool = false) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> {
|
||||||
let colorsSignal: Signal<((UIColor, UIColor?, [UInt32]), [UIColor], [UIColor], UIImage?, Bool, Bool, CGFloat, Int32?), NoError>
|
let colorsSignal: Signal<((UIColor, UIColor?, [UInt32]), [UIColor], [UIColor], UIImage?, Bool, Bool, CGFloat, Int32?), NoError>
|
||||||
|
|
||||||
var reference: MediaResourceReference?
|
var reference: MediaResourceReference?
|
||||||
@ -1552,134 +1568,140 @@ public func themeIconImage(account: Account, accountManager: AccountManager<Tele
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
c.translateBy(x: drawingRect.width / 2.0, y: drawingRect.height / 2.0)
|
if qr {
|
||||||
c.scaleBy(x: 1.0, y: -1.0)
|
if let image = qrIconImage.cgImage {
|
||||||
c.translateBy(x: -drawingRect.width / 2.0, y: -drawingRect.height / 2.0)
|
c.draw(image, in: CGRect(x: floor((drawingRect.width - 36.0) / 2.0), y: floor((drawingRect.height - 36.0) / 2.0), width: 36.0, height: 36.0))
|
||||||
|
}
|
||||||
let incomingColors = colors.1
|
} else {
|
||||||
if emoticon {
|
c.translateBy(x: drawingRect.width / 2.0, y: drawingRect.height / 2.0)
|
||||||
if large {
|
c.scaleBy(x: 1.0, y: -1.0)
|
||||||
c.saveGState()
|
c.translateBy(x: -drawingRect.width / 2.0, y: -drawingRect.height / 2.0)
|
||||||
|
|
||||||
|
let incomingColors = colors.1
|
||||||
|
if emoticon {
|
||||||
|
if large {
|
||||||
|
c.saveGState()
|
||||||
|
|
||||||
c.translateBy(x: 7.0, y: 27.0)
|
c.translateBy(x: 7.0, y: 27.0)
|
||||||
c.translateBy(x: 114.0, y: 32.0)
|
c.translateBy(x: 114.0, y: 32.0)
|
||||||
c.scaleBy(x: 1.0, y: -1.0)
|
c.scaleBy(x: 1.0, y: -1.0)
|
||||||
c.translateBy(x: -114.0, y: -32.0)
|
c.translateBy(x: -114.0, y: -32.0)
|
||||||
|
|
||||||
let _ = try? drawSvgPath(c, path: "M12.8304,29.8712 C10.0551,31.8416 6.6628,33 2.99998,33 C1.98426,33 0.989361,32.9109 0.022644,32.7402 C2.97318,31.9699 5.24596,29.5785 5.84625,26.5607 C5.99996,25.7879 5.99996,24.8586 5.99996,23 V16.0 H6.00743 C6.27176,7.11861 13.5546,0 22.5,0 H61.5 C70.6127,0 78,7.3873 78,16.5 C78,25.6127 70.6127,33 61.5,33 H22.5 C18.8883,33 15.5476,31.8396 12.8304,29.8712 ")
|
let _ = try? drawSvgPath(c, path: "M12.8304,29.8712 C10.0551,31.8416 6.6628,33 2.99998,33 C1.98426,33 0.989361,32.9109 0.022644,32.7402 C2.97318,31.9699 5.24596,29.5785 5.84625,26.5607 C5.99996,25.7879 5.99996,24.8586 5.99996,23 V16.0 H6.00743 C6.27176,7.11861 13.5546,0 22.5,0 H61.5 C70.6127,0 78,7.3873 78,16.5 C78,25.6127 70.6127,33 61.5,33 H22.5 C18.8883,33 15.5476,31.8396 12.8304,29.8712 ")
|
||||||
if Set(incomingColors.map(\.rgb)).count > 1 {
|
if Set(incomingColors.map(\.rgb)).count > 1 {
|
||||||
c.clip()
|
c.clip()
|
||||||
|
|
||||||
var colors: [CGColor] = []
|
var colors: [CGColor] = []
|
||||||
var locations: [CGFloat] = []
|
var locations: [CGFloat] = []
|
||||||
for i in 0 ..< incomingColors.count {
|
for i in 0 ..< incomingColors.count {
|
||||||
let t = CGFloat(i) / CGFloat(incomingColors.count - 1)
|
let t = CGFloat(i) / CGFloat(incomingColors.count - 1)
|
||||||
locations.append(t)
|
locations.append(t)
|
||||||
colors.append(incomingColors[i].cgColor)
|
colors.append(incomingColors[i].cgColor)
|
||||||
|
}
|
||||||
|
|
||||||
|
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
||||||
|
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as NSArray, locations: &locations)!
|
||||||
|
c.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: 34.0), options: CGGradientDrawingOptions())
|
||||||
|
} else {
|
||||||
|
c.setFillColor(incomingColors[0].cgColor)
|
||||||
|
c.fillPath()
|
||||||
}
|
}
|
||||||
|
|
||||||
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
c.restoreGState()
|
||||||
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as NSArray, locations: &locations)!
|
|
||||||
c.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: 34.0), options: CGGradientDrawingOptions())
|
|
||||||
} else {
|
} else {
|
||||||
c.setFillColor(incomingColors[0].cgColor)
|
let rect = CGRect(x: 8.0, y: 44.0, width: 48.0, height: 24.0)
|
||||||
c.fillPath()
|
c.addPath(UIBezierPath(roundedRect: rect, cornerRadius: 12.0).cgPath)
|
||||||
}
|
c.clip()
|
||||||
|
|
||||||
c.restoreGState()
|
if incomingColors.count >= 2 {
|
||||||
} else {
|
let gradientColors = incomingColors.reversed().map { $0.cgColor } as CFArray
|
||||||
let rect = CGRect(x: 8.0, y: 44.0, width: 48.0, height: 24.0)
|
|
||||||
c.addPath(UIBezierPath(roundedRect: rect, cornerRadius: 12.0).cgPath)
|
|
||||||
c.clip()
|
|
||||||
|
|
||||||
if incomingColors.count >= 2 {
|
|
||||||
let gradientColors = incomingColors.reversed().map { $0.cgColor } as CFArray
|
|
||||||
|
|
||||||
var locations: [CGFloat] = []
|
var locations: [CGFloat] = []
|
||||||
for i in 0 ..< incomingColors.count {
|
for i in 0 ..< incomingColors.count {
|
||||||
let t = CGFloat(i) / CGFloat(incomingColors.count - 1)
|
let t = CGFloat(i) / CGFloat(incomingColors.count - 1)
|
||||||
locations.append(t)
|
locations.append(t)
|
||||||
|
}
|
||||||
|
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
||||||
|
let gradient = CGGradient(colorsSpace: colorSpace, colors: gradientColors, locations: &locations)!
|
||||||
|
|
||||||
|
c.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: rect.minY), end: CGPoint(x: 0.0, y: rect.maxY), options: CGGradientDrawingOptions())
|
||||||
|
} else if !incomingColors.isEmpty {
|
||||||
|
c.setFillColor(incomingColors[0].cgColor)
|
||||||
|
c.fill(rect)
|
||||||
}
|
}
|
||||||
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
|
||||||
let gradient = CGGradient(colorsSpace: colorSpace, colors: gradientColors, locations: &locations)!
|
c.resetClip()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let incoming = generateGradientTintedImage(image: UIImage(bundleImageName: "Settings/ThemeBubble"), colors: incomingColors)
|
||||||
|
c.draw(incoming!.cgImage!, in: CGRect(x: 9.0, y: 34.0, width: 57.0, height: 16.0))
|
||||||
|
}
|
||||||
|
|
||||||
|
if !(emoticon && large) {
|
||||||
|
c.translateBy(x: drawingRect.width / 2.0, y: drawingRect.height / 2.0)
|
||||||
|
c.scaleBy(x: -1.0, y: 1.0)
|
||||||
|
c.translateBy(x: -drawingRect.width / 2.0, y: -drawingRect.height / 2.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
let outgoingColors = colors.2
|
||||||
|
if emoticon {
|
||||||
|
if large {
|
||||||
|
c.saveGState()
|
||||||
|
|
||||||
|
c.translateBy(x: (drawingRect.width - 120) - 71, y: 66.0)
|
||||||
|
c.translateBy(x: 114.0, y: 32.0)
|
||||||
|
c.scaleBy(x: 1.0, y: -1.0)
|
||||||
|
c.translateBy(x: 0.0, y: -32.0)
|
||||||
|
|
||||||
|
let _ = try? drawSvgPath(c, path: "M57.1696,29.8712 C59.9449,31.8416 63.3372,33 67,33 C68.0157,33 69.0106,32.9109 69.9773,32.7402 C67.0268,31.9699 64.754,29.5786 64.1537,26.5607 C64,25.7879 64,24.8586 64,23 V16.5 V16 H63.9926 C63.7282,7.11861 56.4454,0 47.5,0 H16.5 C7.3873,0 0,7.3873 0,16.5 C0,25.6127 7.3873,33 16.5,33 H47.5 C51.1117,33 54.4524,31.8396 57.1696,29.8712 ")
|
||||||
|
if Set(outgoingColors.map(\.rgb)).count > 1 {
|
||||||
|
c.clip()
|
||||||
|
|
||||||
c.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: rect.minY), end: CGPoint(x: 0.0, y: rect.maxY), options: CGGradientDrawingOptions())
|
var colors: [CGColor] = []
|
||||||
} else if !incomingColors.isEmpty {
|
var locations: [CGFloat] = []
|
||||||
c.setFillColor(incomingColors[0].cgColor)
|
for i in 0 ..< outgoingColors.count {
|
||||||
c.fill(rect)
|
let t = CGFloat(i) / CGFloat(outgoingColors.count - 1)
|
||||||
|
locations.append(t)
|
||||||
|
colors.append(outgoingColors[i].cgColor)
|
||||||
|
}
|
||||||
|
|
||||||
|
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
||||||
|
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as NSArray, locations: &locations)!
|
||||||
|
c.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: 34.0), options: CGGradientDrawingOptions())
|
||||||
|
} else {
|
||||||
|
c.setFillColor(outgoingColors[0].cgColor)
|
||||||
|
c.fillPath()
|
||||||
|
}
|
||||||
|
|
||||||
|
c.restoreGState()
|
||||||
|
} else {
|
||||||
|
let rect = CGRect(x: 8.0, y: 72.0, width: 48.0, height: 24.0)
|
||||||
|
c.addPath(UIBezierPath(roundedRect: rect, cornerRadius: 12.0).cgPath)
|
||||||
|
c.clip()
|
||||||
|
|
||||||
|
if outgoingColors.count >= 2 {
|
||||||
|
let gradientColors = outgoingColors.reversed().map { $0.cgColor } as CFArray
|
||||||
|
|
||||||
|
var locations: [CGFloat] = []
|
||||||
|
for i in 0 ..< outgoingColors.count {
|
||||||
|
let t = CGFloat(i) / CGFloat(outgoingColors.count - 1)
|
||||||
|
locations.append(t)
|
||||||
|
}
|
||||||
|
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
||||||
|
let gradient = CGGradient(colorsSpace: colorSpace, colors: gradientColors, locations: &locations)!
|
||||||
|
|
||||||
|
c.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: rect.minY), end: CGPoint(x: 0.0, y: rect.maxY), options: CGGradientDrawingOptions())
|
||||||
|
} else if !outgoingColors.isEmpty {
|
||||||
|
c.setFillColor(outgoingColors[0].cgColor)
|
||||||
|
c.fill(rect)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
c.resetClip()
|
c.resetClip()
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let incoming = generateGradientTintedImage(image: UIImage(bundleImageName: "Settings/ThemeBubble"), colors: incomingColors)
|
|
||||||
c.draw(incoming!.cgImage!, in: CGRect(x: 9.0, y: 34.0, width: 57.0, height: 16.0))
|
|
||||||
}
|
|
||||||
|
|
||||||
if !(emoticon && large) {
|
|
||||||
c.translateBy(x: drawingRect.width / 2.0, y: drawingRect.height / 2.0)
|
|
||||||
c.scaleBy(x: -1.0, y: 1.0)
|
|
||||||
c.translateBy(x: -drawingRect.width / 2.0, y: -drawingRect.height / 2.0)
|
|
||||||
}
|
|
||||||
|
|
||||||
let outgoingColors = colors.2
|
|
||||||
if emoticon {
|
|
||||||
if large {
|
|
||||||
c.saveGState()
|
|
||||||
|
|
||||||
c.translateBy(x: (drawingRect.width - 120) - 71, y: 66.0)
|
|
||||||
c.translateBy(x: 114.0, y: 32.0)
|
|
||||||
c.scaleBy(x: 1.0, y: -1.0)
|
|
||||||
c.translateBy(x: 0.0, y: -32.0)
|
|
||||||
|
|
||||||
let _ = try? drawSvgPath(c, path: "M57.1696,29.8712 C59.9449,31.8416 63.3372,33 67,33 C68.0157,33 69.0106,32.9109 69.9773,32.7402 C67.0268,31.9699 64.754,29.5786 64.1537,26.5607 C64,25.7879 64,24.8586 64,23 V16.5 V16 H63.9926 C63.7282,7.11861 56.4454,0 47.5,0 H16.5 C7.3873,0 0,7.3873 0,16.5 C0,25.6127 7.3873,33 16.5,33 H47.5 C51.1117,33 54.4524,31.8396 57.1696,29.8712 ")
|
|
||||||
if Set(outgoingColors.map(\.rgb)).count > 1 {
|
|
||||||
c.clip()
|
|
||||||
|
|
||||||
var colors: [CGColor] = []
|
|
||||||
var locations: [CGFloat] = []
|
|
||||||
for i in 0 ..< outgoingColors.count {
|
|
||||||
let t = CGFloat(i) / CGFloat(outgoingColors.count - 1)
|
|
||||||
locations.append(t)
|
|
||||||
colors.append(outgoingColors[i].cgColor)
|
|
||||||
}
|
|
||||||
|
|
||||||
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
|
||||||
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as NSArray, locations: &locations)!
|
|
||||||
c.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: 34.0), options: CGGradientDrawingOptions())
|
|
||||||
} else {
|
|
||||||
c.setFillColor(outgoingColors[0].cgColor)
|
|
||||||
c.fillPath()
|
|
||||||
}
|
|
||||||
|
|
||||||
c.restoreGState()
|
|
||||||
} else {
|
} else {
|
||||||
let rect = CGRect(x: 8.0, y: 72.0, width: 48.0, height: 24.0)
|
let outgoing = generateGradientTintedImage(image: UIImage(bundleImageName: "Settings/ThemeBubble"), colors: outgoingColors)
|
||||||
c.addPath(UIBezierPath(roundedRect: rect, cornerRadius: 12.0).cgPath)
|
c.draw(outgoing!.cgImage!, in: CGRect(x: 9.0, y: 12.0, width: 57.0, height: 16.0))
|
||||||
c.clip()
|
|
||||||
|
|
||||||
if outgoingColors.count >= 2 {
|
|
||||||
let gradientColors = outgoingColors.reversed().map { $0.cgColor } as CFArray
|
|
||||||
|
|
||||||
var locations: [CGFloat] = []
|
|
||||||
for i in 0 ..< outgoingColors.count {
|
|
||||||
let t = CGFloat(i) / CGFloat(outgoingColors.count - 1)
|
|
||||||
locations.append(t)
|
|
||||||
}
|
|
||||||
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
|
||||||
let gradient = CGGradient(colorsSpace: colorSpace, colors: gradientColors, locations: &locations)!
|
|
||||||
|
|
||||||
c.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: rect.minY), end: CGPoint(x: 0.0, y: rect.maxY), options: CGGradientDrawingOptions())
|
|
||||||
} else if !outgoingColors.isEmpty {
|
|
||||||
c.setFillColor(outgoingColors[0].cgColor)
|
|
||||||
c.fill(rect)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
c.resetClip()
|
|
||||||
} else {
|
|
||||||
let outgoing = generateGradientTintedImage(image: UIImage(bundleImageName: "Settings/ThemeBubble"), colors: outgoingColors)
|
|
||||||
c.draw(outgoing!.cgImage!, in: CGRect(x: 9.0, y: 12.0, width: 57.0, height: 16.0))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
addCorners(context, arguments: arguments)
|
addCorners(context, arguments: arguments)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user