Various Improvements

This commit is contained in:
Ilya Laktyushin 2022-01-09 09:40:39 +03:00
parent 3c1afe4014
commit 15caa32878
16 changed files with 761 additions and 139 deletions

View File

@ -7231,3 +7231,11 @@ Sorry for the inconvenience.";
"ClearCache.Progress" = "Clearing the Cache • %d%";
"ClearCache.KeepOpenedDescription" = "Please keep this window open until the clearing is completed.";
"Share.ShareAsLink" = "Share as Link";
"Share.ShareAsImage" = "Share as Image";
"Share.MessagePreview" = "Message Preview";
"Share.ShareMessage" = "Share Message";
"Conversation.UserSendMessage" = "SEND MESSAGE";

View File

@ -819,6 +819,7 @@ public final class AnimatedStickerNode: ASDisplayNode {
private let eventsNode: AnimatedStickerNodeDisplayEvents
public var automaticallyLoadFirstFrame: Bool = false
public var automaticallyLoadLastFrame: Bool = false
public var playToCompletionOnStop: Bool = false
public var started: () -> Void = {}
@ -993,25 +994,30 @@ public final class AnimatedStickerNode: ASDisplayNode {
}
private func updateIsPlaying() {
guard !self.autoplay else {
return
}
let isPlaying = self.visibility && self.isDisplaying
if self.isPlaying != isPlaying {
self.isPlaying = isPlaying
if isPlaying {
self.play()
} else{
self.pause()
if !self.autoplay {
let isPlaying = self.visibility && self.isDisplaying
if self.isPlaying != isPlaying {
self.isPlaying = isPlaying
if isPlaying {
self.play()
} else{
self.pause()
}
self.isPlayingChanged(isPlaying)
}
self.isPlayingChanged(isPlaying)
}
let canDisplayFirstFrame = self.automaticallyLoadFirstFrame && self.isDisplaying
if self.canDisplayFirstFrame != canDisplayFirstFrame {
self.canDisplayFirstFrame = canDisplayFirstFrame
if canDisplayFirstFrame {
self.play(firstFrame: true)
if self.automaticallyLoadLastFrame {
if self.isDisplaying {
self.seekTo(.end)
}
} else {
let canDisplayFirstFrame = self.automaticallyLoadFirstFrame && self.isDisplaying
if self.canDisplayFirstFrame != canDisplayFirstFrame {
self.canDisplayFirstFrame = canDisplayFirstFrame
if canDisplayFirstFrame {
self.play(firstFrame: true)
}
}
}
}

View File

@ -31,6 +31,7 @@ swift_library(
"//submodules/SegmentedControlNode:SegmentedControlNode",
"//submodules/WallpaperBackgroundNode:WallpaperBackgroundNode",
"//submodules/ShimmerEffect:ShimmerEffect",
"//submodules/ContextUI:ContextUI",
],
visibility = [
"//visibility:public",

View File

@ -300,6 +300,7 @@ public final class ShareController: ViewController {
private var presentationData: PresentationData
private var presentationDataDisposable: Disposable?
private let forceTheme: PresentationTheme?
private let shareAsLink: Bool
private let externalShare: Bool
private let immediateExternalShare: Bool
@ -326,14 +327,16 @@ public final class ShareController: ViewController {
}
}
}
public var openShareAsImage: (([Message]) -> Void)?
public var debugAction: (() -> Void)?
public convenience init(context: AccountContext, subject: ShareControllerSubject, presetText: String? = nil, preferredAction: ShareControllerPreferredAction = .default, showInChat: ((Message) -> Void)? = nil, fromForeignApp: Bool = false, segmentedValues: [ShareControllerSegmentedValue]? = nil, externalShare: Bool = true, immediateExternalShare: Bool = false, switchableAccounts: [AccountWithInfo] = [], immediatePeerId: PeerId? = nil, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, forceTheme: PresentationTheme? = nil, forcedActionTitle: String? = nil) {
self.init(sharedContext: context.sharedContext, currentContext: context, subject: subject, presetText: presetText, preferredAction: preferredAction, showInChat: showInChat, fromForeignApp: fromForeignApp, segmentedValues: segmentedValues, externalShare: externalShare, immediateExternalShare: immediateExternalShare, switchableAccounts: switchableAccounts, immediatePeerId: immediatePeerId, updatedPresentationData: updatedPresentationData, forceTheme: forceTheme, forcedActionTitle: forcedActionTitle)
public convenience init(context: AccountContext, subject: ShareControllerSubject, presetText: String? = nil, preferredAction: ShareControllerPreferredAction = .default, showInChat: ((Message) -> Void)? = nil, fromForeignApp: Bool = false, segmentedValues: [ShareControllerSegmentedValue]? = nil, externalShare: Bool = true, immediateExternalShare: Bool = false, switchableAccounts: [AccountWithInfo] = [], immediatePeerId: PeerId? = nil, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, forceTheme: PresentationTheme? = nil, forcedActionTitle: String? = nil, shareAsLink: Bool = false) {
self.init(sharedContext: context.sharedContext, currentContext: context, subject: subject, presetText: presetText, preferredAction: preferredAction, showInChat: showInChat, fromForeignApp: fromForeignApp, segmentedValues: segmentedValues, externalShare: externalShare, immediateExternalShare: immediateExternalShare, switchableAccounts: switchableAccounts, immediatePeerId: immediatePeerId, updatedPresentationData: updatedPresentationData, forceTheme: forceTheme, forcedActionTitle: forcedActionTitle, shareAsLink: shareAsLink)
}
public init(sharedContext: SharedAccountContext, currentContext: AccountContext, subject: ShareControllerSubject, presetText: String? = nil, preferredAction: ShareControllerPreferredAction = .default, showInChat: ((Message) -> Void)? = nil, fromForeignApp: Bool = false, segmentedValues: [ShareControllerSegmentedValue]? = nil, externalShare: Bool = true, immediateExternalShare: Bool = false, switchableAccounts: [AccountWithInfo] = [], immediatePeerId: PeerId? = nil, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, forceTheme: PresentationTheme? = nil, forcedActionTitle: String? = nil) {
public init(sharedContext: SharedAccountContext, currentContext: AccountContext, subject: ShareControllerSubject, presetText: String? = nil, preferredAction: ShareControllerPreferredAction = .default, showInChat: ((Message) -> Void)? = nil, fromForeignApp: Bool = false, segmentedValues: [ShareControllerSegmentedValue]? = nil, externalShare: Bool = true, immediateExternalShare: Bool = false, switchableAccounts: [AccountWithInfo] = [], immediatePeerId: PeerId? = nil, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, forceTheme: PresentationTheme? = nil, forcedActionTitle: String? = nil, shareAsLink: Bool = false) {
self.sharedContext = sharedContext
self.currentContext = currentContext
self.currentAccount = currentContext.account
@ -346,6 +349,7 @@ public final class ShareController: ViewController {
self.fromForeignApp = fromForeignApp
self.segmentedValues = segmentedValues
self.forceTheme = forceTheme
self.shareAsLink = shareAsLink
self.presentationData = updatedPresentationData?.initial ?? sharedContext.currentPresentationData.with { $0 }
if let forceTheme = self.forceTheme {
@ -484,6 +488,11 @@ public final class ShareController: ViewController {
}
override public func loadDisplayNode() {
var fromPublicChannel = false
if case let .messages(messages) = self.subject, let message = messages.first, let peer = message.peers[message.id.peerId] as? TelegramChannel, case .broadcast = peer.info {
fromPublicChannel = true
}
self.displayNode = ShareControllerNode(sharedContext: self.sharedContext, presentationData: self.presentationData, presetText: self.presetText, defaultAction: self.defaultAction, requestLayout: { [weak self] transition in
self?.requestLayout(transition: transition)
}, presentError: { [weak self] title, text in
@ -491,8 +500,11 @@ public final class ShareController: ViewController {
return
}
strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: title, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
}, externalShare: self.externalShare, immediateExternalShare: self.immediateExternalShare, immediatePeerId: self.immediatePeerId, fromForeignApp: self.fromForeignApp, forceTheme: self.forceTheme, segmentedValues: self.segmentedValues)
}, externalShare: self.externalShare, immediateExternalShare: self.immediateExternalShare, immediatePeerId: self.immediatePeerId, fromForeignApp: self.fromForeignApp, forceTheme: self.forceTheme, fromPublicChannel: fromPublicChannel, segmentedValues: self.segmentedValues)
self.controllerNode.completed = self.completed
self.controllerNode.present = { [weak self] c in
self?.presentInGlobalOverlay(c, with: nil)
}
self.controllerNode.dismiss = { [weak self] shared in
self?.dismissed?(shared)
self?.presentingViewController?.dismiss(animated: false, completion: nil)
@ -651,7 +663,7 @@ public final class ShareController: ViewController {
|> take(1)
}
}
self.controllerNode.shareExternal = { [weak self] in
self.controllerNode.shareExternal = { [weak self] asImage in
if let strongSelf = self {
var collectableItems: [CollectableExternalShareItem] = []
var subject = strongSelf.subject
@ -659,6 +671,8 @@ public final class ShareController: ViewController {
let selectedValue = segmentedValues[strongSelf.controllerNode.selectedSegmentedIndex]
subject = selectedValue.subject
}
var messageUrl: String?
var messagesToShare: [Message]?
switch subject {
case let .url(text):
collectableItems.append(CollectableExternalShareItem(url: explicitUrl(text), text: "", author: nil, timestamp: nil, mediaReference: nil))
@ -675,6 +689,7 @@ public final class ShareController: ViewController {
let latLong = "\(media.latitude),\(media.longitude)"
collectableItems.append(CollectableExternalShareItem(url: "https://maps.apple.com/maps?ll=\(latLong)&q=\(latLong)&t=m", text: "", author: nil, timestamp: nil, mediaReference: nil))
case let .messages(messages):
messagesToShare = messages
for message in messages {
var url: String?
var selectedMedia: Media?
@ -701,6 +716,9 @@ public final class ShareController: ViewController {
if let chatPeer = message.peers[message.id.peerId] as? TelegramChannel {
if message.id.namespace == Namespaces.Message.Cloud, let addressName = chatPeer.addressName, !addressName.isEmpty {
url = "https://t.me/\(addressName)/\(message.id.id)"
if messageUrl == nil {
messageUrl = url
}
}
}
let accountPeerId = strongSelf.currentAccount.peerId
@ -739,30 +757,37 @@ public final class ShareController: ViewController {
if let strongSelf = self, !items.isEmpty {
strongSelf._ready.set(.single(true))
var activityItems: [Any] = []
for item in items {
switch item {
case let .url(url):
activityItems.append(url as NSURL)
case let .text(text):
activityItems.append(text as NSString)
case let .image(image):
activityItems.append(image)
case let .file(url, _, _):
activityItems.append(url)
if strongSelf.shareAsLink, let messageUrl = messageUrl, let url = NSURL(string: messageUrl) {
activityItems.append(url)
} else {
for item in items {
switch item {
case let .url(url):
activityItems.append(url as NSURL)
case let .text(text):
activityItems.append(text as NSString)
case let .image(image):
activityItems.append(image)
case let .file(url, _, _):
activityItems.append(url)
}
}
}
let activities: [UIActivity]? = nil
let _ = (strongSelf.didAppearPromise.get()
|> filter { $0 }
|> take(1)
|> deliverOnMainQueue).start(next: { [weak self] _ in
let activityController = UIActivityViewController(activityItems: activityItems, applicationActivities: activities)
if let strongSelf = self, let window = strongSelf.view.window, let rootViewController = window.rootViewController {
activityController.popoverPresentationController?.sourceView = window
activityController.popoverPresentationController?.sourceRect = CGRect(origin: CGPoint(x: window.bounds.width / 2.0, y: window.bounds.size.height - 1.0), size: CGSize(width: 1.0, height: 1.0))
rootViewController.present(activityController, animated: true, completion: nil)
if asImage, let messages = messagesToShare {
self?.openShareAsImage?(messages)
} else {
let activityController = UIActivityViewController(activityItems: activityItems, applicationActivities: activities)
if let strongSelf = self, let window = strongSelf.view.window, let rootViewController = window.rootViewController {
activityController.popoverPresentationController?.sourceView = window
activityController.popoverPresentationController?.sourceRect = CGRect(origin: CGPoint(x: window.bounds.width / 2.0, y: window.bounds.size.height - 1.0), size: CGSize(width: 1.0, height: 1.0))
rootViewController.present(activityController, animated: true, completion: nil)
}
}
})
}

View File

@ -8,6 +8,7 @@ import TelegramCore
import TelegramPresentationData
import AccountContext
import TelegramIntents
import ContextUI
enum ShareState {
case preparing
@ -29,6 +30,7 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate
private let immediateExternalShare: Bool
private var immediatePeerId: PeerId?
private let fromForeignApp: Bool
private let fromPublicChannel: Bool
private let segmentedValues: [ShareControllerSegmentedValue]?
var selectedSegmentedIndex: Int = 0
@ -58,11 +60,12 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate
var dismiss: ((Bool) -> Void)?
var cancel: (() -> Void)?
var share: ((String, [PeerId]) -> Signal<ShareState, NoError>)?
var shareExternal: (() -> Signal<ShareExternalState, NoError>)?
var shareExternal: ((Bool) -> Signal<ShareExternalState, NoError>)?
var switchToAnotherAccount: (() -> Void)?
var debugAction: (() -> Void)?
var openStats: (() -> Void)?
var completed: (([PeerId]) -> Void)?
var present: ((ViewController) -> Void)?
let ready = Promise<Bool>()
private var didSetReady = false
@ -80,7 +83,7 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate
private let presetText: String?
init(sharedContext: SharedAccountContext, presentationData: PresentationData, presetText: String?, defaultAction: ShareControllerAction?, requestLayout: @escaping (ContainedViewLayoutTransition) -> Void, presentError: @escaping (String?, String) -> Void, externalShare: Bool, immediateExternalShare: Bool, immediatePeerId: PeerId?, fromForeignApp: Bool, forceTheme: PresentationTheme?, segmentedValues: [ShareControllerSegmentedValue]?) {
init(sharedContext: SharedAccountContext, presentationData: PresentationData, presetText: String?, defaultAction: ShareControllerAction?, requestLayout: @escaping (ContainedViewLayoutTransition) -> Void, presentError: @escaping (String?, String) -> Void, externalShare: Bool, immediateExternalShare: Bool, immediatePeerId: PeerId?, fromForeignApp: Bool, forceTheme: PresentationTheme?, fromPublicChannel: Bool, segmentedValues: [ShareControllerSegmentedValue]?) {
self.sharedContext = sharedContext
self.presentationData = presentationData
self.forceTheme = forceTheme
@ -89,6 +92,7 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate
self.immediatePeerId = immediatePeerId
self.fromForeignApp = fromForeignApp
self.presentError = presentError
self.fromPublicChannel = fromPublicChannel
self.segmentedValues = segmentedValues
self.presetText = presetText
@ -756,54 +760,75 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate
}
})
}
let openShare: (Bool) -> Void = { [weak self] reportReady in
let openShare: (Bool, ASDisplayNode?, ContextGesture?) -> Void = { [weak self] reportReady, node, gesture in
guard let strongSelf = self, let shareExternal = strongSelf.shareExternal else {
return
}
var loadingTimestamp: Double?
strongSelf.shareDisposable.set((shareExternal() |> deliverOnMainQueue).start(next: { state in
guard let strongSelf = self else {
return
}
switch state {
case .preparing:
if loadingTimestamp == nil {
strongSelf.inputFieldNode.deactivateInput()
let transition = ContainedViewLayoutTransition.animated(duration: 0.12, curve: .easeInOut)
transition.updateAlpha(node: strongSelf.actionButtonNode, alpha: 0.0)
transition.updateAlpha(node: strongSelf.inputFieldNode, alpha: 0.0)
transition.updateAlpha(node: strongSelf.actionSeparatorNode, alpha: 0.0)
transition.updateAlpha(node: strongSelf.actionsBackgroundNode, alpha: 0.0)
strongSelf.transitionToContentNode(ShareLoadingContainerNode(theme: strongSelf.presentationData.theme, forceNativeAppearance: true), fastOut: true)
loadingTimestamp = CACurrentMediaTime()
if reportReady {
strongSelf.ready.set(.single(true))
}
}
case .done:
if let loadingTimestamp = loadingTimestamp {
let minDelay = 0.6
let delay = max(0.0, (loadingTimestamp + minDelay) - CACurrentMediaTime())
Queue.mainQueue().after(delay, {
if let strongSelf = self {
strongSelf.animateOut(shared: true, completion: {
self?.dismiss?(true)
})
let proceed: (Bool) -> Void = { asImage in
var loadingTimestamp: Double?
strongSelf.shareDisposable.set((shareExternal(asImage) |> deliverOnMainQueue).start(next: { state in
guard let strongSelf = self else {
return
}
switch state {
case .preparing:
if loadingTimestamp == nil {
strongSelf.inputFieldNode.deactivateInput()
let transition = ContainedViewLayoutTransition.animated(duration: 0.12, curve: .easeInOut)
transition.updateAlpha(node: strongSelf.actionButtonNode, alpha: 0.0)
transition.updateAlpha(node: strongSelf.inputFieldNode, alpha: 0.0)
transition.updateAlpha(node: strongSelf.actionSeparatorNode, alpha: 0.0)
transition.updateAlpha(node: strongSelf.actionsBackgroundNode, alpha: 0.0)
strongSelf.transitionToContentNode(ShareLoadingContainerNode(theme: strongSelf.presentationData.theme, forceNativeAppearance: true), fastOut: true)
loadingTimestamp = CACurrentMediaTime()
if reportReady {
strongSelf.ready.set(.single(true))
}
})
} else {
if reportReady {
strongSelf.ready.set(.single(true))
}
strongSelf.animateOut(shared: true, completion: {
self?.dismiss?(true)
})
}
}
}))
case .done:
if let loadingTimestamp = loadingTimestamp {
let minDelay = 0.6
let delay = max(0.0, (loadingTimestamp + minDelay) - CACurrentMediaTime())
Queue.mainQueue().after(delay, {
if let strongSelf = self {
strongSelf.animateOut(shared: true, completion: {
self?.dismiss?(true)
})
}
})
} else {
if reportReady {
strongSelf.ready.set(.single(true))
}
strongSelf.animateOut(shared: true, completion: {
self?.dismiss?(true)
})
}
}
}))
}
if strongSelf.fromPublicChannel, let context = strongSelf.context, let node = node as? ContextReferenceContentNode {
let presentationData = strongSelf.presentationData
let items: [ContextMenuItem] = [
.action(ContextMenuActionItem(text: presentationData.strings.Share_ShareAsLink, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Link"), color: theme.contextMenu.primaryColor) }, action: { _, f in
f(.default)
proceed(false)
})),
.action(ContextMenuActionItem(text: presentationData.strings.Share_ShareAsImage, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Image"), color: theme.contextMenu.primaryColor) }, action: { _, f in
f(.default)
proceed(true)
}))
]
let contextController = ContextController(account: context.account, presentationData: presentationData, source: .reference(ShareContextReferenceContentSource(sourceNode: node)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
strongSelf.present?(contextController)
} else {
proceed(false)
}
}
peersContentNode.openShare = {
openShare(false)
peersContentNode.openShare = { node, gesture in
openShare(false, node, gesture)
}
peersContentNode.segmentedSelectedIndexUpdated = { [weak self] index in
if let strongSelf = self, let _ = strongSelf.segmentedValues {
@ -812,7 +837,7 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate
}
}
if self.immediateExternalShare {
openShare(true)
openShare(true, nil, nil)
} else {
self.transitionToContentNode(peersContentNode, animated: animated)
self.ready.set(.single(true))
@ -982,3 +1007,15 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate
}
}
}
private final class ShareContextReferenceContentSource: ContextReferenceContentSource {
private let sourceNode: ContextReferenceContentNode
init(sourceNode: ContextReferenceContentNode) {
self.sourceNode = sourceNode
}
func transitionInfo() -> ContextControllerReferenceViewInfo? {
return ContextControllerReferenceViewInfo(referenceNode: self.sourceNode, contentAreaInScreenSpace: UIScreen.main.bounds)
}
}

View File

@ -13,6 +13,7 @@ import AccountContext
import PeerPresenceStatusManager
import AppBundle
import SegmentedControlNode
import ContextUI
private let subtitleFont = Font.regular(12.0)
@ -96,7 +97,10 @@ final class SharePeersContainerNode: ASDisplayNode, ShareContentContainerNode {
private let contentTitleAccountNode: AvatarNode
private let contentSeparatorNode: ASDisplayNode
private let searchButtonNode: HighlightableButtonNode
private let shareButtonNode: HighlightableButtonNode
private let shareReferenceNode: ContextReferenceContentNode
private let shareContainerNode: ContextControllerSourceNode
private let segmentedNode: SegmentedControlNode
private let segmentedValues: [ShareControllerSegmentedValue]?
@ -104,7 +108,7 @@ final class SharePeersContainerNode: ASDisplayNode, ShareContentContainerNode {
private var contentOffsetUpdated: ((CGFloat, ContainedViewLayoutTransition) -> Void)?
var openSearch: (() -> Void)?
var openShare: (() -> Void)?
var openShare: ((ASDisplayNode, ContextGesture?) -> Void)?
var segmentedSelectedIndexUpdated: ((Int) -> Void)?
private var ensurePeerVisibleOnLayout: PeerId?
@ -183,6 +187,10 @@ final class SharePeersContainerNode: ASDisplayNode, ShareContentContainerNode {
self.shareButtonNode = HighlightableButtonNode()
self.shareButtonNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "Share/ShareIcon"), color: self.theme.actionSheet.controlAccentColor), for: [])
self.shareReferenceNode = ContextReferenceContentNode()
self.shareContainerNode = ContextControllerSourceNode()
self.shareContainerNode.animateScale = false
let segmentedItems: [SegmentedControlItem]
if let segmentedValues = segmentedValues {
segmentedItems = segmentedValues.map { SegmentedControlItem(title: $0.title) }
@ -213,9 +221,19 @@ final class SharePeersContainerNode: ASDisplayNode, ShareContentContainerNode {
self.addSubnode(self.contentTitleAccountNode)
self.addSubnode(self.segmentedNode)
self.addSubnode(self.searchButtonNode)
self.shareContainerNode.addSubnode(self.shareReferenceNode)
self.shareButtonNode.addSubnode(self.shareContainerNode)
self.addSubnode(self.shareButtonNode)
self.addSubnode(self.contentSeparatorNode)
self.shareContainerNode.activated = { [weak self] gesture, _ in
guard let strongSelf = self else {
return
}
strongSelf.openShare?(strongSelf.shareReferenceNode, gesture)
}
let previousItems = Atomic<[SharePeerEntry]?>(value: [])
self.disposable.set((items
@ -376,6 +394,8 @@ final class SharePeersContainerNode: ASDisplayNode, ShareContentContainerNode {
let shareButtonFrame = CGRect(origin: CGPoint(x: size.width - titleButtonSize.width - 12.0, y: titleOffset + 12.0), size: titleButtonSize)
transition.updateFrame(node: self.shareButtonNode, frame: shareButtonFrame)
transition.updateFrame(node: self.shareContainerNode, frame: CGRect(origin: CGPoint(), size: titleButtonSize))
transition.updateFrame(node: self.shareReferenceNode, frame: CGRect(origin: CGPoint(), size: titleButtonSize))
let segmentedSize = self.segmentedNode.updateLayout(.sizeToFit(maximumWidth: size.width - titleButtonSize.width * 2.0, minimumWidth: 160.0, height: 32.0), transition: transition)
transition.updateFrame(node: self.segmentedNode, frame: CGRect(origin: CGPoint(x: floor((size.width - segmentedSize.width) / 2.0), y: titleOffset + 18.0), size: segmentedSize))
@ -446,9 +466,9 @@ final class SharePeersContainerNode: ASDisplayNode, ShareContentContainerNode {
}
@objc func sharePressed() {
self.openShare?()
self.openShare?(self.shareReferenceNode, nil)
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
let nodes: [ASDisplayNode] = [self.searchButtonNode, self.shareButtonNode, self.contentTitleAccountNode]
for node in nodes {

View File

@ -110,7 +110,8 @@ private final class TabBarItemNode: ASDisplayNode {
self.animationContainerNode = ASDisplayNode()
self.animationNode = AnimatedStickerNode()
self.animationNode.automaticallyLoadFirstFrame = true
self.animationNode.autoplay = true
self.animationNode.automaticallyLoadLastFrame = true
self.textImageNode = ASImageNode()
self.textImageNode.isUserInteractionEnabled = false
@ -150,17 +151,11 @@ private final class TabBarItemNode: ASDisplayNode {
return
}
transition.updateAlpha(node: strongSelf.imageNode, alpha: isExtracted ? 0.0 : 1.0)
transition.updateAlpha(node: strongSelf.animationNode, alpha: isExtracted ? 0.0 : 1.0)
transition.updateAlpha(node: strongSelf.textImageNode, alpha: isExtracted ? 0.0 : 1.0)
transition.updateAlpha(node: strongSelf.contextImageNode, alpha: isExtracted ? 1.0 : 0.0)
transition.updateAlpha(node: strongSelf.contextTextImageNode, alpha: isExtracted ? 1.0 : 0.0)
}
/*let leftSwipe = UISwipeGestureRecognizer(target: self, action: #selector(self.swipeGesture(_:)))
leftSwipe.direction = .left
self.containerNode.view.addGestureRecognizer(leftSwipe)
let rightSwipe = UISwipeGestureRecognizer(target: self, action: #selector(self.swipeGesture(_:)))
rightSwipe.direction = .right
self.containerNode.view.addGestureRecognizer(rightSwipe)*/
}
override func didLoad() {

View File

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

View File

@ -0,0 +1,154 @@
%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 3.835022 3.834961 cm
0.000000 0.000000 0.000000 scn
5.465000 16.330017 m
5.436178 16.330017 l
5.436174 16.330017 l
4.620535 16.330023 3.967872 16.330027 3.440454 16.286936 c
2.899074 16.242702 2.431364 16.149773 2.001125 15.930555 c
1.311511 15.579180 0.750837 15.018506 0.399462 14.328892 c
0.180244 13.898653 0.087314 13.430943 0.043082 12.889563 c
-0.000010 12.362144 -0.000006 11.709482 0.000000 10.893843 c
0.000000 10.893839 l
0.000000 10.865017 l
0.000000 5.465017 l
0.000000 5.436195 l
-0.000006 4.620555 -0.000010 3.967890 0.043082 3.440471 c
0.087314 2.899090 0.180244 2.431380 0.399462 2.001142 c
0.750837 1.311528 1.311511 0.750854 2.001125 0.399478 c
2.431364 0.180260 2.899074 0.087330 3.440454 0.043098 c
3.967863 0.000008 4.620512 0.000011 5.436130 0.000017 c
5.436194 0.000017 l
5.465001 0.000017 l
10.865000 0.000017 l
10.893806 0.000017 l
10.893871 0.000017 l
11.709489 0.000011 12.362138 0.000008 12.889546 0.043098 c
13.430927 0.087330 13.898637 0.180260 14.328876 0.399478 c
15.018489 0.750854 15.579163 1.311528 15.930539 2.001142 c
16.149757 2.431380 16.242687 2.899091 16.286919 3.440471 c
16.330009 3.967880 16.330006 4.620529 16.330000 5.436146 c
16.330000 5.436212 l
16.330000 5.465017 l
16.330000 10.865017 l
16.330000 10.893824 l
16.330000 10.893888 l
16.330006 11.709505 16.330009 12.362154 16.286919 12.889563 c
16.242687 13.430943 16.149757 13.898653 15.930539 14.328892 c
15.579163 15.018506 15.018489 15.579180 14.328876 15.930555 c
13.898637 16.149773 13.430926 16.242702 12.889546 16.286936 c
12.362126 16.330027 11.709461 16.330023 10.893822 16.330017 c
10.865000 16.330017 l
5.465000 16.330017 l
h
2.604933 14.745517 m
2.816429 14.853280 3.089627 14.923840 3.548759 14.961352 c
4.015654 14.999499 4.613948 15.000016 5.465000 15.000016 c
10.865000 15.000016 l
11.716052 15.000016 12.314346 14.999499 12.781241 14.961352 c
13.240374 14.923840 13.513572 14.853280 13.725068 14.745517 c
14.164426 14.521652 14.521636 14.164443 14.745501 13.725084 c
14.853263 13.513588 14.923823 13.240391 14.961336 12.781259 c
14.999483 12.314363 15.000000 11.716068 15.000000 10.865017 c
15.000000 5.465017 l
15.000000 5.137442 14.999924 4.847313 14.997654 4.587710 c
12.903418 6.817746 l
12.230759 7.534022 11.087341 7.515037 10.438833 6.776826 c
9.645941 5.874260 l
5.897148 9.831320 l
5.226022 10.539732 4.092310 10.521740 3.444000 9.792391 c
1.330001 7.414142 l
1.330001 10.865017 l
1.330001 11.716068 1.330518 12.314363 1.368665 12.781259 c
1.406177 13.240391 1.476737 13.513588 1.584500 13.725084 c
1.808365 14.164443 2.165574 14.521652 2.604933 14.745517 c
h
11.933909 5.907277 m
14.833861 2.819281 l
14.807792 2.739742 14.778378 2.669474 14.745501 2.604949 c
14.521636 2.165591 14.164426 1.808381 13.725068 1.584517 c
13.714809 1.579343 l
10.564494 4.904676 l
11.438032 5.899043 l
11.568513 6.047573 11.798571 6.051393 11.933909 5.907277 c
h
1.330001 5.412228 m
1.330038 4.589128 1.331311 4.005958 1.368665 3.548775 c
1.406177 3.089643 1.476737 2.816445 1.584500 2.604949 c
1.808365 2.165591 2.165574 1.808381 2.604933 1.584517 c
2.816429 1.476754 3.089627 1.406194 3.548759 1.368681 c
4.015654 1.330534 4.613949 1.330017 5.465001 1.330017 c
10.865000 1.330017 l
11.357291 1.330017 11.765009 1.330190 12.111642 1.337719 c
4.931631 8.916620 l
4.796600 9.059153 4.568496 9.055533 4.438055 8.908787 c
1.330001 5.412228 l
h
11.664969 10.165024 m
12.493397 10.165024 13.164969 10.836597 13.164969 11.665024 c
13.164969 12.493451 12.493397 13.165024 11.664969 13.165024 c
10.836542 13.165024 10.164969 12.493451 10.164969 11.665024 c
10.164969 10.836597 10.836542 10.165024 11.664969 10.165024 c
h
f*
n
Q
endstream
endobj
3 0 obj
3694
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
0000003784 00000 n
0000003807 00000 n
0000003980 00000 n
0000004054 00000 n
trailer
<< /ID [ (some) (id) ]
/Root 6 0 R
/Size 7
>>
startxref
4113
%%EOF

File diff suppressed because one or more lines are too long

View File

@ -1965,7 +1965,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
})
}, openMessageShareMenu: { [weak self] id in
if let strongSelf = self, let messages = strongSelf.chatDisplayNode.historyNode.messageGroupInCurrentHistoryView(id), let _ = messages.first {
let shareController = ShareController(context: strongSelf.context, subject: .messages(messages), updatedPresentationData: strongSelf.updatedPresentationData)
let shareController = ShareController(context: strongSelf.context, subject: .messages(messages), updatedPresentationData: strongSelf.updatedPresentationData, shareAsLink: true)
shareController.openShareAsImage = { [weak self] messages in
if let strongSelf = self {
strongSelf.present(ChatQrCodeScreen(context: strongSelf.context, subject: .messages(messages)), in: .window(.root))
}
}
shareController.dismissed = { [weak self] shared in
if shared {
self?.commitPurposefulAction()

View File

@ -292,6 +292,8 @@ final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode {
}
} else if let type = webpage.type {
switch type {
case "telegram_user":
actionTitle = item.presentationData.strings.Conversation_UserSendMessage
case "telegram_channel_request":
actionTitle = item.presentationData.strings.Conversation_RequestToJoinChannel
case "telegram_chat_request", "telegram_megagroup_request":

View File

@ -24,6 +24,9 @@ import WallpaperBackgroundNode
import QrCode
import AvatarNode
import ShareController
import TelegramStringFormatting
import PhotoResources
import TextFormat
private func closeButtonImage(theme: PresentationTheme) -> UIImage? {
return generateImage(CGSize(width: 30.0, height: 30.0), contextGenerator: { size, context in
@ -56,6 +59,7 @@ private struct ThemeSettingsThemeEntry: Comparable, Identifiable {
let theme: PresentationTheme
let strings: PresentationStrings
let wallpaper: TelegramWallpaper?
let message: Bool
var stableId: Int {
return index
@ -87,6 +91,9 @@ private struct ThemeSettingsThemeEntry: Comparable, Identifiable {
if lhs.wallpaper != rhs.wallpaper {
return false
}
if lhs.message != rhs.message {
return false
}
return true
}
@ -95,7 +102,7 @@ private struct ThemeSettingsThemeEntry: Comparable, Identifiable {
}
func item(context: AccountContext, action: @escaping (String?) -> Void) -> ListViewItem {
return ThemeSettingsThemeIconItem(context: context, emoticon: self.emoticon, emojiFile: self.emojiFile, themeReference: self.themeReference, nightMode: self.nightMode, selected: self.selected, theme: self.theme, strings: self.strings, wallpaper: self.wallpaper, action: action)
return ThemeSettingsThemeIconItem(context: context, emoticon: self.emoticon, emojiFile: self.emojiFile, themeReference: self.themeReference, nightMode: self.nightMode, selected: self.selected, theme: self.theme, strings: self.strings, wallpaper: self.wallpaper, message: self.message, action: action)
}
}
@ -110,9 +117,10 @@ private class ThemeSettingsThemeIconItem: ListViewItem {
let theme: PresentationTheme
let strings: PresentationStrings
let wallpaper: TelegramWallpaper?
let message: Bool
let action: (String?) -> Void
public init(context: AccountContext, emoticon: String?, emojiFile: TelegramMediaFile?, themeReference: PresentationThemeReference?, nightMode: Bool, selected: Bool, theme: PresentationTheme, strings: PresentationStrings, wallpaper: TelegramWallpaper?, action: @escaping (String?) -> Void) {
public init(context: AccountContext, emoticon: String?, emojiFile: TelegramMediaFile?, themeReference: PresentationThemeReference?, nightMode: Bool, selected: Bool, theme: PresentationTheme, strings: PresentationStrings, wallpaper: TelegramWallpaper?, message: Bool, action: @escaping (String?) -> Void) {
self.context = context
self.emoticon = emoticon
self.emojiFile = emojiFile
@ -122,6 +130,7 @@ private class ThemeSettingsThemeIconItem: ListViewItem {
self.theme = theme
self.strings = strings
self.wallpaper = wallpaper
self.message = message
self.action = action
}
@ -433,7 +442,7 @@ private final class ThemeSettingsThemeItemIconNode : ListViewItemNode {
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, qr: 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, qr: !item.message, message: item.message))
strongSelf.imageNode.backgroundColor = nil
}
}
@ -548,6 +557,26 @@ final class ChatQrCodeScreen: ViewController {
static let themeCrossfadeDuration: Double = 0.3
static let themeCrossfadeDelay: Double = 0.05
enum Subject {
case peer(Peer)
case messages([Message])
var fileName: String {
switch self {
case let .peer(peer):
return "t_me-\(peer.addressName ?? "")"
case let .messages(messages):
if let message = messages.first, let chatPeer = message.peers[message.id.peerId] as? TelegramChannel, message.id.namespace == Namespaces.Message.Cloud, let addressName = chatPeer.addressName, !addressName.isEmpty {
return "t_me-\(addressName)-\(message.id.id)"
} else if let message = messages.first {
return "t_me-\(message.id.id)"
} else {
return "telegram_message"
}
}
}
}
private var controllerNode: ChatQrCodeScreenNode {
return self.displayNode as! ChatQrCodeScreenNode
}
@ -555,7 +584,7 @@ final class ChatQrCodeScreen: ViewController {
private var animatedIn = false
private let context: AccountContext
private let peer: Peer
fileprivate let subject: ChatQrCodeScreen.Subject
private var presentationData: PresentationData
private var presentationThemePromise = Promise<PresentationTheme?>()
@ -563,10 +592,10 @@ final class ChatQrCodeScreen: ViewController {
var dismissed: (() -> Void)?
init(context: AccountContext, peer: Peer) {
init(context: AccountContext, subject: ChatQrCodeScreen.Subject) {
self.context = context
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
self.peer = peer
self.subject = subject
super.init(navigationBarPresentationData: nil)
@ -603,7 +632,7 @@ final class ChatQrCodeScreen: ViewController {
}
override public func loadDisplayNode() {
self.displayNode = ChatQrCodeScreenNode(context: self.context, presentationData: self.presentationData, controller: self, peer: self.peer)
self.displayNode = ChatQrCodeScreenNode(context: self.context, presentationData: self.presentationData, controller: self)
self.controllerNode.previewTheme = { [weak self] _, _, theme in
self?.presentationThemePromise.set(.single(theme))
}
@ -686,7 +715,7 @@ private class ChatQrCodeScreenNode: ViewControllerTracingNode, UIScrollViewDeleg
private var presentationData: PresentationData
private weak var controller: ChatQrCodeScreen?
private let contentNode: QrContentNode
private let contentNode: ContentNode
private let wrappingScrollNode: ASScrollNode
private let contentContainerNode: ASDisplayNode
@ -709,8 +738,6 @@ private class ChatQrCodeScreenNode: ViewControllerTracingNode, UIScrollViewDeleg
let ready = Promise<Bool>()
private let peer: Peer
private var initiallySelectedEmoticon: String?
private var selectedEmoticon: String? = nil {
didSet {
@ -736,10 +763,9 @@ private class ChatQrCodeScreenNode: ViewControllerTracingNode, UIScrollViewDeleg
var dismiss: (() -> Void)?
var cancel: (() -> Void)?
init(context: AccountContext, presentationData: PresentationData, controller: ChatQrCodeScreen, peer: Peer) {
init(context: AccountContext, presentationData: PresentationData, controller: ChatQrCodeScreen) {
self.context = context
self.controller = controller
self.peer = peer
self.presentationData = presentationData
self.wrappingScrollNode = ASScrollNode()
@ -747,7 +773,13 @@ private class ChatQrCodeScreenNode: ViewControllerTracingNode, UIScrollViewDeleg
self.wrappingScrollNode.view.delaysContentTouches = false
self.wrappingScrollNode.view.canCancelContentTouches = true
self.contentNode = QrContentNode(context: context, peer: peer, isStatic: false)
switch controller.subject {
case let .peer(peer):
self.contentNode = QrContentNode(context: context, peer: peer, isStatic: false)
case let .messages(messages):
self.contentNode = MessageContentNode(context: context, messages: messages)
}
self.contentContainerNode = ASDisplayNode()
self.contentContainerNode.isOpaque = false
@ -774,7 +806,14 @@ private class ChatQrCodeScreenNode: ViewControllerTracingNode, UIScrollViewDeleg
self.contentBackgroundNode.backgroundColor = backgroundColor
self.titleNode = ASTextNode()
self.titleNode.attributedText = NSAttributedString(string: self.presentationData.strings.PeerInfo_QRCode_Title, font: Font.semibold(16.0), textColor: textColor)
let title: String
switch controller.subject {
case .peer:
title = self.presentationData.strings.PeerInfo_QRCode_Title
case .messages:
title = self.presentationData.strings.Share_MessagePreview
}
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(16.0), textColor: textColor)
self.cancelButton = HighlightableButtonNode()
self.cancelButton.setImage(closeButtonImage(theme: self.presentationData.theme), for: .normal)
@ -787,7 +826,12 @@ private class ChatQrCodeScreenNode: ViewControllerTracingNode, UIScrollViewDeleg
self.animationNode.isUserInteractionEnabled = false
self.doneButton = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(theme: self.presentationData.theme), height: 52.0, cornerRadius: 11.0, gloss: false)
self.doneButton.title = self.presentationData.strings.InviteLink_QRCode_Share
switch controller.subject {
case .peer:
self.doneButton.title = self.presentationData.strings.InviteLink_QRCode_Share
case .messages:
self.doneButton.title = self.presentationData.strings.Share_ShareMessage
}
self.listNode = ListView()
self.listNode.transform = CATransform3DMakeRotation(-CGFloat.pi / 2.0, 0.0, 0.0, 1.0)
@ -818,13 +862,15 @@ private class ChatQrCodeScreenNode: ViewControllerTracingNode, UIScrollViewDeleg
self.switchThemeButton.addTarget(self, action: #selector(self.switchThemePressed), forControlEvents: .touchUpInside)
self.cancelButton.addTarget(self, action: #selector(self.cancelButtonPressed), forControlEvents: .touchUpInside)
let fileName = controller.subject.fileName
self.doneButton.pressed = { [weak self] in
if let strongSelf = self {
strongSelf.doneButton.isUserInteractionEnabled = false
strongSelf.contentNode.generateImage { [weak self] image in
if let strongSelf = self, let image = image, let jpgData = image.jpegData(compressionQuality: 0.9) {
let tempFilePath = NSTemporaryDirectory() + "t_me-\(peer.addressName ?? "").jpg"
let tempFilePath = NSTemporaryDirectory() + "\(fileName).jpg"
try? FileManager.default.removeItem(atPath: tempFilePath)
let tempFileUrl = URL(fileURLWithPath: tempFilePath)
try? jpgData.write(to: tempFileUrl)
@ -872,18 +918,7 @@ private class ChatQrCodeScreenNode: ViewControllerTracingNode, UIScrollViewDeleg
let initiallySelectedEmoticon: Signal<String, NoError>
let sharedData = self.context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.presentationThemeSettings])
|> take(1)
if self.peer.id == self.context.account.peerId {
initiallySelectedEmoticon = sharedData
|> map { sharedData -> String in
let themeSettings: PresentationThemeSettings
if let current = sharedData.entries[ApplicationSpecificSharedDataKeys.presentationThemeSettings]?.get(PresentationThemeSettings.self) {
themeSettings = current
} else {
themeSettings = PresentationThemeSettings.defaultSettings
}
return themeSettings.theme.emoticon ?? defaultEmoticon
}
} else {
if case let .peer(peer) = controller.subject, peer.id != self.context.account.peerId {
let cachedData = self.context.account.postbox.transaction { transaction in
return transaction.getPeerCachedData(peerId: peer.id)
}
@ -907,8 +942,23 @@ private class ChatQrCodeScreenNode: ViewControllerTracingNode, UIScrollViewDeleg
return currentDefaultEmoticon
}
}
} else {
initiallySelectedEmoticon = sharedData
|> map { sharedData -> String in
let themeSettings: PresentationThemeSettings
if let current = sharedData.entries[ApplicationSpecificSharedDataKeys.presentationThemeSettings]?.get(PresentationThemeSettings.self) {
themeSettings = current
} else {
themeSettings = PresentationThemeSettings.defaultSettings
}
return themeSettings.theme.emoticon ?? defaultEmoticon
}
}
var isMessage = false
if case .messages = controller.subject {
isMessage = true
}
self.disposable.set(combineLatest(queue: Queue.mainQueue(), animatedEmojiStickers, initiallySelectedEmoticon, self.context.engine.themes.getChatThemes(accountManager: self.context.sharedContext.accountManager), self.selectedEmoticonPromise.get(), self.isDarkAppearancePromise.get()).start(next: { [weak self] animatedEmojiStickers, initiallySelectedEmoticon, themes, selectedEmoticon, isDarkAppearance in
guard let strongSelf = self else {
return
@ -933,15 +983,16 @@ private class ChatQrCodeScreenNode: ViewControllerTracingNode, UIScrollViewDeleg
} else {
defaultWallpaper = nil
}
entries.append(ThemeSettingsThemeEntry(index: 0, emoticon: defaultEmoticon, emojiFile: animatedEmojiStickers[defaultEmoticon]?.first?.file, themeReference: .builtin(isDarkAppearance ? .night : .dayClassic), nightMode: isDarkAppearance, selected: selectedEmoticon == defaultEmoticon, theme: presentationData.theme, strings: presentationData.strings, wallpaper: defaultWallpaper))
entries.append(ThemeSettingsThemeEntry(index: 0, emoticon: defaultEmoticon, emojiFile: animatedEmojiStickers[defaultEmoticon]?.first?.file, themeReference: .builtin(isDarkAppearance ? .night : .dayClassic), nightMode: isDarkAppearance, selected: selectedEmoticon == defaultEmoticon, theme: presentationData.theme, strings: presentationData.strings, wallpaper: defaultWallpaper, message: isMessage))
for theme in themes {
guard let emoticon = theme.emoticon else {
continue
}
entries.append(ThemeSettingsThemeEntry(index: entries.count, emoticon: emoticon, emojiFile: animatedEmojiStickers[emoticon]?.first?.file, themeReference: .cloud(PresentationCloudTheme(theme: theme, resolvedWallpaper: nil, creatorAccountId: nil)), nightMode: isDarkAppearance, selected: selectedEmoticon == theme.emoticon, theme: presentationData.theme, strings: presentationData.strings, wallpaper: nil))
entries.append(ThemeSettingsThemeEntry(index: entries.count, emoticon: emoticon, emojiFile: animatedEmojiStickers[emoticon]?.first?.file, themeReference: .cloud(PresentationCloudTheme(theme: theme, resolvedWallpaper: nil, creatorAccountId: nil)), nightMode: isDarkAppearance, selected: selectedEmoticon == theme.emoticon, theme: presentationData.theme, strings: presentationData.strings, wallpaper: nil, message: isMessage))
}
let wallpaper: TelegramWallpaper
let previewTheme: PresentationTheme
if selectedEmoticon == defaultEmoticon {
let presentationTheme = makeDefaultPresentationTheme(reference: isDarkAppearance ? .night : .dayClassic, serviceBackgroundColor: nil)
if isDarkAppearance {
@ -949,10 +1000,13 @@ private class ChatQrCodeScreenNode: ViewControllerTracingNode, UIScrollViewDeleg
} else {
wallpaper = presentationTheme.chat.defaultWallpaper
}
previewTheme = presentationTheme
} else if let theme = themes.first(where: { $0.emoticon == selectedEmoticon }), let presentationTheme = makePresentationTheme(cloudTheme: theme, dark: isDarkAppearance) {
wallpaper = presentationTheme.chat.defaultWallpaper
previewTheme = presentationTheme
} else {
wallpaper = .color(0x000000)
previewTheme = defaultPresentationTheme
}
let action: (String?) -> Void = { [weak self] emoticon in
@ -982,7 +1036,7 @@ private class ChatQrCodeScreenNode: ViewControllerTracingNode, UIScrollViewDeleg
strongSelf.entries = entries
strongSelf.themes = themes
strongSelf.contentNode.update(wallpaper: wallpaper, isDarkAppearance: isDarkAppearance, selectedEmoticon: selectedEmoticon)
strongSelf.contentNode.update(theme: previewTheme, wallpaper: wallpaper, isDarkAppearance: isDarkAppearance, selectedEmoticon: selectedEmoticon)
if isFirstTime {
for theme in themes {
@ -1292,7 +1346,17 @@ private class ChatQrCodeScreenNode: ViewControllerTracingNode, UIScrollViewDeleg
}
}
private class QrContentNode: ASDisplayNode {
private protocol ContentNode: ASDisplayNode {
var containerNode: ASDisplayNode { get }
var wallpaperBackgroundNode: WallpaperBackgroundNode { get }
var isReady: Signal<Bool, NoError> { get }
func generateImage(completion: @escaping (UIImage?) -> Void)
func update(theme: PresentationTheme, wallpaper: TelegramWallpaper, isDarkAppearance: Bool, selectedEmoticon: String?)
func updateLayout(size: CGSize, topInset: CGFloat, bottomInset: CGFloat, transition: ContainedViewLayoutTransition)
}
private class QrContentNode: ASDisplayNode, ContentNode {
private let context: AccountContext
private let peer: Peer
private let isStatic: Bool
@ -1312,7 +1376,7 @@ private class QrContentNode: ASDisplayNode {
private let avatarNode: ImageNode
private var qrCodeSize: Int?
private var currentParams: (TelegramWallpaper, Bool, String?)?
private var currentParams: (PresentationTheme, TelegramWallpaper, Bool, String?)?
private var validLayout: (CGSize, CGFloat, CGFloat)?
private let _ready = Promise<Bool>()
@ -1425,7 +1489,7 @@ private class QrContentNode: ASDisplayNode {
}
func generateImage(completion: @escaping (UIImage?) -> Void) {
guard let (wallpaper, isDarkAppearance, selectedEmoticon) = self.currentParams else {
guard let (theme, wallpaper, isDarkAppearance, selectedEmoticon) = self.currentParams else {
return
}
@ -1443,11 +1507,10 @@ private class QrContentNode: ASDisplayNode {
prepare(view: copyNode.view, scale: scale)
copyNode.updateLayout(size: size, topInset: 0.0, bottomInset: 0.0, transition: .immediate)
copyNode.update(wallpaper: wallpaper, isDarkAppearance: isDarkAppearance, selectedEmoticon: selectedEmoticon)
copyNode.update(theme: theme, wallpaper: wallpaper, isDarkAppearance: isDarkAppearance, selectedEmoticon: selectedEmoticon)
copyNode.frame = CGRect(x: -1000, y: -1000, width: size.width, height: size.height)
self.addSubnode(copyNode)
let _ = (copyNode.isReady
|> take(1)
@ -1473,8 +1536,8 @@ private class QrContentNode: ASDisplayNode {
})
}
func update(wallpaper: TelegramWallpaper, isDarkAppearance: Bool, selectedEmoticon: String?) {
self.currentParams = (wallpaper, isDarkAppearance, selectedEmoticon)
func update(theme: PresentationTheme, wallpaper: TelegramWallpaper, isDarkAppearance: Bool, selectedEmoticon: String?) {
self.currentParams = (theme, wallpaper, isDarkAppearance, selectedEmoticon)
self.wallpaperBackgroundNode.update(wallpaper: wallpaper)
@ -1555,7 +1618,7 @@ private class QrContentNode: ASDisplayNode {
let imageFrame = CGRect(origin: CGPoint(x: floor((codeBackgroundFrame.width - imageSize.width) / 2.0), y: floor((codeBackgroundFrame.width - imageSize.height) / 2.0)), size: imageSize)
transition.updateFrame(node: self.codeImageNode, frame: imageFrame)
let codeTextSize = self.codeTextNode.updateLayout(CGSize(width: codeBackgroundFrame.width - floor(imageFrame.minX * 1.5), height: codeBackgroundFrame.height))
let codeTextSize = self.codeTextNode.updateLayout(CGSize(width: codeBackgroundFrame.width - floor(imageFrame.minX * 1.2), height: codeBackgroundFrame.height))
transition.updateFrame(node: self.codeTextNode, frame: CGRect(origin: CGPoint(x: floor((codeBackgroundFrame.width - codeTextSize.width) / 2.0), y: imageFrame.maxY + floor((codeBackgroundHeight - imageFrame.maxY - codeTextSize.height) / 2.0) - 5.0), size: codeTextSize))
transition.updateFrame(node: self.avatarNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - avatarSize.width) / 2.0), y: codeBackgroundFrame.minY - floor(avatarSize.height * 0.7)), size: avatarSize))
@ -1582,3 +1645,295 @@ private class QrContentNode: ASDisplayNode {
}
}
}
private class MessageContentNode: ASDisplayNode, ContentNode {
private let context: AccountContext
private let messages: [Message]
fileprivate let containerNode: ASDisplayNode
fileprivate let wallpaperBackgroundNode: WallpaperBackgroundNode
private let backgroundNode: ASDisplayNode
private let backgroundImageNode: ASImageNode
private let avatarNode: ImageNode
private let titleNode: ImmediateTextNode
private let dateNode: ImmediateTextNode
private let imageNode: TransformImageNode
private let textNode: ImmediateTextNode
private let linkBackgroundNode: ASDisplayNode
private var linkBackgroundContentNode: ASDisplayNode?
private var linkBackgroundDimNode: ASDisplayNode
private let linkIconNode: ASImageNode
private let linkTextNode: ImmediateTextNode
private var currentParams: (PresentationTheme, TelegramWallpaper, Bool, String?)?
private var validLayout: (CGSize, CGFloat, CGFloat)?
private let _ready = Promise<Bool>()
var isReady: Signal<Bool, NoError> {
return self._ready.get()
}
init(context: AccountContext, messages: [Message]) {
self.context = context
self.messages = messages
self.containerNode = ASDisplayNode()
self.wallpaperBackgroundNode = createWallpaperBackgroundNode(context: context, forChatDisplay: true, useSharedAnimationPhase: false, useExperimentalImplementation: context.sharedContext.immediateExperimentalUISettings.experimentalBackground)
self.backgroundNode = ASDisplayNode()
self.backgroundImageNode = ASImageNode()
self.avatarNode = ImageNode()
self.titleNode = ImmediateTextNode()
self.dateNode = ImmediateTextNode()
self.textNode = ImmediateTextNode()
self.textNode.maximumNumberOfLines = 0
self.imageNode = TransformImageNode()
self.linkBackgroundNode = ASDisplayNode()
self.linkBackgroundNode.clipsToBounds = true
self.linkBackgroundNode.cornerRadius = 16.5
self.linkBackgroundDimNode = ASDisplayNode()
self.linkBackgroundDimNode.alpha = 0.3
self.linkBackgroundDimNode.backgroundColor = .black
self.linkIconNode = ASImageNode()
self.linkIconNode.displaysAsynchronously = false
self.linkIconNode.image = UIImage(bundleImageName: "Share/QrPlaneIcon")
self.linkTextNode = ImmediateTextNode()
self.linkTextNode.textAlignment = .center
super.init()
self.addSubnode(self.containerNode)
self.containerNode.addSubnode(self.wallpaperBackgroundNode)
self.containerNode.addSubnode(self.linkBackgroundNode)
self.containerNode.addSubnode(self.backgroundNode)
self.backgroundNode.addSubnode(self.backgroundImageNode)
self.backgroundNode.addSubnode(self.avatarNode)
self.backgroundNode.addSubnode(self.titleNode)
self.backgroundNode.addSubnode(self.dateNode)
self.backgroundNode.addSubnode(self.textNode)
self.backgroundNode.addSubnode(self.imageNode)
self.linkBackgroundNode.addSubnode(self.linkBackgroundDimNode)
self.linkBackgroundNode.addSubnode(self.linkIconNode)
self.linkBackgroundNode.addSubnode(self.linkTextNode)
self._ready.set(self.wallpaperBackgroundNode.isReady)
if let message = messages.first, let peer = message.peers[message.id.peerId] {
self.avatarNode.setSignal(peerAvatarCompleteImage(account: context.account, peer: EnginePeer(peer), size: CGSize(width: 36.0, height: 36.0), font: avatarPlaceholderFont(size: 14.0), fullSize: true))
}
}
func generateImage(completion: @escaping (UIImage?) -> Void) {
guard let (theme, wallpaper, isDarkAppearance, selectedEmoticon) = self.currentParams else {
return
}
let size = CGSize(width: 390.0, height: 844.0)
let scale: CGFloat = 3.0
let copyNode = MessageContentNode(context: self.context, messages: self.messages)
func prepare(view: UIView, scale: CGFloat) {
view.contentScaleFactor = scale
for subview in view.subviews {
prepare(view: subview, scale: scale)
}
}
prepare(view: copyNode.view, scale: scale)
copyNode.updateLayout(size: size, topInset: 0.0, bottomInset: 0.0, transition: .immediate)
copyNode.update(theme: theme, wallpaper: wallpaper, isDarkAppearance: isDarkAppearance, selectedEmoticon: selectedEmoticon)
copyNode.frame = CGRect(x: -1000, y: -1000, width: size.width, height: size.height)
self.addSubnode(copyNode)
let _ = (copyNode.isReady
|> take(1)
|> deliverOnMainQueue).start(next: { [weak copyNode] _ in
Queue.mainQueue().after(0.1) {
if #available(iOS 10.0, *) {
let format = UIGraphicsImageRendererFormat()
format.scale = scale
let renderer = UIGraphicsImageRenderer(size: size, format: format)
let image = renderer.image { rendererContext in
copyNode?.containerNode.layer.render(in: rendererContext.cgContext)
}
completion(image)
} else {
UIGraphicsBeginImageContextWithOptions(size, true, scale)
copyNode?.containerNode.view.drawHierarchy(in: CGRect(origin: CGPoint(), size: size), afterScreenUpdates: true)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
completion(image)
}
copyNode?.removeFromSupernode()
}
})
}
func update(theme: PresentationTheme, wallpaper: TelegramWallpaper, isDarkAppearance: Bool, selectedEmoticon: String?) {
self.currentParams = (theme, wallpaper, isDarkAppearance, selectedEmoticon)
self.wallpaperBackgroundNode.update(wallpaper: wallpaper)
self.linkBackgroundDimNode.alpha = isDarkAppearance ? 0.6 : 0.2
if self.linkBackgroundContentNode == nil, let contentNode = self.wallpaperBackgroundNode.makeDimmedNode() {
contentNode.frame = CGRect(origin: CGPoint(x: -self.linkBackgroundNode.frame.minX, y: -self.linkBackgroundNode.frame.minY), size: self.wallpaperBackgroundNode.frame.size)
self.linkBackgroundContentNode = contentNode
self.linkBackgroundNode.insertSubnode(contentNode, at: 0)
}
if let (size, topInset, bottomInset) = self.validLayout {
self.updateLayout(size: size, topInset: topInset, bottomInset: bottomInset, transition: .immediate)
}
}
private var initialized = false
func updateLayout(size: CGSize, topInset: CGFloat, bottomInset: CGFloat, transition: ContainedViewLayoutTransition) {
self.validLayout = (size, topInset, bottomInset)
guard let (theme, wallpaper, _, _) = self.currentParams else {
return
}
let graphics = PresentationResourcesChat.principalGraphics(theme: theme, wallpaper: wallpaper, bubbleCorners: defaultPresentationData().chatBubbleCorners)
self.backgroundImageNode.image = graphics.chatMessageBackgroundIncomingImage
let wasInitialized = self.initialized
self.initialized = true
transition.updateFrame(node: self.containerNode, frame: CGRect(origin: CGPoint(), size: size))
transition.updateFrame(node: self.wallpaperBackgroundNode, frame: CGRect(origin: CGPoint(), size: size))
self.wallpaperBackgroundNode.updateLayout(size: size, transition: transition)
let inset: CGFloat = 24.0
let contentInset: CGFloat = 16.0
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
let messageTheme = theme.chat.message.incoming
let title: String
if let message = self.messages.first, let chatPeer = message.peers[message.id.peerId] as? TelegramChannel {
title = EnginePeer(chatPeer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
} else {
title = ""
}
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.medium(17.0), textColor: messageTheme.primaryTextColor)
let titleSize = self.titleNode.updateLayout(size)
let titleFrame = CGRect(origin: CGPoint(x: 62.0, y: 14.0), size: titleSize)
self.titleNode.frame = titleFrame
let dateString: String
if let message = self.messages.first {
dateString = stringForMediumDate(timestamp: message.timestamp, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat)
} else {
dateString = ""
}
self.dateNode.attributedText = NSAttributedString(string: dateString, font: Font.regular(14.0), textColor: messageTheme.secondaryTextColor)
let dateSize = self.dateNode.updateLayout(size)
let dateFrame = CGRect(origin: CGPoint(x: 62.0, y: 35.0), size: dateSize)
self.dateNode.frame = dateFrame
self.avatarNode.frame = CGRect(x: contentInset, y: 14.0, width: 36.0, height: 36.0)
var mediaSize = CGSize()
if let message = self.messages.first {
for media in message.media {
if let image = media as? TelegramMediaImage, let representation = largestRepresentationForPhoto(image) {
mediaSize = representation.dimensions.cgSize.aspectFitted(CGSize(width: size.width - inset * 2.0 - 5.0, height: size.width - inset * 2.0))
if !wasInitialized {
self.imageNode.setSignal(chatMessagePhoto(postbox: self.context.account.postbox, photoReference: .message(message: MessageReference(message), media: image), synchronousLoad: true, highQuality: true))
let imageLayout = self.imageNode.asyncLayout()
let arguments = TransformImageArguments(corners: ImageCorners(radius: 0.0), imageSize: mediaSize, boundingSize: mediaSize, intrinsicInsets: UIEdgeInsets(), resizeMode: .blurBackground, emptyColor: .black, custom: nil)
let imageApply = imageLayout(arguments)
imageApply()
}
self.imageNode.frame = CGRect(origin: CGPoint(x: 3.0, y: 60.0), size: mediaSize)
mediaSize.height += 10.0
}
}
}
if let message = messages.first {
let textFont = Font.regular(17.0)
let boldFont = Font.bold(17.0)
let italicFont = Font.italic(17.0)
let boldItalicFont = Font.semiboldItalic(17.0)
let fixedFont = Font.monospace(17.0)
let blockQuoteFont = textFont
var entities: [MessageTextEntity]?
for attribute in message.attributes {
if let attribute = attribute as? TextEntitiesMessageAttribute {
entities = attribute.entities
}
}
let attributedText: NSAttributedString
if let entities = entities {
attributedText = stringWithAppliedEntities(message.text, entities: entities, baseColor: messageTheme.primaryTextColor, linkColor: messageTheme.linkTextColor, baseFont: textFont, linkFont: textFont, boldFont: boldFont, italicFont: italicFont, boldItalicFont: boldItalicFont, fixedFont: fixedFont, blockQuoteFont: blockQuoteFont, underlineLinks: false)
} else if !message.text.isEmpty {
attributedText = NSAttributedString(string: message.text, font: textFont, textColor: messageTheme.primaryTextColor)
} else {
attributedText = NSAttributedString(string: " ", font: textFont, textColor: messageTheme.primaryTextColor)
}
self.textNode.attributedText = attributedText
}
let textSize = self.textNode.updateLayout(CGSize(width: size.width - inset * 2.0 - contentInset * 2.0, height: size.height))
let textFrame = CGRect(origin: CGPoint(x: contentInset, y: 55.0 + mediaSize.height + 4.0), size: textSize)
self.textNode.frame = textFrame
var contentHeight: CGFloat = 55.0 + 18.0
if !mediaSize.height.isZero {
contentHeight += mediaSize.height
}
if !textSize.height.isZero {
contentHeight += textSize.height
}
let backgroundSize = CGSize(width: size.width - inset * 2.0, height: contentHeight)
let backgroundFrame = CGRect(origin: CGPoint(x: inset, y: max(20.0, floorToScreenPixels((size.height - bottomInset - backgroundSize.height) / 2.0))), size: backgroundSize)
self.backgroundNode.frame = backgroundFrame
self.backgroundImageNode.frame = CGRect(x: -5.0, y: 0.0, width: backgroundSize.width + 5.0, height: backgroundSize.height)
let link: String
if let message = self.messages.first, let chatPeer = message.peers[message.id.peerId] as? TelegramChannel, message.id.namespace == Namespaces.Message.Cloud, let addressName = chatPeer.addressName, !addressName.isEmpty {
link = "t.me/\(addressName)/\(message.id.id)"
} else {
link = ""
}
self.linkTextNode.attributedText = NSAttributedString(string: link, font: Font.medium(17.0), textColor: .white)
let linkSize = self.linkTextNode.updateLayout(size)
let linkBackgroundSize = CGSize(width: linkSize.width + 49.0, height: 33.0)
let linkBackgroundFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - linkBackgroundSize.width) / 2.0), y: backgroundFrame.maxY + 10.0), size: linkBackgroundSize)
self.linkBackgroundNode.frame = linkBackgroundFrame
self.linkBackgroundDimNode.frame = CGRect(origin: CGPoint(), size: linkBackgroundSize)
if let linkBackgroundContentNode = self.linkBackgroundContentNode {
linkBackgroundContentNode.frame = CGRect(origin: CGPoint(x: -self.linkBackgroundNode.frame.minX, y: -self.linkBackgroundNode.frame.minY), size: self.wallpaperBackgroundNode.frame.size)
}
self.linkIconNode.frame = CGRect(x: 2.0, y: -5.0, width: 42.0, height: 42.0)
self.linkTextNode.frame = CGRect(origin: CGPoint(x: 37.0, y: floorToScreenPixels((linkBackgroundSize.height - linkSize.height) / 2.0)), size: linkSize)
}
}

View File

@ -5478,7 +5478,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
return
}
controller.present(ChatQrCodeScreen(context: self.context, peer: peer), in: .window(.root))
controller.present(ChatQrCodeScreen(context: self.context, subject: .peer(peer)), in: .window(.root))
}
fileprivate func openSettings(section: PeerInfoSettingsSection) {

View File

@ -1416,7 +1416,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
}
public func makeChatQrCodeScreen(context: AccountContext, peer: Peer) -> ViewController {
return ChatQrCodeScreen(context: context, peer: peer)
return ChatQrCodeScreen(context: context, subject: .peer(peer))
}
public func makePrivacyAndSecurityController(context: AccountContext) -> ViewController {

View File

@ -1316,7 +1316,7 @@ private let qrIconImage: UIImage = {
})!
}()
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> {
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, message: Bool = false) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> {
let colorsSignal: Signal<((UIColor, UIColor?, [UInt32]), [UIColor], [UIColor], UIImage?, Bool, Bool, CGFloat, Int32?), NoError>
var reference: MediaResourceReference?
@ -1568,7 +1568,9 @@ public func themeIconImage(account: Account, accountManager: AccountManager<Tele
}
}
if qr {
if message {
} else 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))
}