mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-10-09 03:20:48 +00:00
Various Improvements
This commit is contained in:
parent
3c1afe4014
commit
15caa32878
@ -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";
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -31,6 +31,7 @@ swift_library(
|
||||
"//submodules/SegmentedControlNode:SegmentedControlNode",
|
||||
"//submodules/WallpaperBackgroundNode:WallpaperBackgroundNode",
|
||||
"//submodules/ShimmerEffect:ShimmerEffect",
|
||||
"//submodules/ContextUI:ContextUI",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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() {
|
||||
|
12
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Image.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Image.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "image_24.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
154
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Image.imageset/image_24.pdf
vendored
Normal file
154
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Image.imageset/image_24.pdf
vendored
Normal 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
@ -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()
|
||||
|
@ -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":
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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 {
|
||||
|
@ -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))
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user