mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-10-09 11:23:48 +00:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
e1e4ae0949
@ -6766,9 +6766,16 @@ Sorry for the inconvenience.";
|
||||
"Channel.AdminLog.MessageChangedThemeRemove" = "%1$@ disabled chat theme";
|
||||
|
||||
"SponsoredMessageMenu.Info" = "What are sponsored\nmessages?";
|
||||
"SponsoredMessageInfo.Text" = "See https://telegram.org";
|
||||
"SponsoredMessageInfoScreen.Title" = "What are sponsored messages?";
|
||||
"SponsoredMessageInfoScreen.Text" = "Unlike other apps, Telegram never uses your private data to target ads. You are seeing this message only because someone chose this public one-to many channel as a space to promote their messages. This means that no user data is mined or analyzed to display ads, and every user viewing a channel on Telegram sees the same sponsored message.
|
||||
|
||||
Unline other apps, Telegram doesn't track whether you tapped on a sponsored message and doesn't profile you based on your activity. We also prevent external links in sponsored messages to ensure that third parties can't spy on our users. We believe that everyone has the right to privacy, and technological platforms should respect that.
|
||||
|
||||
Telegram offers free and unlimited service to hundreds of millions of users, which involves significant server and traffic costs. In order to remain independent and stay true to its values, Telegram developed a paid tool to promote messages with user privacy in mind. We welcome responsible adverticers at:
|
||||
[url]
|
||||
Ads should no longer be synonymous with abuse of user privacy. Let us redefine how a tech compony should operate — together.";
|
||||
"SponsoredMessageInfo.Action" = "Learn More";
|
||||
"SponsoredMessageInfo.ActionUrl" = "https://telegram.org";
|
||||
"SponsoredMessageInfo.ActionUrl" = "https://telegram.org/ads";
|
||||
|
||||
"Chat.NavigationNoChannels" = "You have no unread channels";
|
||||
|
||||
@ -6797,5 +6804,9 @@ Sorry for the inconvenience.";
|
||||
"Conversation.ContextMenuSeen_1" = "1 Seen";
|
||||
"Conversation.ContextMenuSeen_any" = "%@ Seen";
|
||||
|
||||
"Conversation.ContextMenuNoViews" = "Nobody Viewed";
|
||||
"Conversation.ContextMenuNobodyListened" = "Nobody Listened";
|
||||
"Conversation.ContextMenuNobodyWatched" = "Nobody Watched";
|
||||
|
||||
"VideoChat.RecordingSaved" = "Video chat recording saved to **Saved Messages**.";
|
||||
"LiveStream.RecordingSaved" = "Live stream recording saved to **Saved Messages**.";
|
||||
|
25
submodules/AdUI/BUILD
Normal file
25
submodules/AdUI/BUILD
Normal file
@ -0,0 +1,25 @@
|
||||
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||
|
||||
swift_library(
|
||||
name = "AdUI",
|
||||
module_name = "AdUI",
|
||||
srcs = glob([
|
||||
"Sources/**/*.swift",
|
||||
]),
|
||||
copts = [
|
||||
"-warnings-as-errors",
|
||||
],
|
||||
deps = [
|
||||
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
|
||||
"//submodules/AsyncDisplayKit:AsyncDisplayKit",
|
||||
"//submodules/Display:Display",
|
||||
"//submodules/Postbox:Postbox",
|
||||
"//submodules/TelegramCore:TelegramCore",
|
||||
"//submodules/TelegramPresentationData:TelegramPresentationData",
|
||||
"//submodules/TelegramUIPreferences:TelegramUIPreferences",
|
||||
"//submodules/AccountContext:AccountContext",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
223
submodules/AdUI/Sources/AdInfoScreen.swift
Normal file
223
submodules/AdUI/Sources/AdInfoScreen.swift
Normal file
@ -0,0 +1,223 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import AsyncDisplayKit
|
||||
import SwiftSignalKit
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
import TelegramPresentationData
|
||||
import TelegramUIPreferences
|
||||
import AccountContext
|
||||
|
||||
public final class AdInfoScreen: ViewController {
|
||||
private final class Node: ViewControllerTracingNode {
|
||||
private weak var controller: AdInfoScreen?
|
||||
private let context: AccountContext
|
||||
private var presentationData: PresentationData
|
||||
|
||||
private let titleNode: ImmediateTextNode
|
||||
|
||||
private final class LinkNode: HighlightableButtonNode {
|
||||
private let backgroundNode: ASImageNode
|
||||
private let textNode: ImmediateTextNode
|
||||
|
||||
private let action: () -> Void
|
||||
|
||||
init(text: String, color: UIColor, action: @escaping () -> Void) {
|
||||
self.action = action
|
||||
|
||||
self.backgroundNode = ASImageNode()
|
||||
self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 10.0, color: nil, strokeColor: color, strokeWidth: 1.0, backgroundColor: nil)
|
||||
|
||||
self.textNode = ImmediateTextNode()
|
||||
self.textNode.maximumNumberOfLines = 1
|
||||
self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(16.0), textColor: color)
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.backgroundNode)
|
||||
self.addSubnode(self.textNode)
|
||||
|
||||
self.addTarget(self, action:#selector(self.pressed), forControlEvents: .touchUpInside)
|
||||
}
|
||||
|
||||
@objc private func pressed() {
|
||||
self.action()
|
||||
}
|
||||
|
||||
func update(width: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat {
|
||||
let size = CGSize(width: width, height: 44.0)
|
||||
|
||||
transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
let textSize = self.textNode.updateLayout(CGSize(width: width - 8.0 * 2.0, height: 44.0))
|
||||
transition.updateFrameAdditiveToCenter(node: self.textNode, frame: CGRect(origin: CGPoint(x: floor((size.width - textSize.width) / 2.0), y: floor((size.height - textSize.height) / 2.0)), size: textSize))
|
||||
|
||||
return size.height
|
||||
}
|
||||
}
|
||||
|
||||
private enum Item {
|
||||
case text(ImmediateTextNode)
|
||||
case link(LinkNode)
|
||||
}
|
||||
private let items: [Item]
|
||||
|
||||
private let scrollNode: ASScrollNode
|
||||
|
||||
init(controller: AdInfoScreen, context: AccountContext) {
|
||||
self.controller = controller
|
||||
self.context = context
|
||||
|
||||
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
self.titleNode = ImmediateTextNode()
|
||||
self.titleNode.maximumNumberOfLines = 1
|
||||
self.titleNode.attributedText = NSAttributedString(string: self.presentationData.strings.SponsoredMessageInfoScreen_Title, font: NavigationBar.titleFont, textColor: self.presentationData.theme.rootController.navigationBar.primaryTextColor)
|
||||
|
||||
self.scrollNode = ASScrollNode()
|
||||
self.scrollNode.view.showsVerticalScrollIndicator = true
|
||||
self.scrollNode.view.showsHorizontalScrollIndicator = false
|
||||
self.scrollNode.view.scrollsToTop = true
|
||||
self.scrollNode.view.delaysContentTouches = false
|
||||
self.scrollNode.view.canCancelContentTouches = true
|
||||
if #available(iOS 11.0, *) {
|
||||
self.scrollNode.view.contentInsetAdjustmentBehavior = .never
|
||||
}
|
||||
|
||||
var openUrl: (() -> Void)?
|
||||
|
||||
let rawText = self.presentationData.strings.SponsoredMessageInfoScreen_Text
|
||||
var items: [Item] = []
|
||||
var didAddUrl = false
|
||||
for component in rawText.components(separatedBy: "[url]") {
|
||||
var itemText = component
|
||||
if itemText.hasPrefix("\n") {
|
||||
itemText = String(itemText[itemText.index(itemText.startIndex, offsetBy: 1)...])
|
||||
}
|
||||
if itemText.hasSuffix("\n") {
|
||||
itemText = String(itemText[..<itemText.index(itemText.endIndex, offsetBy: -1)])
|
||||
}
|
||||
|
||||
let textNode = ImmediateTextNode()
|
||||
textNode.maximumNumberOfLines = 0
|
||||
textNode.attributedText = NSAttributedString(string: itemText, font: Font.regular(16.0), textColor: self.presentationData.theme.list.itemPrimaryTextColor)
|
||||
items.append(.text(textNode))
|
||||
|
||||
if !didAddUrl {
|
||||
didAddUrl = true
|
||||
items.append(.link(LinkNode(text: self.presentationData.strings.SponsoredMessageInfo_ActionUrl, color: self.presentationData.theme.list.itemAccentColor, action: {
|
||||
openUrl?()
|
||||
})))
|
||||
}
|
||||
}
|
||||
if !didAddUrl {
|
||||
didAddUrl = true
|
||||
items.append(.link(LinkNode(text: self.presentationData.strings.SponsoredMessageInfo_ActionUrl, color: self.presentationData.theme.list.itemAccentColor, action: {
|
||||
openUrl?()
|
||||
})))
|
||||
}
|
||||
self.items = items
|
||||
|
||||
super.init()
|
||||
|
||||
self.backgroundColor = self.presentationData.theme.list.plainBackgroundColor
|
||||
|
||||
self.addSubnode(self.scrollNode)
|
||||
|
||||
for item in self.items {
|
||||
switch item {
|
||||
case let .text(text):
|
||||
self.scrollNode.addSubnode(text)
|
||||
case let .link(link):
|
||||
self.scrollNode.addSubnode(link)
|
||||
}
|
||||
}
|
||||
|
||||
openUrl = { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.context.sharedContext.applicationBindings.openUrl(strongSelf.presentationData.strings.SponsoredMessageInfo_ActionUrl)
|
||||
}
|
||||
}
|
||||
|
||||
func containerLayoutUpdated(layout: ContainerViewLayout, navigationHeight: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
if self.titleNode.supernode == nil {
|
||||
self.addSubnode(self.titleNode)
|
||||
}
|
||||
let titleSize = self.titleNode.updateLayout(CGSize(width: layout.size.width - layout.safeInsets.left * 2.0 - 80.0 - 16.0 * 2.0, height: 100.0))
|
||||
transition.updateFrameAdditive(node: self.titleNode, frame: CGRect(origin: CGPoint(x: layout.safeInsets.left + 16.0, y: floor((navigationHeight - titleSize.height) / 2.0)), size: titleSize))
|
||||
|
||||
transition.updateFrame(node: self.scrollNode, frame: CGRect(origin: CGPoint(), size: layout.size))
|
||||
self.scrollNode.view.scrollIndicatorInsets = UIEdgeInsets(top: navigationHeight, left: 0.0, bottom: 0.0, right: 0.0)
|
||||
|
||||
let sideInset: CGFloat = layout.safeInsets.left + 16.0
|
||||
let maxWidth: CGFloat = layout.size.width - sideInset * 2.0
|
||||
var contentHeight: CGFloat = navigationHeight + 16.0
|
||||
|
||||
for item in self.items {
|
||||
switch item {
|
||||
case let .text(text):
|
||||
let textSize = text.updateLayout(CGSize(width: maxWidth, height: .greatestFiniteMagnitude))
|
||||
transition.updateFrameAdditive(node: text, frame: CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: textSize))
|
||||
contentHeight += textSize.height
|
||||
case let .link(link):
|
||||
let linkHeight = link.update(width: maxWidth, transition: transition)
|
||||
let linkSize = CGSize(width: maxWidth, height: linkHeight)
|
||||
contentHeight += 16.0
|
||||
transition.updateFrame(node: link, frame: CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: linkSize))
|
||||
contentHeight += linkSize.height
|
||||
contentHeight += 16.0
|
||||
}
|
||||
}
|
||||
|
||||
contentHeight += 16.0
|
||||
contentHeight += layout.intrinsicInsets.bottom
|
||||
|
||||
self.scrollNode.view.contentSize = CGSize(width: layout.size.width, height: contentHeight)
|
||||
}
|
||||
}
|
||||
|
||||
private var node: Node {
|
||||
return self.displayNode as! Node
|
||||
}
|
||||
|
||||
private let context: AccountContext
|
||||
private var presentationData: PresentationData
|
||||
|
||||
public init(context: AccountContext) {
|
||||
self.context = context
|
||||
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData))
|
||||
|
||||
self.navigationPresentation = .modal
|
||||
|
||||
self.navigationItem.setLeftBarButton(UIBarButtonItem(title: "", style: .plain, target: self, action: #selector(self.noAction)), animated: false)
|
||||
self.navigationItem.setRightBarButton(UIBarButtonItem(title: self.presentationData.strings.Common_Done, style: .done, target: self, action: #selector(self.donePressed)), animated: false)
|
||||
}
|
||||
|
||||
required init(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
@objc private func noAction() {
|
||||
}
|
||||
|
||||
@objc private func donePressed() {
|
||||
self.dismiss()
|
||||
}
|
||||
|
||||
override public func loadDisplayNode() {
|
||||
self.displayNode = Node(controller: self, context: self.context)
|
||||
|
||||
super.displayNodeDidLoad()
|
||||
}
|
||||
|
||||
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
super.containerLayoutUpdated(layout, transition: transition)
|
||||
|
||||
self.node.containerLayoutUpdated(layout: layout, navigationHeight: self.navigationLayout(layout: layout).navigationFrame.maxY, transition: transition)
|
||||
}
|
||||
}
|
@ -1122,6 +1122,7 @@ final class BotCheckoutControllerNode: ItemListControllerNode, PKPaymentAuthoriz
|
||||
}
|
||||
|
||||
let merchantId: String
|
||||
var countryCode: String = "US"
|
||||
if nativeProvider.name == "stripe" {
|
||||
merchantId = "merchant.ph.telegra.Telegraph"
|
||||
} else if let paramsId = nativeParams["apple_pay_merchant_id"] as? String {
|
||||
@ -1129,6 +1130,9 @@ final class BotCheckoutControllerNode: ItemListControllerNode, PKPaymentAuthoriz
|
||||
} else {
|
||||
return
|
||||
}
|
||||
if let paramsCountryCode = nativeParams["acquirer_bank_country"] as? String {
|
||||
countryCode = paramsCountryCode
|
||||
}
|
||||
|
||||
let botPeerId = self.messageId.peerId
|
||||
let _ = (context.engine.data.get(
|
||||
@ -1141,9 +1145,9 @@ final class BotCheckoutControllerNode: ItemListControllerNode, PKPaymentAuthoriz
|
||||
request.merchantIdentifier = merchantId
|
||||
request.supportedNetworks = [.visa, .amex, .masterCard]
|
||||
request.merchantCapabilities = [.capability3DS]
|
||||
request.countryCode = "US"
|
||||
request.countryCode = countryCode
|
||||
request.currencyCode = paymentForm.invoice.currency.uppercased()
|
||||
|
||||
|
||||
var items: [PKPaymentSummaryItem] = []
|
||||
|
||||
var totalAmount: Int64 = 0
|
||||
|
@ -250,10 +250,10 @@ func chatContextMenuItems(context: AccountContext, peerId: PeerId, promoInfo: Ch
|
||||
updatedItems.append(.action(ContextMenuActionItem(text: strings.ChatList_Context_Back, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Back"), color: theme.contextMenu.primaryColor)
|
||||
}, action: { c, _ in
|
||||
c.setItems(chatContextMenuItems(context: context, peerId: peerId, promoInfo: promoInfo, source: source, chatListController: chatListController, joined: joined))
|
||||
c.setItems(chatContextMenuItems(context: context, peerId: peerId, promoInfo: promoInfo, source: source, chatListController: chatListController, joined: joined), minHeight: nil)
|
||||
})))
|
||||
|
||||
c.setItems(.single(updatedItems))
|
||||
c.setItems(.single(updatedItems), minHeight: nil)
|
||||
})))
|
||||
}
|
||||
}
|
||||
|
@ -13,8 +13,9 @@ private let animationDurationFactor: Double = 1.0
|
||||
public protocol ContextControllerProtocol {
|
||||
var useComplexItemsTransitionAnimation: Bool { get set }
|
||||
var immediateItemsTransitionAnimation: Bool { get set }
|
||||
|
||||
func setItems(_ items: Signal<[ContextMenuItem], NoError>)
|
||||
|
||||
func getActionsMinHeight() -> CGFloat?
|
||||
func setItems(_ items: Signal<[ContextMenuItem], NoError>, minHeight: CGFloat?)
|
||||
func dismiss(completion: (() -> Void)?)
|
||||
}
|
||||
|
||||
@ -130,6 +131,7 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
||||
let contentReady = Promise<Bool>()
|
||||
|
||||
private var currentItems: [ContextMenuItem]?
|
||||
private var currentActionsMinHeight: CGFloat?
|
||||
|
||||
private var validLayout: ContainerViewLayout?
|
||||
|
||||
@ -448,7 +450,7 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
||||
|
||||
self.itemsDisposable.set((items
|
||||
|> deliverOnMainQueue).start(next: { [weak self] items in
|
||||
self?.setItems(items: items)
|
||||
self?.setItems(items: items, minHeight: nil)
|
||||
}))
|
||||
|
||||
switch source {
|
||||
@ -1167,24 +1169,33 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
||||
intermediateCompletion()
|
||||
})
|
||||
}
|
||||
|
||||
func getActionsMinHeight() -> CGFloat? {
|
||||
if !self.actionsContainerNode.bounds.height.isZero {
|
||||
return self.actionsContainerNode.bounds.height
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func setItemsSignal(items: Signal<[ContextMenuItem], NoError>) {
|
||||
func setItemsSignal(items: Signal<[ContextMenuItem], NoError>, minHeight: CGFloat?) {
|
||||
self.items = items
|
||||
self.itemsDisposable.set((items
|
||||
|> deliverOnMainQueue).start(next: { [weak self] items in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.setItems(items: items)
|
||||
strongSelf.setItems(items: items, minHeight: minHeight)
|
||||
}))
|
||||
}
|
||||
|
||||
private func setItems(items: [ContextMenuItem]) {
|
||||
private func setItems(items: [ContextMenuItem], minHeight: CGFloat?) {
|
||||
if let _ = self.currentItems, !self.didCompleteAnimationIn && self.getController()?.immediateItemsTransitionAnimation == true {
|
||||
return
|
||||
}
|
||||
|
||||
self.currentItems = items
|
||||
self.currentActionsMinHeight = minHeight
|
||||
|
||||
let previousActionsContainerNode = self.actionsContainerNode
|
||||
self.actionsContainerNode = ContextActionsContainerNode(presentationData: self.presentationData, items: items, getController: { [weak self] in
|
||||
@ -1282,17 +1293,19 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
||||
let isInitialLayout = self.actionsContainerNode.frame.size.width.isZero
|
||||
let previousContainerFrame = self.view.convert(self.contentContainerNode.frame, from: self.scrollNode.view)
|
||||
|
||||
let actionsSize = self.actionsContainerNode.updateLayout(widthClass: layout.metrics.widthClass, constrainedWidth: layout.size.width - actionsSideInset * 2.0, transition: actionsContainerTransition)
|
||||
self.actionsContainerNode.updateSize(containerSize: actionsSize, contentSize: actionsSize)
|
||||
let realActionsSize = self.actionsContainerNode.updateLayout(widthClass: layout.metrics.widthClass, constrainedWidth: layout.size.width - actionsSideInset * 2.0, transition: actionsContainerTransition)
|
||||
let adjustedActionsSize = CGSize(width: realActionsSize.width, height: max(realActionsSize.height, self.currentActionsMinHeight ?? 0.0))
|
||||
|
||||
self.actionsContainerNode.updateSize(containerSize: realActionsSize, contentSize: realActionsSize)
|
||||
let contentSize = originalProjectedContentViewFrame.1.size
|
||||
self.contentContainerNode.updateLayout(size: contentSize, scaledSize: contentSize, transition: transition)
|
||||
|
||||
let maximumActionsFrameOrigin = max(60.0, layout.size.height - layout.intrinsicInsets.bottom - actionsBottomInset - actionsSize.height)
|
||||
let maximumActionsFrameOrigin = max(60.0, layout.size.height - layout.intrinsicInsets.bottom - actionsBottomInset - adjustedActionsSize.height)
|
||||
|
||||
let originalActionsY = min(originalProjectedContentViewFrame.1.maxY + contentActionsSpacing, maximumActionsFrameOrigin)
|
||||
let preferredActionsX = originalProjectedContentViewFrame.1.minX
|
||||
|
||||
var originalActionsFrame = CGRect(origin: CGPoint(x: max(actionsSideInset, min(layout.size.width - actionsSize.width - actionsSideInset, preferredActionsX)), y: originalActionsY), size: actionsSize)
|
||||
var originalActionsFrame = CGRect(origin: CGPoint(x: max(actionsSideInset, min(layout.size.width - adjustedActionsSize.width - actionsSideInset, preferredActionsX)), y: originalActionsY), size: realActionsSize)
|
||||
let originalContentX: CGFloat = originalProjectedContentViewFrame.1.minX
|
||||
let originalContentY = originalProjectedContentViewFrame.1.minY
|
||||
|
||||
@ -1307,7 +1320,7 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
||||
}
|
||||
|
||||
var contentHeight = max(layout.size.height, max(layout.size.height, originalActionsFrame.maxY + actionsBottomInset) - originalContentFrame.minY + contentTopInset)
|
||||
contentHeight = max(contentHeight, actionsSize.height + originalActionsFrame.minY + actionsBottomInset)
|
||||
contentHeight = max(contentHeight, adjustedActionsSize.height + originalActionsFrame.minY + actionsBottomInset)
|
||||
|
||||
var overflowOffset: CGFloat
|
||||
var contentContainerFrame: CGRect
|
||||
@ -1360,26 +1373,28 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
||||
let isInitialLayout = self.actionsContainerNode.frame.size.width.isZero
|
||||
let previousContainerFrame = self.view.convert(self.contentContainerNode.frame, from: self.scrollNode.view)
|
||||
|
||||
let actionsSize = self.actionsContainerNode.updateLayout(widthClass: layout.metrics.widthClass, constrainedWidth: layout.size.width - actionsSideInset * 2.0, transition: actionsContainerTransition)
|
||||
self.actionsContainerNode.updateSize(containerSize: actionsSize, contentSize: actionsSize)
|
||||
let realActionsSize = self.actionsContainerNode.updateLayout(widthClass: layout.metrics.widthClass, constrainedWidth: layout.size.width - actionsSideInset * 2.0, transition: actionsContainerTransition)
|
||||
let adjustedActionsSize = CGSize(width: realActionsSize.width, height: max(realActionsSize.height, self.currentActionsMinHeight ?? 0.0))
|
||||
|
||||
self.actionsContainerNode.updateSize(containerSize: realActionsSize, contentSize: realActionsSize)
|
||||
let contentSize = originalProjectedContentViewFrame.1.size
|
||||
self.contentContainerNode.updateLayout(size: contentSize, scaledSize: contentSize, transition: transition)
|
||||
|
||||
let maximumActionsFrameOrigin = max(60.0, layout.size.height - layout.intrinsicInsets.bottom - actionsBottomInset - actionsSize.height)
|
||||
let maximumActionsFrameOrigin = max(60.0, layout.size.height - layout.intrinsicInsets.bottom - actionsBottomInset - adjustedActionsSize.height)
|
||||
let preferredActionsX: CGFloat
|
||||
let originalActionsY: CGFloat
|
||||
if centerVertically {
|
||||
originalActionsY = min(originalProjectedContentViewFrame.1.maxY + contentActionsSpacing, maximumActionsFrameOrigin)
|
||||
preferredActionsX = originalProjectedContentViewFrame.1.maxX - actionsSize.width
|
||||
preferredActionsX = originalProjectedContentViewFrame.1.maxX - adjustedActionsSize.width
|
||||
} else if keepInPlace {
|
||||
originalActionsY = originalProjectedContentViewFrame.1.minY - contentActionsSpacing - actionsSize.height
|
||||
preferredActionsX = max(actionsSideInset, originalProjectedContentViewFrame.1.maxX - actionsSize.width)
|
||||
originalActionsY = originalProjectedContentViewFrame.1.minY - contentActionsSpacing - adjustedActionsSize.height
|
||||
preferredActionsX = max(actionsSideInset, originalProjectedContentViewFrame.1.maxX - adjustedActionsSize.width)
|
||||
} else {
|
||||
originalActionsY = min(originalProjectedContentViewFrame.1.maxY + contentActionsSpacing, maximumActionsFrameOrigin)
|
||||
preferredActionsX = originalProjectedContentViewFrame.1.minX
|
||||
}
|
||||
|
||||
var originalActionsFrame = CGRect(origin: CGPoint(x: max(actionsSideInset, min(layout.size.width - actionsSize.width - actionsSideInset, preferredActionsX)), y: originalActionsY), size: actionsSize)
|
||||
var originalActionsFrame = CGRect(origin: CGPoint(x: max(actionsSideInset, min(layout.size.width - adjustedActionsSize.width - actionsSideInset, preferredActionsX)), y: originalActionsY), size: realActionsSize)
|
||||
let originalContentX: CGFloat = originalProjectedContentViewFrame.1.minX
|
||||
let originalContentY: CGFloat
|
||||
if keepInPlace {
|
||||
@ -1957,11 +1972,18 @@ public final class ContextController: ViewController, StandalonePresentableContr
|
||||
self.controllerNode.animateIn()
|
||||
}
|
||||
}
|
||||
|
||||
public func getActionsMinHeight() -> CGFloat? {
|
||||
if self.isNodeLoaded {
|
||||
return self.controllerNode.getActionsMinHeight()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
public func setItems(_ items: Signal<[ContextMenuItem], NoError>) {
|
||||
public func setItems(_ items: Signal<[ContextMenuItem], NoError>, minHeight: CGFloat? = nil) {
|
||||
self.items = items
|
||||
if self.isNodeLoaded {
|
||||
self.controllerNode.setItemsSignal(items: items)
|
||||
self.controllerNode.setItemsSignal(items: items, minHeight: minHeight)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -33,9 +33,12 @@ extension PeekControllerTheme {
|
||||
public final class PeekController: ViewController, ContextControllerProtocol {
|
||||
public var useComplexItemsTransitionAnimation: Bool = false
|
||||
public var immediateItemsTransitionAnimation = false
|
||||
|
||||
public func getActionsMinHeight() -> CGFloat? {
|
||||
return nil
|
||||
}
|
||||
|
||||
public func setItems(_ items: Signal<[ContextMenuItem], NoError>) {
|
||||
|
||||
public func setItems(_ items: Signal<[ContextMenuItem], NoError>, minHeight: CGFloat?) {
|
||||
}
|
||||
|
||||
private var controllerNode: PeekControllerNode {
|
||||
|
@ -151,7 +151,7 @@ private func cancelContextGestures(view: UIView) {
|
||||
|
||||
open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGestureRecognizerDelegate {
|
||||
public final let scroller: ListViewScroller
|
||||
private final var visibleSize: CGSize = CGSize()
|
||||
public private(set) final var visibleSize: CGSize = CGSize()
|
||||
public private(set) final var insets = UIEdgeInsets()
|
||||
public final var visualInsets: UIEdgeInsets?
|
||||
public private(set) final var headerInsets = UIEdgeInsets()
|
||||
|
@ -1,8 +1,6 @@
|
||||
import UIKit
|
||||
import AsyncDisplayKit
|
||||
|
||||
private let titleFont = Font.with(size: 17.0, design: .regular, weight: .semibold, traits: [.monospacedNumbers])
|
||||
|
||||
private var backArrowImageCache: [Int32: UIImage] = [:]
|
||||
|
||||
public final class SparseNode: ASDisplayNode {
|
||||
@ -236,6 +234,8 @@ open class NavigationBar: ASDisplayNode {
|
||||
public static var defaultSecondaryContentHeight: CGFloat {
|
||||
return 38.0
|
||||
}
|
||||
|
||||
public static let titleFont = Font.with(size: 17.0, design: .regular, weight: .semibold, traits: [.monospacedNumbers])
|
||||
|
||||
var presentationData: NavigationBarPresentationData
|
||||
|
||||
@ -388,7 +388,7 @@ open class NavigationBar: ASDisplayNode {
|
||||
private var title: String? {
|
||||
didSet {
|
||||
if let title = self.title {
|
||||
self.titleNode.attributedText = NSAttributedString(string: title, font: titleFont, textColor: self.presentationData.theme.primaryTextColor)
|
||||
self.titleNode.attributedText = NSAttributedString(string: title, font: NavigationBar.titleFont, textColor: self.presentationData.theme.primaryTextColor)
|
||||
self.titleNode.accessibilityLabel = title
|
||||
if self.titleNode.supernode == nil {
|
||||
self.buttonsContainerNode.addSubnode(self.titleNode)
|
||||
@ -837,7 +837,7 @@ open class NavigationBar: ASDisplayNode {
|
||||
self.rightButtonNode.rippleColor = self.presentationData.theme.primaryTextColor.withAlphaComponent(0.05)
|
||||
self.backButtonArrow.image = backArrowImage(color: self.presentationData.theme.buttonColor)
|
||||
if let title = self.title {
|
||||
self.titleNode.attributedText = NSAttributedString(string: title, font: titleFont, textColor: self.presentationData.theme.primaryTextColor)
|
||||
self.titleNode.attributedText = NSAttributedString(string: title, font: NavigationBar.titleFont, textColor: self.presentationData.theme.primaryTextColor)
|
||||
self.titleNode.accessibilityLabel = title
|
||||
}
|
||||
self.stripeNode.backgroundColor = self.presentationData.theme.separatorColor
|
||||
@ -919,7 +919,7 @@ open class NavigationBar: ASDisplayNode {
|
||||
self.rightButtonNode.rippleColor = self.presentationData.theme.primaryTextColor.withAlphaComponent(0.05)
|
||||
self.backButtonArrow.image = backArrowImage(color: self.presentationData.theme.buttonColor)
|
||||
if let title = self.title {
|
||||
self.titleNode.attributedText = NSAttributedString(string: title, font: titleFont, textColor: self.presentationData.theme.primaryTextColor)
|
||||
self.titleNode.attributedText = NSAttributedString(string: title, font: NavigationBar.titleFont, textColor: self.presentationData.theme.primaryTextColor)
|
||||
self.titleNode.accessibilityLabel = title
|
||||
}
|
||||
self.stripeNode.backgroundColor = self.presentationData.theme.separatorColor
|
||||
@ -1191,7 +1191,7 @@ open class NavigationBar: ASDisplayNode {
|
||||
}
|
||||
} else if let title = self.title {
|
||||
let node = ImmediateTextNode()
|
||||
node.attributedText = NSAttributedString(string: title, font: titleFont, textColor: foregroundColor)
|
||||
node.attributedText = NSAttributedString(string: title, font: NavigationBar.titleFont, textColor: foregroundColor)
|
||||
return node
|
||||
} else {
|
||||
return nil
|
||||
|
@ -2087,7 +2087,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
return
|
||||
}
|
||||
|
||||
c.setItems(strongSelf.contextMenuSpeedItems())
|
||||
c.setItems(strongSelf.contextMenuSpeedItems(), minHeight: nil)
|
||||
})))
|
||||
|
||||
if let (message, _, _) = strongSelf.contentInfo() {
|
||||
@ -2206,7 +2206,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
c.dismiss(completion: nil)
|
||||
return
|
||||
}
|
||||
c.setItems(strongSelf.contextMenuMainItems())
|
||||
c.setItems(strongSelf.contextMenuMainItems(), minHeight: nil)
|
||||
})))
|
||||
|
||||
return items
|
||||
|
@ -158,7 +158,7 @@ public func presentPeerReportOptions(context: AccountContext, parent: ViewContro
|
||||
backAction(c)
|
||||
})))
|
||||
}
|
||||
contextController.setItems(.single(items))
|
||||
contextController.setItems(.single(items), minHeight: nil)
|
||||
} else {
|
||||
contextController?.dismiss(completion: nil)
|
||||
parent.view.endEditing(true)
|
||||
|
@ -2473,7 +2473,7 @@ public final class VoiceChatController: ViewController {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
c.setItems(strongSelf.contextMenuDisplayAsItems())
|
||||
c.setItems(strongSelf.contextMenuDisplayAsItems(), minHeight: nil)
|
||||
})))
|
||||
items.append(.separator)
|
||||
break
|
||||
@ -2506,7 +2506,7 @@ public final class VoiceChatController: ViewController {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
c.setItems(strongSelf.contextMenuAudioItems())
|
||||
c.setItems(strongSelf.contextMenuAudioItems(), minHeight: nil)
|
||||
})))
|
||||
}
|
||||
|
||||
@ -2543,7 +2543,7 @@ public final class VoiceChatController: ViewController {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
c.setItems(strongSelf.contextMenuPermissionItems())
|
||||
c.setItems(strongSelf.contextMenuPermissionItems(), minHeight: nil)
|
||||
})))
|
||||
}
|
||||
}
|
||||
@ -2803,7 +2803,7 @@ public final class VoiceChatController: ViewController {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
c.setItems(strongSelf.contextMenuMainItems())
|
||||
c.setItems(strongSelf.contextMenuMainItems(), minHeight: nil)
|
||||
})))
|
||||
return .single(items)
|
||||
}
|
||||
@ -2898,7 +2898,7 @@ public final class VoiceChatController: ViewController {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
c.setItems(strongSelf.contextMenuMainItems())
|
||||
c.setItems(strongSelf.contextMenuMainItems(), minHeight: nil)
|
||||
})))
|
||||
return items
|
||||
}
|
||||
@ -2944,7 +2944,7 @@ public final class VoiceChatController: ViewController {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
c.setItems(strongSelf.contextMenuMainItems())
|
||||
c.setItems(strongSelf.contextMenuMainItems(), minHeight: nil)
|
||||
})))
|
||||
}
|
||||
return .single(items)
|
||||
|
@ -260,7 +260,7 @@ private class AdMessagesHistoryContextImpl {
|
||||
|
||||
self.state.set(CachedState.getCached(postbox: account.postbox, peerId: peerId)
|
||||
|> mapToSignal { cachedState -> Signal<State, NoError> in
|
||||
if let cachedState = cachedState, cachedState.timestamp >= Int32(Date().timeIntervalSince1970) - 5 * 60 {
|
||||
if false, let cachedState = cachedState, cachedState.timestamp >= Int32(Date().timeIntervalSince1970) - 5 * 60 {
|
||||
return account.postbox.transaction { transaction -> State in
|
||||
return State(messages: cachedState.messages.map { message in
|
||||
return message.toMessage(peerId: peerId, transaction: transaction)
|
||||
|
@ -239,6 +239,7 @@ swift_library(
|
||||
"//submodules/GradientBackground:GradientBackground",
|
||||
"//submodules/WallpaperBackgroundNode:WallpaperBackgroundNode",
|
||||
"//submodules/ComponentFlow:ComponentFlow",
|
||||
"//submodules/AdUI:AdUI",
|
||||
] + select({
|
||||
"@build_bazel_rules_apple//apple:ios_armv7": [],
|
||||
"@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets,
|
||||
|
@ -3440,7 +3440,15 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
})
|
||||
|
||||
if case .standard(previewing: false) = mode, let channel = renderedPeer?.chatMainPeer as? TelegramChannel, case .broadcast = channel.info {
|
||||
if strongSelf.nextChannelToReadDisposable == nil {
|
||||
var isRegularChat = false
|
||||
if let subject = subject {
|
||||
if case .message = subject {
|
||||
isRegularChat = true
|
||||
}
|
||||
} else {
|
||||
isRegularChat = true
|
||||
}
|
||||
if isRegularChat, strongSelf.nextChannelToReadDisposable == nil {
|
||||
strongSelf.nextChannelToReadDisposable = (combineLatest(queue: .mainQueue(),
|
||||
strongSelf.context.engine.peers.getNextUnreadChannel(peerId: channel.id, chatListFilterId: strongSelf.currentChatListFilter, getFilterPredicate: chatListFilterPredicate),
|
||||
ApplicationSpecificNotice.getNextChatSuggestionTip(accountManager: strongSelf.context.sharedContext.accountManager)
|
||||
@ -6493,7 +6501,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
})
|
||||
})))
|
||||
|
||||
contextController.setItems(.single(contextItems))
|
||||
contextController.setItems(.single(contextItems), minHeight: nil)
|
||||
}
|
||||
return
|
||||
} else {
|
||||
@ -6512,7 +6520,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
})
|
||||
})))
|
||||
|
||||
contextController.setItems(.single(contextItems))
|
||||
contextController.setItems(.single(contextItems), minHeight: nil)
|
||||
|
||||
return
|
||||
} else {
|
||||
@ -12625,7 +12633,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
|
||||
if canDisplayContextMenu, let contextController = contextController {
|
||||
contextController.setItems(.single(contextItems))
|
||||
contextController.setItems(.single(contextItems), minHeight: nil)
|
||||
} else {
|
||||
actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [
|
||||
ActionSheetButtonItem(title: self.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in
|
||||
|
@ -2613,6 +2613,8 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
let navigationButtonsSnapshotState: ChatHistoryNavigationButtons.SnapshotState
|
||||
let titleAccessoryPanelSnapshot: UIView?
|
||||
let navigationBarHeight: CGFloat
|
||||
let inputPanelNodeSnapshot: UIView?
|
||||
let inputPanelOverscrollNodeSnapshot: UIView?
|
||||
|
||||
fileprivate init(
|
||||
historySnapshotState: ChatHistoryListNode.SnapshotState,
|
||||
@ -2620,7 +2622,9 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
avatarSnapshotState: ChatAvatarNavigationNode.SnapshotState?,
|
||||
navigationButtonsSnapshotState: ChatHistoryNavigationButtons.SnapshotState,
|
||||
titleAccessoryPanelSnapshot: UIView?,
|
||||
navigationBarHeight: CGFloat
|
||||
navigationBarHeight: CGFloat,
|
||||
inputPanelNodeSnapshot: UIView?,
|
||||
inputPanelOverscrollNodeSnapshot: UIView?
|
||||
) {
|
||||
self.historySnapshotState = historySnapshotState
|
||||
self.titleViewSnapshotState = titleViewSnapshotState
|
||||
@ -2628,6 +2632,8 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
self.navigationButtonsSnapshotState = navigationButtonsSnapshotState
|
||||
self.titleAccessoryPanelSnapshot = titleAccessoryPanelSnapshot
|
||||
self.navigationBarHeight = navigationBarHeight
|
||||
self.inputPanelNodeSnapshot = inputPanelNodeSnapshot
|
||||
self.inputPanelOverscrollNodeSnapshot = inputPanelOverscrollNodeSnapshot
|
||||
}
|
||||
}
|
||||
|
||||
@ -2640,13 +2646,25 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
snapshot.frame = titleAccessoryPanelNode.frame
|
||||
titleAccessoryPanelSnapshot = snapshot
|
||||
}
|
||||
var inputPanelNodeSnapshot: UIView?
|
||||
if let inputPanelNode = self.inputPanelNode, let snapshot = inputPanelNode.view.snapshotView(afterScreenUpdates: false) {
|
||||
snapshot.frame = inputPanelNode.frame
|
||||
inputPanelNodeSnapshot = snapshot
|
||||
}
|
||||
var inputPanelOverscrollNodeSnapshot: UIView?
|
||||
if let inputPanelOverscrollNode = self.inputPanelOverscrollNode, let snapshot = inputPanelOverscrollNode.view.snapshotView(afterScreenUpdates: false) {
|
||||
snapshot.frame = inputPanelOverscrollNode.frame
|
||||
inputPanelOverscrollNodeSnapshot = snapshot
|
||||
}
|
||||
return SnapshotState(
|
||||
historySnapshotState: self.historyNode.prepareSnapshotState(),
|
||||
titleViewSnapshotState: titleViewSnapshotState,
|
||||
avatarSnapshotState: avatarSnapshotState,
|
||||
navigationButtonsSnapshotState: self.navigateButtons.prepareSnapshotState(),
|
||||
titleAccessoryPanelSnapshot: titleAccessoryPanelSnapshot,
|
||||
navigationBarHeight: self.navigationBar?.backgroundNode.bounds.height ?? 0.0
|
||||
navigationBarHeight: self.navigationBar?.backgroundNode.bounds.height ?? 0.0,
|
||||
inputPanelNodeSnapshot: inputPanelNodeSnapshot,
|
||||
inputPanelOverscrollNodeSnapshot: inputPanelOverscrollNodeSnapshot
|
||||
)
|
||||
}
|
||||
|
||||
@ -2686,6 +2704,27 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
navigationBar.backgroundNode.update(size: currentFrame.size, transition: .animated(duration: 0.5, curve: .spring))
|
||||
}
|
||||
}
|
||||
|
||||
if let inputPanelNode = self.inputPanelNode, let inputPanelNodeSnapshot = snapshotState.inputPanelNodeSnapshot {
|
||||
inputPanelNode.view.superview?.insertSubview(inputPanelNodeSnapshot, belowSubview: inputPanelNode.view)
|
||||
|
||||
inputPanelNodeSnapshot.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak inputPanelNodeSnapshot] _ in
|
||||
inputPanelNodeSnapshot?.removeFromSuperview()
|
||||
})
|
||||
inputPanelNodeSnapshot.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: -5.0), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true)
|
||||
|
||||
if let inputPanelOverscrollNodeSnapshot = snapshotState.inputPanelOverscrollNodeSnapshot {
|
||||
inputPanelNode.view.superview?.insertSubview(inputPanelOverscrollNodeSnapshot, belowSubview: inputPanelNode.view)
|
||||
|
||||
inputPanelOverscrollNodeSnapshot.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak inputPanelOverscrollNodeSnapshot] _ in
|
||||
inputPanelOverscrollNodeSnapshot?.removeFromSuperview()
|
||||
})
|
||||
inputPanelOverscrollNodeSnapshot.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: -5.0), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true)
|
||||
}
|
||||
|
||||
inputPanelNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||
inputPanelNode.layer.animatePosition(from: CGPoint(x: 0.0, y: 5.0), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
||||
}
|
||||
}
|
||||
|
||||
private var preivousChatInputPanelOverscrollNodeTimestamp: Double = 0.0
|
||||
|
@ -1039,6 +1039,16 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
||||
var disableAnimations = false
|
||||
|
||||
if let strongSelf = self, updatedScrollPosition == nil, case .InteractiveChanges = reason, case let .known(offset) = strongSelf.visibleContentOffset(), abs(offset) <= 0.9, let previous = previous {
|
||||
var fillsScreen = true
|
||||
switch strongSelf.visibleBottomContentOffset() {
|
||||
case let .known(bottomOffset):
|
||||
if bottomOffset <= strongSelf.visibleSize.height - strongSelf.insets.bottom {
|
||||
fillsScreen = false
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
var previousNumAds = 0
|
||||
for entry in previous.filteredEntries {
|
||||
if case let .MessageEntry(message, _, _, _, _, _) = entry {
|
||||
@ -1062,7 +1072,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
||||
}
|
||||
}
|
||||
|
||||
if let firstNonAdIndex = firstNonAdIndex, previousNumAds == 0, updatedNumAds != 0 {
|
||||
if fillsScreen, let firstNonAdIndex = firstNonAdIndex, previousNumAds == 0, updatedNumAds != 0 {
|
||||
updatedScrollPosition = .index(index: .message(firstNonAdIndex), position: .top(0.0), directionHint: .Up, animated: false, highlight: false)
|
||||
disableAnimations = true
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ import UndoUI
|
||||
import ShimmerEffect
|
||||
import AnimatedAvatarSetNode
|
||||
import AvatarNode
|
||||
import AdUI
|
||||
|
||||
private struct MessageContextMenuData {
|
||||
let starStatus: Bool?
|
||||
@ -139,7 +140,10 @@ private func canEditMessage(accountPeerId: PeerId, limitsConfiguration: LimitsCo
|
||||
return false
|
||||
}
|
||||
|
||||
private func canViewReadStats(message: Message, appConfig: AppConfiguration) -> Bool {
|
||||
private func canViewReadStats(message: Message, isMessageRead: Bool, appConfig: AppConfiguration) -> Bool {
|
||||
if !isMessageRead {
|
||||
return false
|
||||
}
|
||||
if message.flags.contains(.Incoming) {
|
||||
return false
|
||||
}
|
||||
@ -149,6 +153,21 @@ private func canViewReadStats(message: Message, appConfig: AppConfiguration) ->
|
||||
for media in message.media {
|
||||
if let _ = media as? TelegramMediaAction {
|
||||
return false
|
||||
} else if let file = media as? TelegramMediaFile {
|
||||
if file.isVoice || file.isInstantVideo {
|
||||
var hasRead = false
|
||||
for attribute in message.attributes {
|
||||
if let attribute = attribute as? ConsumableContentMessageAttribute {
|
||||
if attribute.consumed {
|
||||
hasRead = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if !hasRead {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -345,18 +364,10 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
|
||||
var actions: [ContextMenuItem] = []
|
||||
actions.append(.action(ContextMenuActionItem(text: presentationData.strings.SponsoredMessageMenu_Info, textColor: .primary, textLayout: .twoLinesMax, textFont: .custom(Font.regular(presentationData.listsFontSize.baseDisplaySize - 1.0)), badge: nil, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Info"), color: theme.actionSheet.primaryTextColor)
|
||||
}, iconSource: nil, action: { _, f in
|
||||
f(.default)
|
||||
|
||||
controllerInteraction.presentController(textAlertController(context: context, title: nil, text: presentationData.strings.SponsoredMessageInfo_Text, actions: [
|
||||
TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {
|
||||
}),
|
||||
TextAlertAction(type: .defaultAction, title: presentationData.strings.SponsoredMessageInfo_Action, action: {
|
||||
context.sharedContext.openExternalUrl(context: context, urlContext: .generic, url: presentationData.strings.SponsoredMessageInfo_ActionUrl, forceExternal: true, presentationData: presentationData, navigationController: controllerInteraction.navigationController(), dismissInput: {
|
||||
controllerInteraction.navigationController()?.view.endEditing(true)
|
||||
})
|
||||
}),
|
||||
]), nil)
|
||||
}, iconSource: nil, action: { c, _ in
|
||||
c.dismiss(completion: {
|
||||
controllerInteraction.navigationController()?.pushViewController(AdInfoScreen(context: context))
|
||||
})
|
||||
})))
|
||||
|
||||
actions.append(.separator)
|
||||
@ -518,30 +529,40 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
|
||||
let cachedData = context.account.postbox.transaction { transaction -> CachedPeerData? in
|
||||
return transaction.getPeerCachedData(peerId: messages[0].id.peerId)
|
||||
}
|
||||
|
||||
let readState = context.account.postbox.transaction { transaction -> CombinedPeerReadState? in
|
||||
return transaction.getCombinedPeerReadState(messages[0].id.peerId)
|
||||
}
|
||||
|
||||
let dataSignal: Signal<(MessageContextMenuData, [MessageId: ChatUpdatingMessageMedia], CachedPeerData?, AppConfiguration), NoError> = combineLatest(
|
||||
let dataSignal: Signal<(MessageContextMenuData, [MessageId: ChatUpdatingMessageMedia], CachedPeerData?, AppConfiguration, Bool), NoError> = combineLatest(
|
||||
loadLimits,
|
||||
loadStickerSaveStatusSignal,
|
||||
loadResourceStatusSignal,
|
||||
context.sharedContext.chatAvailableMessageActions(postbox: context.account.postbox, accountPeerId: context.account.peerId, messageIds: Set(messages.map { $0.id })),
|
||||
context.account.pendingUpdateMessageManager.updatingMessageMedia
|
||||
|> take(1),
|
||||
cachedData
|
||||
cachedData,
|
||||
readState
|
||||
)
|
||||
|> map { limitsAndAppConfig, stickerSaveStatus, resourceStatus, messageActions, updatingMessageMedia, cachedData -> (MessageContextMenuData, [MessageId: ChatUpdatingMessageMedia], CachedPeerData?, AppConfiguration) in
|
||||
|> map { limitsAndAppConfig, stickerSaveStatus, resourceStatus, messageActions, updatingMessageMedia, cachedData, readState -> (MessageContextMenuData, [MessageId: ChatUpdatingMessageMedia], CachedPeerData?, AppConfiguration, Bool) in
|
||||
let (limitsConfiguration, appConfig) = limitsAndAppConfig
|
||||
var canEdit = false
|
||||
if !isAction {
|
||||
let message = messages[0]
|
||||
canEdit = canEditMessage(context: context, limitsConfiguration: limitsConfiguration, message: message)
|
||||
}
|
||||
|
||||
var isMessageRead = false
|
||||
if let readState = readState {
|
||||
isMessageRead = readState.isOutgoingMessageIndexRead(message.index)
|
||||
}
|
||||
|
||||
return (MessageContextMenuData(starStatus: stickerSaveStatus, canReply: canReply, canPin: canPin, canEdit: canEdit, canSelect: canSelect, resourceStatus: resourceStatus, messageActions: messageActions), updatingMessageMedia, cachedData, appConfig)
|
||||
return (MessageContextMenuData(starStatus: stickerSaveStatus, canReply: canReply, canPin: canPin, canEdit: canEdit, canSelect: canSelect, resourceStatus: resourceStatus, messageActions: messageActions), updatingMessageMedia, cachedData, appConfig, isMessageRead)
|
||||
}
|
||||
|
||||
return dataSignal
|
||||
|> deliverOnMainQueue
|
||||
|> map { data, updatingMessageMedia, cachedData, appConfig -> [ContextMenuItem] in
|
||||
|> map { data, updatingMessageMedia, cachedData, appConfig, isMessageRead -> [ContextMenuItem] in
|
||||
var actions: [ContextMenuItem] = []
|
||||
|
||||
var isPinnedMessages = false
|
||||
@ -1103,8 +1124,10 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
|
||||
}
|
||||
|
||||
if !isPinnedMessages, !isReplyThreadHead, data.canSelect {
|
||||
var didAddSeparator = false
|
||||
if !selectAll || messages.count == 1 {
|
||||
if !actions.isEmpty {
|
||||
if !actions.isEmpty && !didAddSeparator {
|
||||
didAddSeparator = true
|
||||
actions.append(.separator)
|
||||
}
|
||||
|
||||
@ -1118,6 +1141,11 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
|
||||
}
|
||||
|
||||
if messages.count > 1 {
|
||||
if !actions.isEmpty && !didAddSeparator {
|
||||
didAddSeparator = true
|
||||
actions.append(.separator)
|
||||
}
|
||||
|
||||
actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_ContextMenuSelectAll(Int32(messages.count)), icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/SelectAll"), color: theme.actionSheet.primaryTextColor)
|
||||
}, action: { _, f in
|
||||
@ -1128,7 +1156,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
|
||||
}
|
||||
}
|
||||
|
||||
if let peer = message.peers[message.id.peerId], canViewReadStats(message: message, appConfig: appConfig) {
|
||||
if let peer = message.peers[message.id.peerId], canViewReadStats(message: message, isMessageRead: isMessageRead, appConfig: appConfig) {
|
||||
var hasReadReports = false
|
||||
if let channel = peer as? TelegramChannel {
|
||||
if case .group = channel.info {
|
||||
@ -1160,29 +1188,21 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
|
||||
subActions.append(.action(ContextMenuActionItem(text: presentationData.strings.Common_Back, textColor: .primary, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Back"), color: theme.actionSheet.primaryTextColor)
|
||||
}, action: { controller, _ in
|
||||
controller.setItems(contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState: chatPresentationInterfaceState, context: context, messages: messages, controllerInteraction: controllerInteraction, selectAll: selectAll, interfaceInteraction: interfaceInteraction, readStats: stats))
|
||||
controller.setItems(contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState: chatPresentationInterfaceState, context: context, messages: messages, controllerInteraction: controllerInteraction, selectAll: selectAll, interfaceInteraction: interfaceInteraction, readStats: stats), minHeight: nil)
|
||||
})))
|
||||
|
||||
let debugRepeatCount: Int
|
||||
#if DEBUG
|
||||
debugRepeatCount = stats.peers.count == 1 ? 1 : 50
|
||||
#else
|
||||
debugRepeatCount = 1
|
||||
#endif
|
||||
for peer in stats.peers {
|
||||
let avatarSignal = peerAvatarCompleteImage(account: context.account, peer: peer._asPeer(), size: CGSize(width: 30.0, height: 30.0))
|
||||
|
||||
for _ in 0 ..< debugRepeatCount {
|
||||
for peer in stats.peers {
|
||||
let avatarSignal = peerAvatarCompleteImage(account: context.account, peer: peer._asPeer(), size: CGSize(width: 30.0, height: 30.0))
|
||||
|
||||
subActions.append(.action(ContextMenuActionItem(text: peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), textLayout: .singleLine, icon: { _ in nil }, iconSource: ContextMenuActionItemIconSource(size: CGSize(width: 30.0, height: 30.0), signal: avatarSignal), action: { _, f in
|
||||
c.dismiss(completion: {
|
||||
controllerInteraction.openPeer(peer.id, .default, nil)
|
||||
})
|
||||
})))
|
||||
}
|
||||
subActions.append(.action(ContextMenuActionItem(text: peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), textLayout: .singleLine, icon: { _ in nil }, iconSource: ContextMenuActionItemIconSource(size: CGSize(width: 30.0, height: 30.0), signal: avatarSignal), action: { _, f in
|
||||
c.dismiss(completion: {
|
||||
controllerInteraction.openPeer(peer.id, .default, nil)
|
||||
})
|
||||
})))
|
||||
}
|
||||
|
||||
c.setItems(.single(subActions))
|
||||
let minHeight = c.getActionsMinHeight()
|
||||
c.setItems(.single(subActions), minHeight: minHeight)
|
||||
} else {
|
||||
f(.default)
|
||||
}
|
||||
@ -1845,14 +1865,24 @@ private final class ChatReadReportContextItemNode: ASDisplayNode, ContextMenuCus
|
||||
|
||||
let rightTextInset: CGFloat = sideInset + 36.0
|
||||
|
||||
let calculatedWidth = min(constrainedWidth, 260.0)
|
||||
let calculatedWidth = min(constrainedWidth, 250.0)
|
||||
|
||||
let textFont = Font.regular(self.presentationData.listsFontSize.baseDisplaySize)
|
||||
|
||||
if let currentStats = self.currentStats {
|
||||
if currentStats.peers.isEmpty {
|
||||
//TODO:localize
|
||||
self.textNode.attributedText = NSAttributedString(string: "No Views", font: textFont, textColor: self.presentationData.theme.contextMenu.secondaryColor)
|
||||
var text = self.presentationData.strings.Conversation_ContextMenuNoViews
|
||||
for media in self.item.message.media {
|
||||
if let file = media as? TelegramMediaFile {
|
||||
if file.isVoice {
|
||||
text = self.presentationData.strings.Conversation_ContextMenuNobodyListened
|
||||
} else if file.isInstantVideo {
|
||||
text = self.presentationData.strings.Conversation_ContextMenuNobodyWatched
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.textNode.attributedText = NSAttributedString(string: text, font: textFont, textColor: self.presentationData.theme.contextMenu.secondaryColor)
|
||||
} else if currentStats.peers.count == 1 {
|
||||
self.textNode.attributedText = NSAttributedString(string: currentStats.peers[0].displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder), font: textFont, textColor: self.presentationData.theme.contextMenu.primaryColor)
|
||||
} else {
|
||||
|
@ -1850,7 +1850,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
}
|
||||
|
||||
return items
|
||||
})
|
||||
}, minHeight: nil)
|
||||
})))
|
||||
}
|
||||
if strongSelf.searchDisplayController == nil {
|
||||
@ -1984,7 +1984,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
}
|
||||
|
||||
return items
|
||||
})
|
||||
}, minHeight: nil)
|
||||
})))
|
||||
}
|
||||
|
||||
@ -3700,7 +3700,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
}, action: { [weak self] c, f in
|
||||
self?.openReport(user: false, contextController: c, backAction: { c in
|
||||
if let mainItemsImpl = mainItemsImpl {
|
||||
c.setItems(mainItemsImpl())
|
||||
c.setItems(mainItemsImpl(), minHeight: nil)
|
||||
}
|
||||
})
|
||||
})))
|
||||
@ -4442,7 +4442,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
})))
|
||||
|
||||
if let contextController = contextController {
|
||||
contextController.setItems(.single(items))
|
||||
contextController.setItems(.single(items), minHeight: nil)
|
||||
} else {
|
||||
strongSelf.state = strongSelf.state.withHighlightedButton(.voiceChat)
|
||||
if let (layout, navigationHeight) = strongSelf.validLayout {
|
||||
@ -4538,7 +4538,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
}
|
||||
|
||||
if let contextController = contextController {
|
||||
contextController.setItems(.single(items))
|
||||
contextController.setItems(.single(items), minHeight: nil)
|
||||
} else {
|
||||
strongSelf.state = strongSelf.state.withHighlightedButton(.voiceChat)
|
||||
if let (layout, navigationHeight) = strongSelf.validLayout {
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit aaede253be4dbfdfcb2258cdb6faa843785192e6
|
||||
Subproject commit f76a9290fa502a8df473dd872aedf9a553b089cc
|
Loading…
x
Reference in New Issue
Block a user