mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Merge branch 'master' into postbox-refactoring-1
This commit is contained in:
commit
3f61ff85f0
Binary file not shown.
Binary file not shown.
BIN
Telegram/Telegram-iOS/Resources/TestHearts2.tgs
Normal file
BIN
Telegram/Telegram-iOS/Resources/TestHearts2.tgs
Normal file
Binary file not shown.
@ -6766,11 +6766,16 @@ Sorry for the inconvenience.";
|
||||
"Channel.AdminLog.MessageChangedThemeRemove" = "%1$@ disabled chat theme";
|
||||
|
||||
"SponsoredMessageMenu.Info" = "What are sponsored\nmessages?";
|
||||
"SponsoredMessageInfo.Text" = "Unlike other apps, Telegram never uses your private data to target ads. No user data is mined or analyzed to display ads, and every user viewing a channel on Telegram sees the same sponsored message. We believe that everyone has the right to privacy, and technological platforms should respect that.
|
||||
"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.
|
||||
|
||||
More at: https://ads.telegram.org";
|
||||
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";
|
||||
|
||||
@ -6798,3 +6803,14 @@ More at: https://ads.telegram.org";
|
||||
|
||||
"Conversation.ContextMenuSeen_1" = "1 Seen";
|
||||
"Conversation.ContextMenuSeen_any" = "%@ Seen";
|
||||
"Conversation.ContextMenuListened_1" = "1 Listened";
|
||||
"Conversation.ContextMenuListened_any" = "%@ Listened";
|
||||
"Conversation.ContextMenuWatched_1" = "1 Watched";
|
||||
"Conversation.ContextMenuWatched_any" = "%@ Watched";
|
||||
|
||||
"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)
|
||||
})))
|
||||
}
|
||||
}
|
||||
|
@ -124,13 +124,13 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
||||
|
||||
private var validLayout: (ContainerViewLayout, CGFloat)?
|
||||
|
||||
public init(context: AccountContext, filter: ChatListNodePeersFilter, groupId: PeerGroupId, displaySearchFilters: Bool, initialFilter: ChatListSearchFilter = .chats, openPeer originalOpenPeer: @escaping (Peer, Peer?, Bool) -> Void, openDisabledPeer: @escaping (Peer) -> Void, openRecentPeerOptions: @escaping (Peer) -> Void, openMessage originalOpenMessage: @escaping (Peer, MessageId, Bool) -> Void, addContact: ((String) -> Void)?, peerContextAction: ((Peer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?) -> Void)?, present: @escaping (ViewController, Any?) -> Void, presentInGlobalOverlay: @escaping (ViewController, Any?) -> Void, navigationController: NavigationController?) {
|
||||
public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, filter: ChatListNodePeersFilter, groupId: PeerGroupId, displaySearchFilters: Bool, initialFilter: ChatListSearchFilter = .chats, openPeer originalOpenPeer: @escaping (Peer, Peer?, Bool) -> Void, openDisabledPeer: @escaping (Peer) -> Void, openRecentPeerOptions: @escaping (Peer) -> Void, openMessage originalOpenMessage: @escaping (Peer, MessageId, Bool) -> Void, addContact: ((String) -> Void)?, peerContextAction: ((Peer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?) -> Void)?, present: @escaping (ViewController, Any?) -> Void, presentInGlobalOverlay: @escaping (ViewController, Any?) -> Void, navigationController: NavigationController?) {
|
||||
self.context = context
|
||||
self.peersFilter = filter
|
||||
self.groupId = groupId
|
||||
self.displaySearchFilters = displaySearchFilters
|
||||
self.navigationController = navigationController
|
||||
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
self.presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
self.selectedFilterKey = .filter(initialFilter.id)
|
||||
self.selectedFilterKeyPromise.set(.single(self.selectedFilterKey))
|
||||
@ -140,7 +140,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
||||
self.presentInGlobalOverlay = presentInGlobalOverlay
|
||||
|
||||
self.filterContainerNode = ChatListSearchFiltersContainerNode()
|
||||
self.paneContainerNode = ChatListSearchPaneContainerNode(context: context, peersFilter: self.peersFilter, groupId: groupId, searchQuery: self.searchQuery.get(), searchOptions: self.searchOptions.get(), navigationController: navigationController)
|
||||
self.paneContainerNode = ChatListSearchPaneContainerNode(context: context, updatedPresentationData: updatedPresentationData, peersFilter: self.peersFilter, groupId: groupId, searchQuery: self.searchQuery.get(), searchOptions: self.searchOptions.get(), navigationController: navigationController)
|
||||
self.paneContainerNode.clipsToBounds = true
|
||||
|
||||
super.init()
|
||||
@ -397,7 +397,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
||||
}
|
||||
}))
|
||||
|
||||
self.presentationDataDisposable = (context.sharedContext.presentationData
|
||||
self.presentationDataDisposable = ((updatedPresentationData?.signal ?? context.sharedContext.presentationData)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] presentationData in
|
||||
if let strongSelf = self {
|
||||
let previousTheme = strongSelf.presentationData.theme
|
||||
|
@ -714,7 +714,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
||||
|
||||
private var hiddenMediaDisposable: Disposable?
|
||||
|
||||
init(context: AccountContext, interaction: ChatListSearchInteraction, key: ChatListSearchPaneKey, peersFilter: ChatListNodePeersFilter, groupId: PeerGroupId?, searchQuery: Signal<String?, NoError>, searchOptions: Signal<ChatListSearchOptions?, NoError>, navigationController: NavigationController?) {
|
||||
init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, interaction: ChatListSearchInteraction, key: ChatListSearchPaneKey, peersFilter: ChatListNodePeersFilter, groupId: PeerGroupId?, searchQuery: Signal<String?, NoError>, searchOptions: Signal<ChatListSearchOptions?, NoError>, navigationController: NavigationController?) {
|
||||
self.context = context
|
||||
self.interaction = interaction
|
||||
self.key = key
|
||||
@ -739,7 +739,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
||||
}
|
||||
self.tagMask = tagMask
|
||||
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
let presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 }
|
||||
self.presentationData = presentationData
|
||||
self.presentationDataPromise.set(.single(ChatListPresentationData(theme: self.presentationData.theme, fontSize: self.presentationData.listsFontSize, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameSortOrder: self.presentationData.nameSortOrder, nameDisplayOrder: self.presentationData.nameDisplayOrder, disableAnimations: true)))
|
||||
|
||||
@ -1501,7 +1501,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
||||
}
|
||||
}))
|
||||
|
||||
self.presentationDataDisposable = (context.sharedContext.presentationData
|
||||
self.presentationDataDisposable = ((updatedPresentationData?.signal ?? context.sharedContext.presentationData)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] presentationData in
|
||||
if let strongSelf = self {
|
||||
strongSelf.presentationData = presentationData
|
||||
|
@ -72,6 +72,7 @@ private final class ChatListSearchPendingPane {
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?,
|
||||
interaction: ChatListSearchInteraction,
|
||||
navigationController: NavigationController?,
|
||||
peersFilter: ChatListNodePeersFilter,
|
||||
@ -81,7 +82,7 @@ private final class ChatListSearchPendingPane {
|
||||
key: ChatListSearchPaneKey,
|
||||
hasBecomeReady: @escaping (ChatListSearchPaneKey) -> Void
|
||||
) {
|
||||
let paneNode = ChatListSearchListPaneNode(context: context, interaction: interaction, key: key, peersFilter: key == .chats ? peersFilter : [], groupId: groupId, searchQuery: searchQuery, searchOptions: searchOptions, navigationController: navigationController)
|
||||
let paneNode = ChatListSearchListPaneNode(context: context, updatedPresentationData: updatedPresentationData, interaction: interaction, key: key, peersFilter: key == .chats ? peersFilter : [], groupId: groupId, searchQuery: searchQuery, searchOptions: searchOptions, navigationController: navigationController)
|
||||
|
||||
self.pane = ChatListSearchPaneWrapper(key: key, node: paneNode)
|
||||
self.disposable = (paneNode.isReady
|
||||
@ -99,6 +100,7 @@ private final class ChatListSearchPendingPane {
|
||||
|
||||
final class ChatListSearchPaneContainerNode: ASDisplayNode, UIGestureRecognizerDelegate {
|
||||
private let context: AccountContext
|
||||
private let updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?
|
||||
private let peersFilter: ChatListNodePeersFilter
|
||||
private let groupId: PeerGroupId
|
||||
private let searchQuery: Signal<String?, NoError>
|
||||
@ -134,8 +136,9 @@ final class ChatListSearchPaneContainerNode: ASDisplayNode, UIGestureRecognizerD
|
||||
|
||||
private var currentAvailablePanes: [ChatListSearchPaneKey]?
|
||||
|
||||
init(context: AccountContext, peersFilter: ChatListNodePeersFilter, groupId: PeerGroupId, searchQuery: Signal<String?, NoError>, searchOptions: Signal<ChatListSearchOptions?, NoError>, navigationController: NavigationController?) {
|
||||
init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, peersFilter: ChatListNodePeersFilter, groupId: PeerGroupId, searchQuery: Signal<String?, NoError>, searchOptions: Signal<ChatListSearchOptions?, NoError>, navigationController: NavigationController?) {
|
||||
self.context = context
|
||||
self.updatedPresentationData = updatedPresentationData
|
||||
self.peersFilter = peersFilter
|
||||
self.groupId = groupId
|
||||
self.searchQuery = searchQuery
|
||||
@ -361,6 +364,7 @@ final class ChatListSearchPaneContainerNode: ASDisplayNode, UIGestureRecognizerD
|
||||
var leftScope = false
|
||||
let pane = ChatListSearchPendingPane(
|
||||
context: self.context,
|
||||
updatedPresentationData: self.updatedPresentationData,
|
||||
interaction: self.interaction!,
|
||||
navigationController: self.navigationController,
|
||||
peersFilter: self.peersFilter,
|
||||
|
@ -875,14 +875,14 @@ public final class ContactListNode: ASDisplayNode {
|
||||
|
||||
public var multipleSelection = false
|
||||
|
||||
public init(context: AccountContext, presentation: Signal<ContactListPresentation, NoError>, filters: [ContactListFilter] = [.excludeSelf], selectionState: ContactListNodeGroupSelectionState? = nil, displayPermissionPlaceholder: Bool = true, displaySortOptions: Bool = false, displayCallIcons: Bool = false, contextAction: ((Peer, ASDisplayNode, ContextGesture?) -> Void)? = nil, isSearch: Bool = false, multipleSelection: Bool = false) {
|
||||
public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, presentation: Signal<ContactListPresentation, NoError>, filters: [ContactListFilter] = [.excludeSelf], selectionState: ContactListNodeGroupSelectionState? = nil, displayPermissionPlaceholder: Bool = true, displaySortOptions: Bool = false, displayCallIcons: Bool = false, contextAction: ((Peer, ASDisplayNode, ContextGesture?) -> Void)? = nil, isSearch: Bool = false, multipleSelection: Bool = false) {
|
||||
self.context = context
|
||||
self.filters = filters
|
||||
self.displayPermissionPlaceholder = displayPermissionPlaceholder
|
||||
self.contextAction = contextAction
|
||||
self.multipleSelection = multipleSelection
|
||||
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
let presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 }
|
||||
self.presentationData = presentationData
|
||||
|
||||
self.listNode = ListView()
|
||||
@ -1361,7 +1361,7 @@ public final class ContactListNode: ASDisplayNode {
|
||||
self?.enqueueTransition(transition)
|
||||
}))
|
||||
|
||||
self.presentationDataDisposable = (context.sharedContext.presentationData
|
||||
self.presentationDataDisposable = ((updatedPresentationData?.signal ?? context.sharedContext.presentationData)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] presentationData in
|
||||
if let strongSelf = self {
|
||||
let previousTheme = strongSelf.presentationData.theme
|
||||
|
@ -221,13 +221,13 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo
|
||||
return true
|
||||
}
|
||||
|
||||
public init(context: AccountContext, onlyWriteable: Bool, categories: ContactsSearchCategories, filters: [ContactListFilter] = [.excludeSelf], addContact: ((String) -> Void)?, openPeer: @escaping (ContactListPeer) -> Void, contextAction: ((Peer, ASDisplayNode, ContextGesture?) -> Void)?) {
|
||||
public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, onlyWriteable: Bool, categories: ContactsSearchCategories, filters: [ContactListFilter] = [.excludeSelf], addContact: ((String) -> Void)?, openPeer: @escaping (ContactListPeer) -> Void, contextAction: ((Peer, ASDisplayNode, ContextGesture?) -> Void)?) {
|
||||
self.context = context
|
||||
self.addContact = addContact
|
||||
self.openPeer = openPeer
|
||||
self.contextAction = contextAction
|
||||
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
let presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 }
|
||||
self.presentationData = presentationData
|
||||
|
||||
self.themeAndStringsPromise = Promise((self.presentationData.theme, self.presentationData.strings))
|
||||
|
@ -13,8 +13,10 @@ 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 setItems(_ items: Signal<[ContextMenuItem], NoError>, minHeight: CGFloat?, previousActionsTransition: ContextController.PreviousActionsTransition)
|
||||
func dismiss(completion: (() -> Void)?)
|
||||
}
|
||||
|
||||
@ -130,6 +132,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 +451,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, previousActionsTransition: .scale)
|
||||
}))
|
||||
|
||||
switch source {
|
||||
@ -1167,24 +1170,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?, previousActionsTransition: ContextController.PreviousActionsTransition) {
|
||||
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, previousActionsTransition: previousActionsTransition)
|
||||
}))
|
||||
}
|
||||
|
||||
private func setItems(items: [ContextMenuItem]) {
|
||||
private func setItems(items: [ContextMenuItem], minHeight: CGFloat?, previousActionsTransition: ContextController.PreviousActionsTransition) {
|
||||
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
|
||||
@ -1197,7 +1209,7 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
||||
self.scrollNode.insertSubnode(self.actionsContainerNode, aboveSubnode: previousActionsContainerNode)
|
||||
|
||||
if let layout = self.validLayout {
|
||||
self.updateLayout(layout: layout, transition: .animated(duration: 0.3, curve: .spring), previousActionsContainerNode: previousActionsContainerNode)
|
||||
self.updateLayout(layout: layout, transition: .animated(duration: 0.3, curve: .spring), previousActionsContainerNode: previousActionsContainerNode, previousActionsTransition: previousActionsTransition)
|
||||
} else {
|
||||
previousActionsContainerNode.removeFromSupernode()
|
||||
}
|
||||
@ -1220,7 +1232,7 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
||||
}
|
||||
}
|
||||
|
||||
func updateLayout(layout: ContainerViewLayout, transition: ContainedViewLayoutTransition, previousActionsContainerNode: ContextActionsContainerNode?) {
|
||||
func updateLayout(layout: ContainerViewLayout, transition: ContainedViewLayoutTransition, previousActionsContainerNode: ContextActionsContainerNode?, previousActionsTransition: ContextController.PreviousActionsTransition = .scale) {
|
||||
if self.isAnimatingOut {
|
||||
return
|
||||
}
|
||||
@ -1282,17 +1294,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 +1321,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 +1374,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 {
|
||||
@ -1634,13 +1650,44 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
||||
})
|
||||
self.actionsContainerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
} else {
|
||||
transition.updateTransformScale(node: previousActionsContainerNode, scale: 0.1)
|
||||
previousActionsContainerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak previousActionsContainerNode] _ in
|
||||
previousActionsContainerNode?.removeFromSupernode()
|
||||
})
|
||||
|
||||
transition.animateTransformScale(node: self.actionsContainerNode, from: 0.1)
|
||||
self.actionsContainerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
switch previousActionsTransition {
|
||||
case .scale:
|
||||
transition.updateTransformScale(node: previousActionsContainerNode, scale: 0.1)
|
||||
previousActionsContainerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak previousActionsContainerNode] _ in
|
||||
previousActionsContainerNode?.removeFromSupernode()
|
||||
})
|
||||
|
||||
transition.animateTransformScale(node: self.actionsContainerNode, from: 0.1)
|
||||
self.actionsContainerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
case let .slide(forward):
|
||||
if case .compact = layout.metrics.widthClass {
|
||||
if forward {
|
||||
transition.updatePosition(node: previousActionsContainerNode, position: CGPoint(x: -previousActionsContainerNode.bounds.width / 2.0, y: previousActionsContainerNode.position.y), completion: { [weak previousActionsContainerNode] _ in
|
||||
previousActionsContainerNode?.removeFromSupernode()
|
||||
})
|
||||
transition.animatePositionAdditive(node: self.actionsContainerNode, offset: CGPoint(x: layout.size.width + self.actionsContainerNode.bounds.width / 2.0 - self.actionsContainerNode.position.x, y: 0.0))
|
||||
} else {
|
||||
transition.updatePosition(node: previousActionsContainerNode, position: CGPoint(x: layout.size.width + previousActionsContainerNode.bounds.width / 2.0, y: previousActionsContainerNode.position.y), completion: { [weak previousActionsContainerNode] _ in
|
||||
previousActionsContainerNode?.removeFromSupernode()
|
||||
})
|
||||
transition.animatePositionAdditive(node: self.actionsContainerNode, offset: CGPoint(x: -self.actionsContainerNode.bounds.width / 2.0 - self.actionsContainerNode.position.x, y: 0.0))
|
||||
}
|
||||
} else {
|
||||
let offset: CGFloat
|
||||
if forward {
|
||||
offset = previousActionsContainerNode.bounds.width
|
||||
} else {
|
||||
offset = -previousActionsContainerNode.bounds.width
|
||||
}
|
||||
transition.updatePosition(node: previousActionsContainerNode, position: previousActionsContainerNode.position.offsetBy(dx: -offset, dy: 0.0))
|
||||
previousActionsContainerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak previousActionsContainerNode] _ in
|
||||
previousActionsContainerNode?.removeFromSupernode()
|
||||
})
|
||||
|
||||
transition.animatePositionAdditive(node: self.actionsContainerNode, offset: CGPoint(x: offset, y: 0.0))
|
||||
self.actionsContainerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
previousActionsContainerNode.removeFromSupernode()
|
||||
@ -1816,6 +1863,11 @@ public enum ContextContentSource {
|
||||
}
|
||||
|
||||
public final class ContextController: ViewController, StandalonePresentableController, ContextControllerProtocol {
|
||||
public enum PreviousActionsTransition {
|
||||
case scale
|
||||
case slide(forward: Bool)
|
||||
}
|
||||
|
||||
private let account: Account
|
||||
private var presentationData: PresentationData
|
||||
private let source: ContextContentSource
|
||||
@ -1957,11 +2009,25 @@ 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?) {
|
||||
self.items = items
|
||||
if self.isNodeLoaded {
|
||||
self.controllerNode.setItemsSignal(items: items)
|
||||
self.controllerNode.setItemsSignal(items: items, minHeight: minHeight, previousActionsTransition: .scale)
|
||||
}
|
||||
}
|
||||
|
||||
public func setItems(_ items: Signal<[ContextMenuItem], NoError>, minHeight: CGFloat?, previousActionsTransition: ContextController.PreviousActionsTransition) {
|
||||
self.items = items
|
||||
if self.isNodeLoaded {
|
||||
self.controllerNode.setItemsSignal(items: items, minHeight: minHeight, previousActionsTransition: previousActionsTransition)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -33,9 +33,15 @@ 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?) {
|
||||
}
|
||||
|
||||
public func setItems(_ items: Signal<[ContextMenuItem], NoError>, minHeight: CGFloat?, previousActionsTransition: ContextController.PreviousActionsTransition) {
|
||||
}
|
||||
|
||||
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)
|
||||
|
@ -315,7 +315,7 @@ private struct NotificationExceptionPeerState : Equatable {
|
||||
}
|
||||
|
||||
|
||||
public func notificationPeerExceptionController(context: AccountContext, peer: Peer, mode: NotificationExceptionMode, edit: Bool = false, updatePeerSound: @escaping(PeerId, PeerMessageSound) -> Void, updatePeerNotificationInterval: @escaping(PeerId, Int32?) -> Void, updatePeerDisplayPreviews: @escaping(PeerId, PeerNotificationDisplayPreviews) -> Void, removePeerFromExceptions: @escaping () -> Void, modifiedPeer: @escaping () -> Void) -> ViewController {
|
||||
public func notificationPeerExceptionController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, peer: Peer, mode: NotificationExceptionMode, edit: Bool = false, updatePeerSound: @escaping(PeerId, PeerMessageSound) -> Void, updatePeerNotificationInterval: @escaping(PeerId, Int32?) -> Void, updatePeerDisplayPreviews: @escaping(PeerId, PeerNotificationDisplayPreviews) -> Void, removePeerFromExceptions: @escaping () -> Void, modifiedPeer: @escaping () -> Void) -> ViewController {
|
||||
let initialState = NotificationExceptionPeerState(canRemove: false)
|
||||
let statePromise = Promise(initialState)
|
||||
let stateValue = Atomic(value: initialState)
|
||||
@ -365,7 +365,7 @@ public func notificationPeerExceptionController(context: AccountContext, peer: P
|
||||
})
|
||||
|
||||
|
||||
let signal = combineLatest(queue: .mainQueue(), context.sharedContext.presentationData, statePromise.get() |> distinctUntilChanged)
|
||||
let signal = combineLatest(queue: .mainQueue(), (updatedPresentationData?.signal ?? context.sharedContext.presentationData), statePromise.get() |> distinctUntilChanged)
|
||||
|> map { presentationData, state -> (ItemListControllerState, (ItemListNodeState, Any)) in
|
||||
let leftNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Cancel), style: .regular, enabled: true, action: {
|
||||
arguments.cancel()
|
||||
|
@ -703,7 +703,11 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate
|
||||
let maximumNumberOfColors: Int
|
||||
switch self.state.section {
|
||||
case .accent:
|
||||
maximumNumberOfColors = 2
|
||||
if [.classic, .day].contains(self.theme.referenceTheme.baseTheme) {
|
||||
maximumNumberOfColors = 2
|
||||
} else {
|
||||
maximumNumberOfColors = 1
|
||||
}
|
||||
case .background:
|
||||
maximumNumberOfColors = 4
|
||||
case .messages:
|
||||
|
@ -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)
|
||||
})))
|
||||
}
|
||||
}
|
||||
@ -2613,7 +2613,15 @@ public final class VoiceChatController: ViewController {
|
||||
if let strongSelf = self {
|
||||
strongSelf.call.setShouldBeRecording(false, title: nil, videoOrientation: nil)
|
||||
|
||||
strongSelf.presentUndoOverlay(content: .forward(savedMessages: true, text: strongSelf.presentationData.strings.VoiceChat_RecordingSaved), action: { [weak self] value in
|
||||
|
||||
let text: String
|
||||
if let channel = strongSelf.peer as? TelegramChannel, case .broadcast = channel.info {
|
||||
text = strongSelf.presentationData.strings.LiveStream_RecordingSaved
|
||||
} else {
|
||||
text = strongSelf.presentationData.strings.VideoChat_RecordingSaved
|
||||
}
|
||||
|
||||
strongSelf.presentUndoOverlay(content: .forward(savedMessages: true, text: text), action: { [weak self] value in
|
||||
if case .info = value, let strongSelf = self, let navigationController = strongSelf.controller?.navigationController as? NavigationController {
|
||||
let context = strongSelf.context
|
||||
strongSelf.controller?.dismiss(completion: {
|
||||
@ -2795,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)
|
||||
}
|
||||
@ -2890,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
|
||||
}
|
||||
@ -2936,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)
|
||||
|
@ -117,7 +117,7 @@ public extension TelegramChannel {
|
||||
if let defaultBannedRights = self.defaultBannedRights, defaultBannedRights.flags.contains(.banChangeInfo) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
return false
|
||||
}
|
||||
case .addAdmins:
|
||||
if let adminRights = self.adminRights, adminRights.rights.contains(.canAddAdmins) {
|
||||
|
@ -189,7 +189,21 @@ private func validatePeerReadState(network: Network, postbox: Postbox, stateMana
|
||||
}
|
||||
}
|
||||
}
|
||||
transaction.resetIncomingReadStates([peerId: [Namespaces.Message.Cloud: readState]])
|
||||
var updatedReadState = readState
|
||||
if case let .idBased(updatedMaxIncomingReadId, updatedMaxOutgoingReadId, updatedMaxKnownId, updatedCount, updatedMarkedUnread) = readState, let readStates = transaction.getPeerReadStates(peerId) {
|
||||
for (namespace, state) in readStates {
|
||||
if namespace == Namespaces.Message.Cloud {
|
||||
switch state {
|
||||
case let .idBased(_, maxOutgoingReadId, _, _, _):
|
||||
updatedReadState = .idBased(maxIncomingReadId: updatedMaxIncomingReadId, maxOutgoingReadId: max(updatedMaxOutgoingReadId, maxOutgoingReadId), maxKnownId: updatedMaxKnownId, count: updatedCount, markedUnread: updatedMarkedUnread)
|
||||
case .indexBased:
|
||||
break
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
transaction.resetIncomingReadStates([peerId: [Namespaces.Message.Cloud: updatedReadState]])
|
||||
return nil
|
||||
}
|
||||
|> mapToSignalPromotingError { error -> Signal<Never, PeerReadStateValidationError> in
|
||||
|
@ -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)
|
||||
|
@ -34,18 +34,8 @@ public func customizePresentationTheme(_ theme: PresentationTheme, specialMode:
|
||||
}
|
||||
|
||||
public func makePresentationTheme(settings: TelegramThemeSettings, specialMode: Bool = false, title: String? = nil, serviceBackgroundColor: UIColor? = nil) -> PresentationTheme? {
|
||||
var baseTheme: TelegramBaseTheme = settings.baseTheme
|
||||
var chatWallpaper = settings.wallpaper
|
||||
if specialMode && baseTheme == .tinted {
|
||||
baseTheme = .night
|
||||
|
||||
if let wallpaper = settings.wallpaper {
|
||||
let colors = (wallpaper.settings?.colors ?? []).map { UIColor(rgb: $0).withMultiplied(hue: 1.0, saturation: 1.0, brightness: 2.25).rgb }
|
||||
chatWallpaper = settings.wallpaper?.withUpdatedSettings(WallpaperSettings(blur: wallpaper.settings?.blur ?? false, motion: wallpaper.settings?.blur ?? false, colors: colors, intensity: wallpaper.settings?.intensity.flatMap({ -(max(55, $0)) }), rotation: wallpaper.settings?.rotation))
|
||||
}
|
||||
}
|
||||
let defaultTheme = makeDefaultPresentationTheme(reference: PresentationBuiltinThemeReference(baseTheme: baseTheme), extendingThemeReference: nil, serviceBackgroundColor: serviceBackgroundColor, preview: false)
|
||||
return customizePresentationTheme(defaultTheme, specialMode: specialMode, editing: true, title: title, accentColor: UIColor(argb: settings.accentColor), outgoingAccentColor: settings.outgoingAccentColor.flatMap { UIColor(argb: $0) }, backgroundColors: [], bubbleColors: settings.messageColors, animateBubbleColors: settings.animateMessageColors, wallpaper: chatWallpaper)
|
||||
let defaultTheme = makeDefaultPresentationTheme(reference: PresentationBuiltinThemeReference(baseTheme: settings.baseTheme), extendingThemeReference: nil, serviceBackgroundColor: serviceBackgroundColor, preview: false)
|
||||
return customizePresentationTheme(defaultTheme, specialMode: specialMode, editing: true, title: title, accentColor: UIColor(argb: settings.accentColor), outgoingAccentColor: settings.outgoingAccentColor.flatMap { UIColor(argb: $0) }, backgroundColors: [], bubbleColors: settings.messageColors, animateBubbleColors: settings.animateMessageColors, wallpaper: settings.wallpaper)
|
||||
}
|
||||
|
||||
public func makePresentationTheme(mediaBox: MediaBox, themeReference: PresentationThemeReference, extendingThemeReference: PresentationThemeReference? = nil, accentColor: UIColor? = nil, outgoingAccentColor: UIColor? = nil, backgroundColors: [UInt32] = [], bubbleColors: [UInt32] = [], animateBubbleColors: Bool? = nil, wallpaper: TelegramWallpaper? = nil, baseColor: PresentationThemeBaseColor? = nil, serviceBackgroundColor: UIColor? = nil, specialMode: Bool = false, preview: Bool = false) -> PresentationTheme? {
|
||||
|
@ -1274,6 +1274,11 @@ public final class PresentationTheme: Equatable {
|
||||
public let resourceCache: PresentationsResourceCache = PresentationsResourceCache()
|
||||
|
||||
public init(name: PresentationThemeName, index: Int64, referenceTheme: PresentationBuiltinThemeReference, overallDarkAppearance: Bool, intro: PresentationThemeIntro, passcode: PresentationThemePasscode, rootController: PresentationThemeRootController, list: PresentationThemeList, chatList: PresentationThemeChatList, chat: PresentationThemeChat, actionSheet: PresentationThemeActionSheet, contextMenu: PresentationThemeContextMenu, inAppNotification: PresentationThemeInAppNotification, chart: PresentationThemeChart, preview: Bool = false) {
|
||||
var overallDarkAppearance = overallDarkAppearance
|
||||
if [.night, .tinted].contains(referenceTheme.baseTheme) {
|
||||
overallDarkAppearance = true
|
||||
}
|
||||
|
||||
self.name = name
|
||||
self.index = index
|
||||
self.referenceTheme = referenceTheme
|
||||
|
@ -159,8 +159,11 @@ public enum PresentationResourceKey: Int32 {
|
||||
|
||||
case chatInputTextFieldBackgroundImage
|
||||
case chatInputTextFieldClearImage
|
||||
case chatInputPanelSendIconImage
|
||||
case chatInputPanelSendButtonImage
|
||||
case chatInputPanelApplyIconImage
|
||||
case chatInputPanelApplyButtonImage
|
||||
case chatInputPanelScheduleIconImage
|
||||
case chatInputPanelScheduleButtonImage
|
||||
case chatInputPanelVoiceButtonImage
|
||||
case chatInputPanelVideoButtonImage
|
||||
|
@ -428,6 +428,27 @@ public struct PresentationResourcesChat {
|
||||
})
|
||||
}
|
||||
|
||||
public static func chatInputPanelSendIconImage(_ theme: PresentationTheme) -> UIImage? {
|
||||
return theme.image(PresentationResourceKey.chatInputPanelSendIconImage.rawValue, { theme in
|
||||
return generateImage(CGSize(width: 33.0, height: 33.0), rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
let color: UIColor
|
||||
if [.day, .night].contains(theme.referenceTheme.baseTheme) && theme.chat.message.outgoing.bubble.withWallpaper.fill.count > 1 {
|
||||
color = .white
|
||||
} else {
|
||||
color = theme.chat.inputPanel.actionControlForegroundColor
|
||||
}
|
||||
context.setStrokeColor(color.cgColor)
|
||||
context.setFillColor(color.cgColor)
|
||||
context.setLineWidth(2.0)
|
||||
context.setLineCap(.round)
|
||||
context.setLineJoin(.round)
|
||||
let _ = try? drawSvgPath(context, path: "M11,14.6666667 L16.4310816,9.40016333 L16.4310816,9.40016333 C16.4694824,9.36292619 16.5305176,9.36292619 16.5689184,9.40016333 L22,14.6666667 S ")
|
||||
let _ = try? drawSvgPath(context, path: "M16.5,9.33333333 C17.0522847,9.33333333 17.5,9.78104858 17.5,10.3333333 L17.5,24 C17.5,24.5522847 17.0522847,25 16.5,25 C15.9477153,25 15.5,24.5522847 15.5,24 L15.5,10.3333333 C15.5,9.78104858 15.9477153,9.33333333 16.5,9.33333333 Z ")
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
public static func chatInputPanelApplyButtonImage(_ theme: PresentationTheme) -> UIImage? {
|
||||
return theme.image(PresentationResourceKey.chatInputPanelApplyButtonImage.rawValue, { theme in
|
||||
return generateImage(CGSize(width: 33.0, height: 33.0), rotatedContext: { size, context in
|
||||
@ -444,6 +465,26 @@ public struct PresentationResourcesChat {
|
||||
})
|
||||
}
|
||||
|
||||
public static func chatInputPanelApplyIconImage(_ theme: PresentationTheme) -> UIImage? {
|
||||
return theme.image(PresentationResourceKey.chatInputPanelApplyIconImage.rawValue, { theme in
|
||||
return generateImage(CGSize(width: 33.0, height: 33.0), rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
let color: UIColor
|
||||
if [.day, .night].contains(theme.referenceTheme.baseTheme) && theme.chat.message.outgoing.bubble.withWallpaper.fill.count > 1 {
|
||||
color = .white
|
||||
} else {
|
||||
color = theme.chat.inputPanel.actionControlForegroundColor
|
||||
}
|
||||
context.setStrokeColor(color.cgColor)
|
||||
context.setFillColor(color.cgColor)
|
||||
context.setLineWidth(2.0)
|
||||
context.setLineCap(.round)
|
||||
context.setLineJoin(.round)
|
||||
let _ = try? drawSvgPath(context, path: "M9.33333333,17.2686567 L14.1849216,22.120245 L14.1849216,22.120245 C14.2235835,22.1589069 14.2862668,22.1589069 14.3249287,22.120245 C14.3261558,22.1190179 14.3273504,22.1177588 14.3285113,22.1164689 L24.3333333,11 S ")
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
public static func chatInputPanelScheduleButtonImage(_ theme: PresentationTheme) -> UIImage? {
|
||||
return theme.image(PresentationResourceKey.chatInputPanelScheduleButtonImage.rawValue, { theme in
|
||||
return generateImage(CGSize(width: 33.0, height: 33.0), rotatedContext: { size, context in
|
||||
@ -463,6 +504,30 @@ public struct PresentationResourcesChat {
|
||||
})
|
||||
}
|
||||
|
||||
public static func chatInputPanelScheduleIconImage(_ theme: PresentationTheme) -> UIImage? {
|
||||
return theme.image(PresentationResourceKey.chatInputPanelScheduleIconImage.rawValue, { theme in
|
||||
return generateImage(CGSize(width: 33.0, height: 33.0), rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
let imageRect = CGRect(origin: CGPoint(), size: size)
|
||||
context.translateBy(x: imageRect.midX, y: imageRect.midY)
|
||||
context.scaleBy(x: 1.0, y: -1.0)
|
||||
context.translateBy(x: -imageRect.midX, y: -imageRect.midY)
|
||||
|
||||
let color: UIColor
|
||||
if [.day, .night].contains(theme.referenceTheme.baseTheme) && theme.chat.message.outgoing.bubble.withWallpaper.fill.count > 1 {
|
||||
color = .white
|
||||
} else {
|
||||
color = theme.chat.inputPanel.actionControlForegroundColor
|
||||
}
|
||||
|
||||
if let image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/ScheduleIcon"), color: color) {
|
||||
context.draw(image.cgImage!, in: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - image.size.width) / 2.0), y: floorToScreenPixels((size.height - image.size.height) / 2.0)), size: image.size))
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
public static func chatInputPanelVoiceButtonImage(_ theme: PresentationTheme) -> UIImage? {
|
||||
return theme.image(PresentationResourceKey.chatInputPanelVoiceButtonImage.rawValue, { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Text/IconMicrophone"), color: theme.chat.inputPanel.panelControlColor)
|
||||
|
@ -242,6 +242,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,
|
||||
|
@ -354,8 +354,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
private var canReadHistoryValue = false
|
||||
private var canReadHistoryDisposable: Disposable?
|
||||
|
||||
private var themeEmoticonPreviewPromise = ValuePromise<String?>(nil)
|
||||
private var themeDarkAppearancePreviewPromise = ValuePromise<Bool?>(nil)
|
||||
private var themeEmoticonAndDarkAppearancePreviewPromise = Promise<(String?, Bool?)>((nil, nil))
|
||||
private var didSetPresentationData = false
|
||||
private var presentationData: PresentationData
|
||||
private var presentationDataPromise = Promise<PresentationData>()
|
||||
@ -417,6 +416,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
private weak var sendMessageActionsController: ChatSendMessageActionSheetController?
|
||||
private var searchResultsController: ChatSearchResultsController?
|
||||
|
||||
private weak var themeSceen: ChatThemeScreen?
|
||||
|
||||
private weak var currentPinchController: PinchController?
|
||||
private weak var currentPinchSourceItemNode: ListViewItemNode?
|
||||
|
||||
@ -3438,7 +3439,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)
|
||||
@ -3898,8 +3907,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
|
||||
let accountManager = context.sharedContext.accountManager
|
||||
self.presentationDataDisposable = combineLatest(queue: Queue.mainQueue(), context.sharedContext.presentationData, themeSettings, context.engine.themes.getChatThemes(accountManager: accountManager, onlyCached: false), themeEmoticon, self.themeEmoticonPreviewPromise.get(), self.themeDarkAppearancePreviewPromise.get()).start(next: { [weak self] presentationData, themeSettings, chatThemes, themeEmoticon, themeEmoticonPreview, darkAppearancePreview in
|
||||
self.presentationDataDisposable = combineLatest(queue: Queue.mainQueue(), context.sharedContext.presentationData, themeSettings, context.engine.themes.getChatThemes(accountManager: accountManager, onlyCached: false), themeEmoticon, self.themeEmoticonAndDarkAppearancePreviewPromise.get()).start(next: { [weak self] presentationData, themeSettings, chatThemes, themeEmoticon, themeEmoticonAndDarkAppearance in
|
||||
if let strongSelf = self {
|
||||
let (themeEmoticonPreview, darkAppearancePreview) = themeEmoticonAndDarkAppearance
|
||||
|
||||
let previousTheme = strongSelf.presentationData.theme
|
||||
let previousStrings = strongSelf.presentationData.strings
|
||||
let previousChatWallpaper = strongSelf.presentationData.chatWallpaper
|
||||
@ -3995,6 +4006,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
|
||||
controllerInteraction.updatedPresentationData = strongSelf.updatedPresentationData
|
||||
strongSelf.presentationDataPromise.set(.single(strongSelf.presentationData))
|
||||
|
||||
if !isFirstTime && previousTheme !== presentationData.theme {
|
||||
strongSelf.presentCrossfadeSnapshot(delay: 0.2)
|
||||
}
|
||||
}
|
||||
strongSelf.presentationReady.set(.single(true))
|
||||
}
|
||||
@ -6487,7 +6502,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
})
|
||||
})))
|
||||
|
||||
contextController.setItems(.single(contextItems))
|
||||
contextController.setItems(.single(contextItems), minHeight: nil)
|
||||
}
|
||||
return
|
||||
} else {
|
||||
@ -6506,7 +6521,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
})
|
||||
})))
|
||||
|
||||
contextController.setItems(.single(contextItems))
|
||||
contextController.setItems(.single(contextItems), minHeight: nil)
|
||||
|
||||
return
|
||||
} else {
|
||||
@ -8009,6 +8024,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
self.dismissAllTooltips()
|
||||
|
||||
self.sendMessageActionsController?.dismiss()
|
||||
self.themeSceen?.dismiss()
|
||||
|
||||
if let _ = self.peekData {
|
||||
self.peekTimerDisposable.set(nil)
|
||||
@ -9577,7 +9593,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
|
||||
configureLegacyAssetPicker(controller, context: strongSelf.context, peer: peer, chatLocation: strongSelf.chatLocation, initialCaption: inputText.string, hasSchedule: strongSelf.presentationInterfaceState.subject != .scheduledMessages && peer.id.namespace != Namespaces.Peer.SecretChat, presentWebSearch: editingMedia ? nil : { [weak self, weak legacyController] in
|
||||
if let strongSelf = self {
|
||||
let controller = WebSearchController(context: strongSelf.context, peer: peer, chatLocation: strongSelf.chatLocation, configuration: searchBotsConfiguration, mode: .media(completion: { results, selectionState, editingState, silentPosting in
|
||||
let controller = WebSearchController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, peer: peer, chatLocation: strongSelf.chatLocation, configuration: searchBotsConfiguration, mode: .media(completion: { results, selectionState, editingState, silentPosting in
|
||||
if let legacyController = legacyController {
|
||||
legacyController.dismiss()
|
||||
}
|
||||
@ -9684,7 +9700,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
|> deliverOnMainQueue).start(next: { [weak self] configuration in
|
||||
if let strongSelf = self {
|
||||
let controller = WebSearchController(context: strongSelf.context, peer: peer, chatLocation: strongSelf.chatLocation, configuration: configuration, mode: .media(completion: { [weak self] results, selectionState, editingState, silentPosting in
|
||||
let controller = WebSearchController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, peer: peer, chatLocation: strongSelf.chatLocation, configuration: configuration, mode: .media(completion: { [weak self] results, selectionState, editingState, silentPosting in
|
||||
legacyEnqueueWebSearchMessages(selectionState, editingState, enqueueChatContextResult: { [weak self] result in
|
||||
if let strongSelf = self {
|
||||
strongSelf.enqueueChatContextResult(results, result, hideVia: true)
|
||||
@ -12618,7 +12634,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
|
||||
@ -13303,13 +13319,16 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
self.push(controller)
|
||||
}
|
||||
|
||||
private var crossfading = false
|
||||
private func presentCrossfadeSnapshot(delay: Double) {
|
||||
guard let snapshotView = self.view.snapshotView(afterScreenUpdates: false) else {
|
||||
guard !self.crossfading, let snapshotView = self.view.snapshotView(afterScreenUpdates: false) else {
|
||||
return
|
||||
}
|
||||
self.crossfading = true
|
||||
self.view.addSubview(snapshotView)
|
||||
|
||||
snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, delay: delay, removeOnCompletion: false, completion: { [weak snapshotView] _ in
|
||||
snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, delay: delay, removeOnCompletion: false, completion: { [weak self, weak snapshotView] _ in
|
||||
self?.crossfading = false
|
||||
snapshotView?.removeFromSuperview()
|
||||
})
|
||||
}
|
||||
@ -13345,25 +13364,17 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
selectedEmoticon = nil
|
||||
}
|
||||
|
||||
let controller = ChatThemeScreen(context: context, updatedPresentationData: strongSelf.updatedPresentationData, initiallySelectedEmoticon: selectedEmoticon, previewTheme: { [weak self] emoticon in
|
||||
let controller = ChatThemeScreen(context: context, updatedPresentationData: strongSelf.updatedPresentationData, initiallySelectedEmoticon: selectedEmoticon, previewTheme: { [weak self] emoticon, dark in
|
||||
if let strongSelf = self {
|
||||
strongSelf.presentCrossfadeSnapshot(delay: 0.2)
|
||||
strongSelf.themeEmoticonPreviewPromise.set(emoticon)
|
||||
if emoticon == nil {
|
||||
strongSelf.themeDarkAppearancePreviewPromise.set(nil)
|
||||
}
|
||||
}
|
||||
}, previewDarkTheme: { dark in
|
||||
if let strongSelf = self {
|
||||
strongSelf.presentCrossfadeSnapshot(delay: 0.0)
|
||||
strongSelf.themeDarkAppearancePreviewPromise.set(dark)
|
||||
strongSelf.themeEmoticonAndDarkAppearancePreviewPromise.set(.single((emoticon, dark)))
|
||||
}
|
||||
}, completion: { [weak self] emoticon in
|
||||
strongSelf.presentCrossfadeSnapshot(delay: 0.2)
|
||||
strongSelf.themeDarkAppearancePreviewPromise.set(nil)
|
||||
strongSelf.themeEmoticonAndDarkAppearancePreviewPromise.set(.single((emoticon, nil)))
|
||||
let _ = context.engine.themes.setChatTheme(peerId: peerId, emoticon: emoticon).start(completed: { [weak self] in
|
||||
if let strongSelf = self {
|
||||
strongSelf.themeEmoticonPreviewPromise.set(nil)
|
||||
strongSelf.themeEmoticonAndDarkAppearancePreviewPromise.set(.single((nil, nil)))
|
||||
}
|
||||
})
|
||||
})
|
||||
@ -13375,6 +13386,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
}
|
||||
strongSelf.present(controller, in: .window(.root))
|
||||
strongSelf.themeSceen = controller
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -492,7 +492,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
|
||||
self.historyNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))))
|
||||
|
||||
self.textInputPanelNode = ChatTextInputPanelNode(presentationInterfaceState: chatPresentationInterfaceState, presentController: { [weak self] controller in
|
||||
self.textInputPanelNode = ChatTextInputPanelNode(presentationInterfaceState: chatPresentationInterfaceState, presentationContext: ChatPresentationContext(backgroundNode: backgroundNode), presentController: { [weak self] controller in
|
||||
self?.interfaceInteraction?.presentController(controller, nil)
|
||||
})
|
||||
self.textInputPanelNode?.storedInputLanguage = chatPresentationInterfaceState.interfaceState.inputLanguage
|
||||
@ -1446,9 +1446,11 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
}
|
||||
}
|
||||
if inputPanelNodeHandlesTransition {
|
||||
inputPanelNode.updateAbsoluteRect(apparentInputPanelFrame, within: layout.size, transition: .immediate)
|
||||
inputPanelNode.frame = apparentInputPanelFrame
|
||||
inputPanelNode.alpha = 1.0
|
||||
} else {
|
||||
inputPanelNode.updateAbsoluteRect(apparentInputPanelFrame, within: layout.size, transition: transition)
|
||||
transition.updateFrame(node: inputPanelNode, frame: apparentInputPanelFrame)
|
||||
transition.updateAlpha(node: inputPanelNode, alpha: 1.0)
|
||||
}
|
||||
@ -2611,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,
|
||||
@ -2618,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
|
||||
@ -2626,6 +2632,8 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
self.navigationButtonsSnapshotState = navigationButtonsSnapshotState
|
||||
self.titleAccessoryPanelSnapshot = titleAccessoryPanelSnapshot
|
||||
self.navigationBarHeight = navigationBarHeight
|
||||
self.inputPanelNodeSnapshot = inputPanelNodeSnapshot
|
||||
self.inputPanelOverscrollNodeSnapshot = inputPanelOverscrollNodeSnapshot
|
||||
}
|
||||
}
|
||||
|
||||
@ -2638,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
|
||||
)
|
||||
}
|
||||
|
||||
@ -2684,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
|
||||
}
|
||||
|
@ -11,6 +11,9 @@ class ChatInputPanelNode: ASDisplayNode {
|
||||
var interfaceInteraction: ChatPanelInterfaceInteraction?
|
||||
var prevInputPanelNode: ChatInputPanelNode?
|
||||
|
||||
func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize, transition: ContainedViewLayoutTransition) {
|
||||
}
|
||||
|
||||
func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics) -> CGFloat {
|
||||
return 0.0
|
||||
}
|
||||
|
@ -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, previousActionsTransition: .slide(forward: false))
|
||||
})))
|
||||
|
||||
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, previousActionsTransition: .slide(forward: true))
|
||||
} else {
|
||||
f(.default)
|
||||
}
|
||||
@ -1845,18 +1865,39 @@ 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 {
|
||||
self.textNode.attributedText = NSAttributedString(string: self.presentationData.strings.Conversation_ContextMenuSeen(Int32(currentStats.peers.count)), font: textFont, textColor: self.presentationData.theme.contextMenu.primaryColor)
|
||||
var text = self.presentationData.strings.Conversation_ContextMenuSeen(Int32(currentStats.peers.count))
|
||||
for media in self.item.message.media {
|
||||
if let file = media as? TelegramMediaFile {
|
||||
if file.isVoice {
|
||||
text = self.presentationData.strings.Conversation_ContextMenuListened(Int32(currentStats.peers.count))
|
||||
} else if file.isInstantVideo {
|
||||
text = self.presentationData.strings.Conversation_ContextMenuWatched(Int32(currentStats.peers.count))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.textNode.attributedText = NSAttributedString(string: text, font: textFont, textColor: self.presentationData.theme.contextMenu.primaryColor)
|
||||
}
|
||||
} else {
|
||||
self.textNode.attributedText = NSAttributedString(string: " ", font: textFont, textColor: self.presentationData.theme.contextMenu.primaryColor)
|
||||
@ -1927,8 +1968,18 @@ private final class ChatReadReportContextItemNode: ASDisplayNode, ContextMenuCus
|
||||
self.performAction()
|
||||
}
|
||||
|
||||
private var actionTemporarilyDisabled: Bool = false
|
||||
|
||||
func performAction() {
|
||||
guard let controller = self.getController(), let currentStats = self.currentStats else {
|
||||
if self.actionTemporarilyDisabled {
|
||||
return
|
||||
}
|
||||
self.actionTemporarilyDisabled = true
|
||||
Queue.mainQueue().async { [weak self] in
|
||||
self?.actionTemporarilyDisabled = false
|
||||
}
|
||||
|
||||
guard let controller = self.getController(), let currentStats = self.currentStats else {
|
||||
return
|
||||
}
|
||||
self.item.action(controller, { [weak self] result in
|
||||
|
@ -294,7 +294,7 @@ func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState
|
||||
textInputPanelNode.context = context
|
||||
return (textInputPanelNode, nil)
|
||||
} else {
|
||||
let panel = ChatTextInputPanelNode(presentationInterfaceState: chatPresentationInterfaceState, presentController: { [weak interfaceInteraction] controller in
|
||||
let panel = ChatTextInputPanelNode(presentationInterfaceState: chatPresentationInterfaceState, presentationContext: nil, presentController: { [weak interfaceInteraction] controller in
|
||||
interfaceInteraction?.presentController(controller, nil)
|
||||
})
|
||||
|
||||
|
@ -1308,8 +1308,10 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
self?.additionalAnimationNodes.removeAll(where: { $0 === additionalAnimationNode })
|
||||
additionalAnimationNode?.removeFromSupernode()
|
||||
}
|
||||
additionalAnimationNode.frame = animationNode.frame.insetBy(dx: -animationNode.frame.width, dy: -animationNode.frame.height)
|
||||
var animationFrame = animationNode.frame.insetBy(dx: -animationNode.frame.width, dy: -animationNode.frame.height)
|
||||
.offsetBy(dx: incoming ? animationNode.frame.width - 10.0 : -animationNode.frame.width + 10.0, dy: 0.0)
|
||||
animationFrame = animationFrame.offsetBy(dx: CGFloat.random(in: -30.0 ... 30.0), dy: CGFloat.random(in: -30.0 ... 30.0))
|
||||
additionalAnimationNode.frame = animationFrame
|
||||
if incoming {
|
||||
additionalAnimationNode.transform = CATransform3DMakeScale(-1.0, 1.0, 1.0)
|
||||
}
|
||||
@ -1434,7 +1436,11 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
}
|
||||
return .optionalAction({
|
||||
if firstScalar.value == heart {
|
||||
self.playAdditionalAnimation("TestHearts")
|
||||
if self.additionalAnimationNodes.count % 2 == 0 {
|
||||
self.playAdditionalAnimation("TestHearts")
|
||||
} else {
|
||||
self.playAdditionalAnimation("TestHearts2")
|
||||
}
|
||||
} else if firstScalar.value == fireworks {
|
||||
self.playAdditionalAnimation("TestFireworks")
|
||||
}
|
||||
|
@ -188,13 +188,13 @@ final class ChatMessageBubbleBackdrop: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
func update(rect: CGRect, within containerSize: CGSize) {
|
||||
func update(rect: CGRect, within containerSize: CGSize, transition: ContainedViewLayoutTransition = .immediate) {
|
||||
self.absolutePosition = (rect, containerSize)
|
||||
if let backgroundContent = self.backgroundContent {
|
||||
var backgroundFrame = backgroundContent.frame
|
||||
backgroundFrame.origin.x += rect.minX
|
||||
backgroundFrame.origin.y += rect.minY
|
||||
backgroundContent.update(rect: backgroundFrame, within: containerSize)
|
||||
backgroundContent.update(rect: backgroundFrame, within: containerSize, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -144,11 +144,8 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
|
||||
var secretVideoPlaceholderBackgroundImage: UIImage?
|
||||
var updatedInfoBackgroundImage: UIImage?
|
||||
var updatedMuteIconImage: UIImage?
|
||||
if item.presentationData.theme != currentItem?.presentationData.theme {
|
||||
updatedInfoBackgroundImage = PresentationResourcesChat.chatInstantMessageInfoBackgroundImage(item.presentationData.theme.theme)
|
||||
updatedMuteIconImage = PresentationResourcesChat.chatInstantMessageMuteIconImage(item.presentationData.theme.theme)
|
||||
}
|
||||
|
||||
var updatedInstantVideoBackgroundImage: UIImage?
|
||||
let instantVideoBackgroundImage: UIImage?
|
||||
switch statusDisplayType {
|
||||
case .free:
|
||||
@ -157,6 +154,12 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
|
||||
instantVideoBackgroundImage = nil
|
||||
}
|
||||
|
||||
if item.presentationData.theme != currentItem?.presentationData.theme {
|
||||
updatedInstantVideoBackgroundImage = instantVideoBackgroundImage
|
||||
updatedInfoBackgroundImage = PresentationResourcesChat.chatInstantMessageInfoBackgroundImage(item.presentationData.theme.theme)
|
||||
updatedMuteIconImage = PresentationResourcesChat.chatInstantMessageMuteIconImage(item.presentationData.theme.theme)
|
||||
}
|
||||
|
||||
let theme = item.presentationData.theme
|
||||
let isSecretMedia = item.message.containsSecretMedia
|
||||
var secretProgressIcon: UIImage?
|
||||
@ -333,6 +336,10 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
|
||||
strongSelf.secretVideoPlaceholderBackground.image = secretVideoPlaceholderBackgroundImage
|
||||
}
|
||||
|
||||
if let updatedInstantVideoBackgroundImage = updatedInstantVideoBackgroundImage, let decoration = strongSelf.videoNode?.decoration as? ChatBubbleInstantVideoDecoration, let decorationBackgroundNode = decoration.backgroundNode as? ASImageNode {
|
||||
decorationBackgroundNode.image = updatedInstantVideoBackgroundImage
|
||||
}
|
||||
|
||||
strongSelf.media = updatedFile
|
||||
|
||||
if let infoBackgroundImage = strongSelf.infoBackgroundNode.image, let muteImage = strongSelf.muteIconNode.image {
|
||||
|
@ -24,6 +24,7 @@ final class ChatRecentActionsController: TelegramBaseController {
|
||||
return (self.presentationData, self.presentationDataPromise.get())
|
||||
}
|
||||
private var presentationDataDisposable: Disposable?
|
||||
private var didSetPresentationData = false
|
||||
|
||||
private var interaction: ChatRecentActionsInteraction!
|
||||
private var panelInteraction: ChatPanelInterfaceInteraction!
|
||||
@ -185,10 +186,12 @@ final class ChatRecentActionsController: TelegramBaseController {
|
||||
}
|
||||
}
|
||||
|
||||
let isFirstTime = !strongSelf.didSetPresentationData
|
||||
strongSelf.presentationData = presentationData
|
||||
strongSelf.presentationDataPromise.set(.single(presentationData))
|
||||
strongSelf.didSetPresentationData = true
|
||||
|
||||
if previousTheme !== presentationData.theme || previousStrings !== presentationData.strings {
|
||||
if isFirstTime || previousTheme !== presentationData.theme || previousStrings !== presentationData.strings {
|
||||
strongSelf.updateThemeAndStrings()
|
||||
}
|
||||
}
|
||||
|
@ -246,9 +246,9 @@ final class ChatSendMessageActionSheetControllerNode: ViewControllerTracingNode,
|
||||
self.contentNodes = contentNodes
|
||||
|
||||
super.init()
|
||||
|
||||
self.sendButtonNode.setImage(PresentationResourcesChat.chatInputPanelSendButtonImage(self.presentationData.theme), for: [])
|
||||
self.sendButtonNode.addTarget(self, action: #selector(sendButtonPressed), forControlEvents: .touchUpInside)
|
||||
|
||||
// self.sendButtonNode.setImage(PresentationResourcesChat.chatInputPanelSendButtonImage(self.presentationData.theme), for: [])
|
||||
self.sendButtonNode.addTarget(self, action: #selector(self.sendButtonPressed), forControlEvents: .touchUpInside)
|
||||
|
||||
if let attributedText = textInputNode.attributedText, !attributedText.string.isEmpty {
|
||||
self.fromMessageTextNode.attributedText = attributedText
|
||||
@ -341,6 +341,10 @@ final class ChatSendMessageActionSheetControllerNode: ViewControllerTracingNode,
|
||||
if #available(iOSApplicationExtension 11.0, iOS 11.0, *) {
|
||||
self.scrollNode.view.contentInsetAdjustmentBehavior = .never
|
||||
}
|
||||
|
||||
if let snapshotView = self.sourceSendButton.view.snapshotView(afterScreenUpdates: false) {
|
||||
self.sendButtonNode.view.addSubview(snapshotView)
|
||||
}
|
||||
}
|
||||
|
||||
func updatePresentationData(_ presentationData: PresentationData) {
|
||||
@ -361,7 +365,7 @@ final class ChatSendMessageActionSheetControllerNode: ViewControllerTracingNode,
|
||||
self.dimNode.backgroundColor = presentationData.theme.contextMenu.dimColor
|
||||
|
||||
self.contentContainerNode.backgroundColor = self.presentationData.theme.contextMenu.backgroundColor
|
||||
self.sendButtonNode.setImage(PresentationResourcesChat.chatInputPanelSendButtonImage(self.presentationData.theme), for: [])
|
||||
// self.sendButtonNode.setImage(PresentationResourcesChat.chatInputPanelSendButtonImage(self.presentationData.theme), for: [])
|
||||
|
||||
if let toAttributedText = self.textInputNode.attributedText?.mutableCopy() as? NSMutableAttributedString {
|
||||
toAttributedText.addAttribute(NSAttributedString.Key.foregroundColor, value: self.presentationData.theme.chat.message.outgoing.primaryTextColor, range: NSMakeRange(0, (toAttributedText.string as NSString).length))
|
||||
|
@ -2,13 +2,18 @@ import Foundation
|
||||
import UIKit
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
import TelegramCore
|
||||
import TelegramPresentationData
|
||||
import ContextUI
|
||||
|
||||
final class ChatTextInputActionButtonsNode: ASDisplayNode {
|
||||
private let presentationContext: ChatPresentationContext?
|
||||
private let strings: PresentationStrings
|
||||
|
||||
let micButton: ChatTextInputMediaRecordingButton
|
||||
let sendContainerNode: ASDisplayNode
|
||||
let backdropNode: ChatMessageBubbleBackdrop
|
||||
let backgroundNode: ASDisplayNode
|
||||
let sendButton: HighlightTrackingButtonNode
|
||||
var sendButtonRadialStatusNode: ChatSendButtonRadialStatusNode?
|
||||
var sendButtonHasApplyIcon = false
|
||||
@ -26,10 +31,21 @@ final class ChatTextInputActionButtonsNode: ASDisplayNode {
|
||||
|
||||
var micButtonPointerInteraction: PointerInteraction?
|
||||
|
||||
init(theme: PresentationTheme, strings: PresentationStrings, presentController: @escaping (ViewController) -> Void) {
|
||||
private var validLayout: CGSize?
|
||||
|
||||
init(presentationInterfaceState: ChatPresentationInterfaceState, presentationContext: ChatPresentationContext?, presentController: @escaping (ViewController) -> Void) {
|
||||
self.presentationContext = presentationContext
|
||||
let theme = presentationInterfaceState.theme
|
||||
let strings = presentationInterfaceState.strings
|
||||
self.strings = strings
|
||||
|
||||
|
||||
self.micButton = ChatTextInputMediaRecordingButton(theme: theme, strings: strings, presentController: presentController)
|
||||
|
||||
self.sendContainerNode = ASDisplayNode()
|
||||
self.backgroundNode = ASDisplayNode()
|
||||
self.backgroundNode.backgroundColor = theme.chat.inputPanel.actionControlFillColor
|
||||
self.backgroundNode.clipsToBounds = true
|
||||
self.backdropNode = ChatMessageBubbleBackdrop()
|
||||
self.sendButton = HighlightTrackingButtonNode(pointerStyle: .lift)
|
||||
|
||||
self.expandMediaInputButton = HighlightableButtonNode(pointerStyle: .default)
|
||||
@ -43,24 +59,32 @@ final class ChatTextInputActionButtonsNode: ASDisplayNode {
|
||||
if let strongSelf = self {
|
||||
if strongSelf.sendButtonHasApplyIcon || !strongSelf.sendButtonLongPressEnabled {
|
||||
if highlighted {
|
||||
strongSelf.layer.removeAnimation(forKey: "opacity")
|
||||
strongSelf.alpha = 0.4
|
||||
strongSelf.sendContainerNode.layer.removeAnimation(forKey: "opacity")
|
||||
strongSelf.sendContainerNode.alpha = 0.4
|
||||
} else {
|
||||
strongSelf.alpha = 1.0
|
||||
strongSelf.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
|
||||
strongSelf.sendContainerNode.alpha = 1.0
|
||||
strongSelf.sendContainerNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
|
||||
}
|
||||
} else {
|
||||
if highlighted {
|
||||
strongSelf.sendButton.layer.animateScale(from: 1.0, to: 0.75, duration: 0.4, removeOnCompletion: false)
|
||||
strongSelf.sendContainerNode.layer.animateScale(from: 1.0, to: 0.75, duration: 0.4, removeOnCompletion: false)
|
||||
} else if let presentationLayer = strongSelf.sendButton.layer.presentation() {
|
||||
strongSelf.sendButton.layer.animateScale(from: CGFloat((presentationLayer.value(forKeyPath: "transform.scale.y") as? NSNumber)?.floatValue ?? 1.0), to: 1.0, duration: 0.25, removeOnCompletion: false)
|
||||
strongSelf.sendContainerNode.layer.animateScale(from: CGFloat((presentationLayer.value(forKeyPath: "transform.scale.y") as? NSNumber)?.floatValue ?? 1.0), to: 1.0, duration: 0.25, removeOnCompletion: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.view.addSubview(self.micButton)
|
||||
self.addSubnode(self.sendButton)
|
||||
|
||||
self.addSubnode(self.sendContainerNode)
|
||||
self.sendContainerNode.addSubnode(self.backgroundNode)
|
||||
if let presentationContext = presentationContext {
|
||||
let graphics = PresentationResourcesChat.principalGraphics(theme: theme, wallpaper: presentationInterfaceState.chatWallpaper, bubbleCorners: presentationInterfaceState.bubbleCorners)
|
||||
self.backdropNode.setType(type: .outgoing(.None), theme: ChatPresentationThemeData(theme: theme, wallpaper: presentationInterfaceState.chatWallpaper), essentialGraphics: graphics, maskMode: true, backgroundNode: presentationContext.backgroundNode)
|
||||
self.backgroundNode.addSubnode(self.backdropNode)
|
||||
}
|
||||
self.sendContainerNode.addSubnode(self.sendButton)
|
||||
self.addSubnode(self.expandMediaInputButton)
|
||||
}
|
||||
|
||||
@ -75,23 +99,51 @@ final class ChatTextInputActionButtonsNode: ASDisplayNode {
|
||||
return
|
||||
}
|
||||
if !strongSelf.sendButtonHasApplyIcon {
|
||||
strongSelf.sendButtonLongPressed?(strongSelf.sendButton, recognizer)
|
||||
strongSelf.sendButtonLongPressed?(strongSelf.sendContainerNode, recognizer)
|
||||
}
|
||||
}
|
||||
|
||||
self.micButtonPointerInteraction = PointerInteraction(view: self.micButton, style: .circle)
|
||||
}
|
||||
|
||||
func updateTheme(theme: PresentationTheme) {
|
||||
func updateTheme(theme: PresentationTheme, wallpaper: TelegramWallpaper) {
|
||||
self.micButton.updateTheme(theme: theme)
|
||||
self.expandMediaInputButton.setImage(PresentationResourcesChat.chatInputPanelExpandButtonImage(theme), for: [])
|
||||
|
||||
self.backgroundNode.backgroundColor = theme.chat.inputPanel.actionControlFillColor
|
||||
|
||||
if [.day, .night].contains(theme.referenceTheme.baseTheme) && theme.chat.message.outgoing.bubble.withWallpaper.fill.count > 1 {
|
||||
self.backdropNode.isHidden = false
|
||||
} else {
|
||||
self.backdropNode.isHidden = true
|
||||
}
|
||||
|
||||
let graphics = PresentationResourcesChat.principalGraphics(theme: theme, wallpaper: wallpaper, bubbleCorners: .init(mainRadius: 1, auxiliaryRadius: 1, mergeBubbleCorners: false))
|
||||
self.backdropNode.setType(type: .outgoing(.None), theme: ChatPresentationThemeData(theme: theme, wallpaper: wallpaper), essentialGraphics: graphics, maskMode: false, backgroundNode: self.presentationContext?.backgroundNode)
|
||||
}
|
||||
|
||||
private var absoluteRect: (CGRect, CGSize)?
|
||||
func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize, transition: ContainedViewLayoutTransition) {
|
||||
self.absoluteRect = (rect, containerSize)
|
||||
self.backdropNode.update(rect: rect, within: containerSize, transition: transition)
|
||||
}
|
||||
|
||||
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState) {
|
||||
self.validLayout = size
|
||||
transition.updateFrame(layer: self.micButton.layer, frame: CGRect(origin: CGPoint(), size: size))
|
||||
self.micButton.layoutItems()
|
||||
|
||||
transition.updateFrame(layer: self.sendButton.layer, frame: CGRect(origin: CGPoint(), size: size))
|
||||
transition.updateFrame(node: self.sendContainerNode, frame: CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
let backgroundSize = CGSize(width: 33.0, height: 33.0)
|
||||
transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - backgroundSize.width) / 2.0), y: floorToScreenPixels((size.height - backgroundSize.height) / 2.0)), size: backgroundSize))
|
||||
self.backgroundNode.cornerRadius = backgroundSize.width / 2.0
|
||||
|
||||
transition.updateFrame(node: self.backdropNode, frame: CGRect(origin: CGPoint(x: -2.0, y: -2.0), size: CGSize(width: size.width + 12.0, height: size.height + 2.0)))
|
||||
if let (rect, containerSize) = self.absoluteRect {
|
||||
self.backdropNode.update(rect: rect, within: containerSize)
|
||||
}
|
||||
|
||||
var isScheduledMessages = false
|
||||
if case .scheduledMessages = interfaceState.subject {
|
||||
@ -104,12 +156,12 @@ final class ChatTextInputActionButtonsNode: ASDisplayNode {
|
||||
sendButtonRadialStatusNode = current
|
||||
} else {
|
||||
sendButtonRadialStatusNode = ChatSendButtonRadialStatusNode(color: interfaceState.theme.chat.inputPanel.panelControlAccentColor)
|
||||
sendButtonRadialStatusNode.alpha = self.sendButton.alpha
|
||||
sendButtonRadialStatusNode.alpha = self.sendContainerNode.alpha
|
||||
self.sendButtonRadialStatusNode = sendButtonRadialStatusNode
|
||||
self.addSubnode(sendButtonRadialStatusNode)
|
||||
}
|
||||
|
||||
transition.updateSublayerTransformScale(layer: self.sendButton.layer, scale: CGPoint(x: 0.7575, y: 0.7575))
|
||||
transition.updateSublayerTransformScale(layer: self.sendContainerNode.layer, scale: CGPoint(x: 0.7575, y: 0.7575))
|
||||
|
||||
let defaultSendButtonSize: CGFloat = 25.0
|
||||
let defaultOriginX = floorToScreenPixels((self.sendButton.bounds.width - defaultSendButtonSize) / 2.0)
|
||||
@ -123,7 +175,7 @@ final class ChatTextInputActionButtonsNode: ASDisplayNode {
|
||||
self.sendButtonRadialStatusNode = nil
|
||||
sendButtonRadialStatusNode.removeFromSupernode()
|
||||
}
|
||||
transition.updateSublayerTransformScale(layer: self.sendButton.layer, scale: CGPoint(x: 1.0, y: 1.0))
|
||||
transition.updateSublayerTransformScale(layer: self.sendContainerNode.layer, scale: CGPoint(x: 1.0, y: 1.0))
|
||||
}
|
||||
|
||||
transition.updateFrame(node: self.expandMediaInputButton, frame: CGRect(origin: CGPoint(), size: size))
|
||||
|
@ -433,7 +433,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
||||
private let accessoryButtonSpacing: CGFloat = 0.0
|
||||
private let accessoryButtonInset: CGFloat = 2.0
|
||||
|
||||
init(presentationInterfaceState: ChatPresentationInterfaceState, presentController: @escaping (ViewController) -> Void) {
|
||||
init(presentationInterfaceState: ChatPresentationInterfaceState, presentationContext: ChatPresentationContext?, presentController: @escaping (ViewController) -> Void) {
|
||||
self.presentationInterfaceState = presentationInterfaceState
|
||||
|
||||
self.clippingNode = ASDisplayNode()
|
||||
@ -475,7 +475,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
||||
self.searchLayoutClearImageNode.isUserInteractionEnabled = false
|
||||
self.searchLayoutClearButton.addSubnode(self.searchLayoutClearImageNode)
|
||||
|
||||
self.actionButtons = ChatTextInputActionButtonsNode(theme: presentationInterfaceState.theme, strings: presentationInterfaceState.strings, presentController: presentController)
|
||||
self.actionButtons = ChatTextInputActionButtonsNode(presentationInterfaceState: presentationInterfaceState, presentationContext: presentationContext, presentController: presentController)
|
||||
self.counterTextNode = ImmediateTextNode()
|
||||
self.counterTextNode.textAlignment = .center
|
||||
|
||||
@ -563,7 +563,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
||||
}
|
||||
|
||||
self.actionButtons.sendButton.addTarget(self, action: #selector(self.sendButtonPressed), forControlEvents: .touchUpInside)
|
||||
self.actionButtons.sendButton.alpha = 0.0
|
||||
self.actionButtons.sendContainerNode.alpha = 0.0
|
||||
self.actionButtons.updateAccessibility()
|
||||
|
||||
self.actionButtons.expandMediaInputButton.addTarget(self, action: #selector(self.expandButtonPressed), forControlEvents: .touchUpInside)
|
||||
@ -758,6 +758,15 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
||||
return minimalHeight
|
||||
}
|
||||
|
||||
private var absoluteRect: (CGRect, CGSize)?
|
||||
override func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize, transition: ContainedViewLayoutTransition) {
|
||||
self.absoluteRect = (rect, containerSize)
|
||||
|
||||
if !self.actionButtons.frame.width.isZero {
|
||||
self.actionButtons.updateAbsoluteRect(CGRect(origin: rect.origin.offsetBy(dx: self.actionButtons.frame.minX, dy: self.actionButtons.frame.minY), size: self.actionButtons.frame.size), within: containerSize, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics) -> CGFloat {
|
||||
let previousAdditionalSideInsets = self.validLayout?.3
|
||||
self.validLayout = (width, leftInset, rightInset, additionalSideInsets, maxHeight, metrics, isSecondary)
|
||||
@ -866,7 +875,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
||||
self.attachmentButton.setImage(PresentationResourcesChat.chatInputPanelAttachmentButtonImage(interfaceState.theme), for: [])
|
||||
}
|
||||
|
||||
self.actionButtons.updateTheme(theme: interfaceState.theme)
|
||||
self.actionButtons.updateTheme(theme: interfaceState.theme, wallpaper: interfaceState.chatWallpaper)
|
||||
|
||||
let textFieldMinHeight = calclulateTextFieldMinHeight(interfaceState, metrics: metrics)
|
||||
let minimalInputHeight: CGFloat = 2.0 + textFieldMinHeight
|
||||
@ -968,7 +977,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
||||
if !self.actionButtons.animatingSendButton {
|
||||
let imageNode = self.actionButtons.sendButton.imageNode
|
||||
|
||||
if transition.isAnimated && !self.actionButtons.sendButton.alpha.isZero && self.actionButtons.sendButton.layer.animation(forKey: "opacity") == nil, let previousImage = imageNode.image {
|
||||
if transition.isAnimated && !self.actionButtons.sendContainerNode.alpha.isZero && self.actionButtons.sendButton.layer.animation(forKey: "opacity") == nil, let previousImage = imageNode.image {
|
||||
let tempView = UIImageView(image: previousImage)
|
||||
self.actionButtons.sendButton.view.addSubview(tempView)
|
||||
tempView.frame = imageNode.frame
|
||||
@ -982,12 +991,12 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
||||
}
|
||||
self.actionButtons.sendButtonHasApplyIcon = sendButtonHasApplyIcon
|
||||
if self.actionButtons.sendButtonHasApplyIcon {
|
||||
self.actionButtons.sendButton.setImage(PresentationResourcesChat.chatInputPanelApplyButtonImage(interfaceState.theme), for: [])
|
||||
self.actionButtons.sendButton.setImage(PresentationResourcesChat.chatInputPanelApplyIconImage(interfaceState.theme), for: [])
|
||||
} else {
|
||||
if isScheduledMessages {
|
||||
self.actionButtons.sendButton.setImage(PresentationResourcesChat.chatInputPanelScheduleButtonImage(interfaceState.theme), for: [])
|
||||
} else {
|
||||
self.actionButtons.sendButton.setImage(PresentationResourcesChat.chatInputPanelSendButtonImage(interfaceState.theme), for: [])
|
||||
self.actionButtons.sendButton.setImage(PresentationResourcesChat.chatInputPanelSendIconImage(interfaceState.theme), for: [])
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1392,6 +1401,9 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
||||
|
||||
let actionButtonsFrame = CGRect(origin: CGPoint(x: width - rightInset - 43.0 - UIScreenPixel + composeButtonsOffset, y: panelHeight - minimalHeight), size: CGSize(width: 44.0, height: minimalHeight))
|
||||
transition.updateFrame(node: self.actionButtons, frame: actionButtonsFrame)
|
||||
if let (rect, containerSize) = self.absoluteRect {
|
||||
self.actionButtons.updateAbsoluteRect(CGRect(x: rect.origin.x + actionButtonsFrame.origin.x, y: rect.origin.y + actionButtonsFrame.origin.y, width: actionButtonsFrame.width, height: actionButtonsFrame.height), within: containerSize, transition: transition)
|
||||
}
|
||||
|
||||
if let presentationInterfaceState = self.presentationInterfaceState {
|
||||
self.actionButtons.updateLayout(size: CGSize(width: 44.0, height: minimalHeight), transition: transition, interfaceState: presentationInterfaceState)
|
||||
@ -1647,9 +1659,6 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
||||
self.menuButton.transform = CATransform3DIdentity
|
||||
self.menuButton.layer.animateScale(from: 0.3, to: 1.0, duration: 0.15, delay: 0, removeOnCompletion: false)
|
||||
}
|
||||
|
||||
prevPreviewInputPanelNode.sendButton.layer.animateScale(from: 1.0, to: 0.3, duration: 0.15, removeOnCompletion: false)
|
||||
prevPreviewInputPanelNode.sendButton.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false)
|
||||
}
|
||||
|
||||
var clippingDelta: CGFloat = 0.0
|
||||
@ -1761,23 +1770,22 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
||||
if self.extendedSearchLayout {
|
||||
hideMicButton = true
|
||||
|
||||
if !self.actionButtons.sendButton.alpha.isZero {
|
||||
self.actionButtons.sendButton.alpha = 0.0
|
||||
if !self.actionButtons.sendContainerNode.alpha.isZero {
|
||||
self.actionButtons.sendContainerNode.alpha = 0.0
|
||||
self.actionButtons.sendButtonRadialStatusNode?.alpha = 0.0
|
||||
self.actionButtons.updateAccessibility()
|
||||
if animated {
|
||||
self.actionButtons.animatingSendButton = true
|
||||
self.actionButtons.sendButton.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, completion: { [weak self] _ in
|
||||
self.actionButtons.sendContainerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, completion: { [weak self] _ in
|
||||
if let strongSelf = self {
|
||||
strongSelf.actionButtons.animatingSendButton = false
|
||||
strongSelf.applyUpdateSendButtonIcon()
|
||||
}
|
||||
})
|
||||
self.actionButtons.sendButton.layer.animateScale(from: 1.0, to: 0.2, duration: 0.2)
|
||||
self.actionButtons.sendButtonRadialStatusNode?.layer.animateScale(from: 1.0, to: 0.2, duration: 0.2)
|
||||
|
||||
self.actionButtons.sendButtonRadialStatusNode?.alpha = 0.0
|
||||
self.actionButtons.sendContainerNode.layer.animateScale(from: 1.0, to: 0.2, duration: 0.2)
|
||||
|
||||
self.actionButtons.sendButtonRadialStatusNode?.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2)
|
||||
self.actionButtons.sendButtonRadialStatusNode?.layer.animateScale(from: 1.0, to: 0.2, duration: 0.2)
|
||||
}
|
||||
}
|
||||
if self.searchLayoutClearButton.alpha.isZero {
|
||||
@ -1800,30 +1808,30 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
||||
|
||||
if (hasText || self.keepSendButtonEnabled && !mediaInputIsActive) {
|
||||
hideMicButton = true
|
||||
if self.actionButtons.sendButton.alpha.isZero {
|
||||
self.actionButtons.sendButton.alpha = 1.0
|
||||
if self.actionButtons.sendContainerNode.alpha.isZero {
|
||||
self.actionButtons.sendContainerNode.alpha = 1.0
|
||||
self.actionButtons.sendButtonRadialStatusNode?.alpha = 1.0
|
||||
self.actionButtons.updateAccessibility()
|
||||
if animated {
|
||||
self.actionButtons.sendButton.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1)
|
||||
self.actionButtons.sendContainerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1)
|
||||
self.actionButtons.sendButtonRadialStatusNode?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1)
|
||||
if animateWithBounce {
|
||||
self.actionButtons.sendButton.layer.animateSpring(from: NSNumber(value: Float(0.1)), to: NSNumber(value: Float(1.0)), keyPath: "transform.scale", duration: 0.6)
|
||||
self.actionButtons.sendContainerNode.layer.animateSpring(from: NSNumber(value: Float(0.1)), to: NSNumber(value: Float(1.0)), keyPath: "transform.scale", duration: 0.6)
|
||||
self.actionButtons.sendButtonRadialStatusNode?.layer.animateSpring(from: NSNumber(value: Float(0.1)), to: NSNumber(value: Float(1.0)), keyPath: "transform.scale", duration: 0.6)
|
||||
} else {
|
||||
self.actionButtons.sendButton.layer.animateScale(from: 0.2, to: 1.0, duration: 0.25)
|
||||
self.actionButtons.sendContainerNode.layer.animateScale(from: 0.2, to: 1.0, duration: 0.25)
|
||||
self.actionButtons.sendButtonRadialStatusNode?.layer.animateScale(from: 0.2, to: 1.0, duration: 0.25)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if !self.actionButtons.sendButton.alpha.isZero {
|
||||
self.actionButtons.sendButton.alpha = 0.0
|
||||
if !self.actionButtons.sendContainerNode.alpha.isZero {
|
||||
self.actionButtons.sendContainerNode.alpha = 0.0
|
||||
self.actionButtons.sendButtonRadialStatusNode?.alpha = 0.0
|
||||
self.actionButtons.updateAccessibility()
|
||||
if animated {
|
||||
self.actionButtons.animatingSendButton = true
|
||||
self.actionButtons.sendButton.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, completion: { [weak self] _ in
|
||||
self.actionButtons.sendContainerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, completion: { [weak self] _ in
|
||||
if let strongSelf = self {
|
||||
strongSelf.actionButtons.animatingSendButton = false
|
||||
strongSelf.applyUpdateSendButtonIcon()
|
||||
@ -1915,7 +1923,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
||||
}
|
||||
|
||||
@objc func editableTextNodeShouldReturn(_ editableTextNode: ASEditableTextNode) -> Bool {
|
||||
if self.actionButtons.sendButton.supernode != nil && !self.actionButtons.sendButton.isHidden && !self.actionButtons.sendButton.alpha.isZero {
|
||||
if self.actionButtons.sendButton.supernode != nil && !self.actionButtons.sendButton.isHidden && !self.actionButtons.sendContainerNode.alpha.isZero {
|
||||
self.sendButtonPressed()
|
||||
}
|
||||
return false
|
||||
@ -1928,12 +1936,12 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
||||
if sendButtonHasApplyIcon != self.actionButtons.sendButtonHasApplyIcon {
|
||||
self.actionButtons.sendButtonHasApplyIcon = sendButtonHasApplyIcon
|
||||
if self.actionButtons.sendButtonHasApplyIcon {
|
||||
self.actionButtons.sendButton.setImage(PresentationResourcesChat.chatInputPanelApplyButtonImage(interfaceState.theme), for: [])
|
||||
self.actionButtons.sendButton.setImage(PresentationResourcesChat.chatInputPanelApplyIconImage(interfaceState.theme), for: [])
|
||||
} else {
|
||||
if case .scheduledMessages = interfaceState.subject {
|
||||
self.actionButtons.sendButton.setImage(PresentationResourcesChat.chatInputPanelScheduleButtonImage(interfaceState.theme), for: [])
|
||||
self.actionButtons.sendButton.setImage(PresentationResourcesChat.chatInputPanelScheduleIconImage(interfaceState.theme), for: [])
|
||||
} else {
|
||||
self.actionButtons.sendButton.setImage(PresentationResourcesChat.chatInputPanelSendButtonImage(interfaceState.theme), for: [])
|
||||
self.actionButtons.sendButton.setImage(PresentationResourcesChat.chatInputPanelSendIconImage(interfaceState.theme), for: [])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -344,7 +344,7 @@ private final class ThemeSettingsThemeItemIconNode : ListViewItemNode {
|
||||
if let snapshotView = self.view.snapshotView(afterScreenUpdates: false) {
|
||||
self.view.addSubview(snapshotView)
|
||||
|
||||
snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak snapshotView] _ in
|
||||
snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, delay: 0.2, removeOnCompletion: false, completion: { [weak snapshotView] _ in
|
||||
snapshotView?.removeFromSuperview()
|
||||
})
|
||||
}
|
||||
@ -379,8 +379,7 @@ final class ChatThemeScreen: ViewController {
|
||||
private let context: AccountContext
|
||||
private let initiallySelectedEmoticon: String?
|
||||
private let dismissByTapOutside: Bool
|
||||
private let previewTheme: (String?) -> Void
|
||||
private let previewDarkTheme: (Bool) -> Void
|
||||
private let previewTheme: (String?, Bool?) -> Void
|
||||
private let completion: (String?) -> Void
|
||||
|
||||
private var presentationData: PresentationData
|
||||
@ -394,13 +393,12 @@ final class ChatThemeScreen: ViewController {
|
||||
}
|
||||
}
|
||||
|
||||
init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>), initiallySelectedEmoticon: String?, dismissByTapOutside: Bool = true, previewTheme: @escaping (String?) -> Void, previewDarkTheme: @escaping (Bool) -> Void, completion: @escaping (String?) -> Void) {
|
||||
init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>), initiallySelectedEmoticon: String?, dismissByTapOutside: Bool = true, previewTheme: @escaping (String?, Bool?) -> Void, completion: @escaping (String?) -> Void) {
|
||||
self.context = context
|
||||
self.presentationData = updatedPresentationData.initial
|
||||
self.initiallySelectedEmoticon = initiallySelectedEmoticon
|
||||
self.dismissByTapOutside = dismissByTapOutside
|
||||
self.previewTheme = previewTheme
|
||||
self.previewDarkTheme = previewDarkTheme
|
||||
self.completion = completion
|
||||
|
||||
super.init(navigationBarPresentationData: nil)
|
||||
@ -431,21 +429,15 @@ final class ChatThemeScreen: ViewController {
|
||||
override public func loadDisplayNode() {
|
||||
self.displayNode = ChatThemeScreenNode(context: self.context, presentationData: self.presentationData, initiallySelectedEmoticon: self.initiallySelectedEmoticon, dismissByTapOutside: self.dismissByTapOutside)
|
||||
self.controllerNode.passthroughHitTestImpl = self.passthroughHitTestImpl
|
||||
self.controllerNode.previewTheme = { [weak self] emoticon in
|
||||
self.controllerNode.previewTheme = { [weak self] emoticon, dark in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.previewTheme(emoticon ?? "")
|
||||
strongSelf.previewTheme((emoticon ?? ""), dark)
|
||||
}
|
||||
self.controllerNode.present = { [weak self] c in
|
||||
self?.present(c, in: .current)
|
||||
}
|
||||
self.controllerNode.previewDarkTheme = { [weak self] dark in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.previewDarkTheme(dark)
|
||||
}
|
||||
self.controllerNode.completion = { [weak self] emoticon in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
@ -464,7 +456,7 @@ final class ChatThemeScreen: ViewController {
|
||||
return
|
||||
}
|
||||
strongSelf.dismiss()
|
||||
strongSelf.previewTheme(nil)
|
||||
strongSelf.previewTheme(nil, nil)
|
||||
}
|
||||
}
|
||||
|
||||
@ -549,15 +541,20 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega
|
||||
}
|
||||
}
|
||||
private var selectedEmoticonPromise: ValuePromise<String?>
|
||||
|
||||
private var isDarkAppearancePromise: ValuePromise<Bool>
|
||||
private var isDarkAppearance: Bool = false {
|
||||
didSet {
|
||||
self.isDarkAppearancePromise.set(self.isDarkAppearance)
|
||||
}
|
||||
}
|
||||
|
||||
private var containerLayout: (ContainerViewLayout, CGFloat)?
|
||||
|
||||
private let disposable = MetaDisposable()
|
||||
|
||||
var present: ((ViewController) -> Void)?
|
||||
var previewTheme: ((String?) -> Void)?
|
||||
var previewDarkTheme: ((Bool) -> Void)?
|
||||
var previewTheme: ((String?, Bool?) -> Void)?
|
||||
var completion: ((String?) -> Void)?
|
||||
var dismiss: (() -> Void)?
|
||||
var cancel: (() -> Void)?
|
||||
@ -588,6 +585,7 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega
|
||||
self.backgroundNode.clipsToBounds = true
|
||||
self.backgroundNode.cornerRadius = 16.0
|
||||
|
||||
self.isDarkAppearance = self.presentationData.theme.overallDarkAppearance
|
||||
self.isDarkAppearancePromise = ValuePromise(self.presentationData.theme.overallDarkAppearance)
|
||||
|
||||
let backgroundColor = self.presentationData.theme.actionSheet.itemBackgroundColor
|
||||
@ -611,7 +609,7 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega
|
||||
self.cancelButton.setImage(closeButtonImage(theme: self.presentationData.theme), for: .normal)
|
||||
|
||||
self.switchThemeButton = HighlightTrackingButtonNode()
|
||||
self.animationNode = AnimationNode(animation: self.presentationData.theme.overallDarkAppearance ? "anim_sun_reverse" : "anim_sun", colors: iconColors(theme: self.presentationData.theme), scale: 1.0)
|
||||
self.animationNode = AnimationNode(animation: self.isDarkAppearance ? "anim_sun_reverse" : "anim_sun", colors: iconColors(theme: self.presentationData.theme), scale: 1.0)
|
||||
self.animationNode.isUserInteractionEnabled = false
|
||||
|
||||
self.doneButton = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(theme: self.presentationData.theme), height: 52.0, cornerRadius: 11.0, gloss: false)
|
||||
@ -676,7 +674,7 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega
|
||||
strongSelf.animateCrossfade(animateBackground: strongSelf.presentationData.theme.overallDarkAppearance, updateSunIcon: true)
|
||||
|
||||
strongSelf.selectedEmoticon = emoticon
|
||||
strongSelf.previewTheme?(emoticon)
|
||||
strongSelf.previewTheme?(emoticon, strongSelf.isDarkAppearance)
|
||||
let _ = ensureThemeVisible(listNode: strongSelf.listNode, emoticon: emoticon, animated: true)
|
||||
|
||||
strongSelf.doneButton.title = emoticon == nil ? strongSelf.presentationData.strings.Conversation_Theme_Reset : strongSelf.presentationData.strings.Conversation_Theme_Apply
|
||||
@ -774,9 +772,7 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega
|
||||
}
|
||||
let previousTheme = self.presentationData.theme
|
||||
self.presentationData = presentationData
|
||||
|
||||
self.isDarkAppearancePromise.set(presentationData.theme.overallDarkAppearance)
|
||||
|
||||
|
||||
if let effectView = self.effectNode.view as? UIVisualEffectView {
|
||||
effectView.effect = UIBlurEffect(style: presentationData.theme.actionSheet.backgroundType == .light ? .light : .dark)
|
||||
}
|
||||
@ -794,7 +790,7 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega
|
||||
if self.animationNode.isPlaying {
|
||||
|
||||
} else {
|
||||
self.animationNode.setAnimation(name: self.presentationData.theme.overallDarkAppearance ? "anim_sun_reverse" : "anim_sun", colors: iconColors(theme: self.presentationData.theme))
|
||||
self.animationNode.setAnimation(name: self.isDarkAppearance ? "anim_sun_reverse" : "anim_sun", colors: iconColors(theme: self.presentationData.theme))
|
||||
}
|
||||
}
|
||||
|
||||
@ -814,9 +810,12 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega
|
||||
|
||||
@objc func switchThemePressed() {
|
||||
self.animateCrossfade(animateBackground: true)
|
||||
self.animationNode.setAnimation(name: self.presentationData.theme.overallDarkAppearance ? "anim_sun_reverse" : "anim_sun", colors: iconColors(theme: self.presentationData.theme))
|
||||
self.animationNode.setAnimation(name: self.isDarkAppearance ? "anim_sun_reverse" : "anim_sun", colors: iconColors(theme: self.presentationData.theme))
|
||||
self.animationNode.playOnce()
|
||||
self.previewDarkTheme?(!self.presentationData.theme.overallDarkAppearance)
|
||||
|
||||
let isDarkAppearance = !self.isDarkAppearance
|
||||
self.previewTheme?(self.selectedEmoticon, isDarkAppearance)
|
||||
self.isDarkAppearance = isDarkAppearance
|
||||
|
||||
let _ = ApplicationSpecificNotice.incrementChatSpecificThemesDarkPreviewTip(accountManager: self.context.sharedContext.accountManager, count: 3).start()
|
||||
}
|
||||
@ -828,7 +827,7 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega
|
||||
}
|
||||
|
||||
private func animateCrossfade(animateBackground: Bool = true, updateSunIcon: Bool = false) {
|
||||
let delay: Double = animateBackground ? 0.0 : 0.2
|
||||
let delay: Double = 0.2
|
||||
|
||||
if let snapshotView = self.animationNode.view.snapshotView(afterScreenUpdates: false) {
|
||||
snapshotView.frame = self.animationNode.frame
|
||||
@ -856,16 +855,7 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega
|
||||
snapshotView?.removeFromSuperview()
|
||||
})
|
||||
}
|
||||
|
||||
if animateBackground, let snapshotView = self.cancelButton.view.snapshotView(afterScreenUpdates: false) {
|
||||
snapshotView.frame = self.cancelButton.frame
|
||||
self.cancelButton.view.superview?.insertSubview(snapshotView, aboveSubview: self.cancelButton.view)
|
||||
|
||||
snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, delay: delay, removeOnCompletion: false, completion: { [weak snapshotView] _ in
|
||||
snapshotView?.removeFromSuperview()
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
self.listNode.forEachVisibleItemNode { node in
|
||||
if let node = node as? ThemeSettingsThemeItemIconNode {
|
||||
node.crossfade()
|
||||
|
@ -39,7 +39,12 @@ final class ContactSelectionControllerNode: ASDisplayNode {
|
||||
var requestMultipleAction: (() -> Void)?
|
||||
var dismiss: (() -> Void)?
|
||||
|
||||
var presentationData: PresentationData
|
||||
var presentationData: PresentationData {
|
||||
didSet {
|
||||
self.presentationDataPromise.set(.single(self.presentationData))
|
||||
}
|
||||
}
|
||||
private var presentationDataPromise = Promise<PresentationData>()
|
||||
|
||||
private let countPanelNode: ContactSelectionCountPanelNode
|
||||
|
||||
@ -51,7 +56,7 @@ final class ContactSelectionControllerNode: ASDisplayNode {
|
||||
self.displayDeviceContacts = displayDeviceContacts
|
||||
self.displayCallIcons = displayCallIcons
|
||||
|
||||
self.contactListNode = ContactListNode(context: context, presentation: .single(.natural(options: options, includeChatList: false)), displayCallIcons: displayCallIcons, multipleSelection: multipleSelection)
|
||||
self.contactListNode = ContactListNode(context: context, updatedPresentationData: (presentationData, self.presentationDataPromise.get()), presentation: .single(.natural(options: options, includeChatList: false)), displayCallIcons: displayCallIcons, multipleSelection: multipleSelection)
|
||||
|
||||
self.dimNode = ASDisplayNode()
|
||||
|
||||
@ -151,7 +156,7 @@ final class ContactSelectionControllerNode: ASDisplayNode {
|
||||
} else {
|
||||
categories.insert(.global)
|
||||
}
|
||||
self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, contentNode: ContactsSearchContainerNode(context: self.context, onlyWriteable: false, categories: categories, addContact: nil, openPeer: { [weak self] peer in
|
||||
self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, contentNode: ContactsSearchContainerNode(context: self.context, updatedPresentationData: (self.presentationData, self.presentationDataPromise.get()), onlyWriteable: false, categories: categories, addContact: nil, openPeer: { [weak self] peer in
|
||||
if let strongSelf = self {
|
||||
var updated = false
|
||||
strongSelf.contactListNode.updateSelectionState { state -> ContactListNodeGroupSelectionState? in
|
||||
|
@ -590,7 +590,7 @@ private final class DrawingStickersScreenNode: ViewControllerTracingNode {
|
||||
}
|
||||
|
||||
let panelEntries = chatMediaInputPanelEntries(view: view, savedStickers: savedStickers, recentStickers: recentStickers, peerSpecificPack: nil, canInstallPeerSpecificPack: .none, theme: theme, strings: strings, hasGifs: false, hasSettings: false)
|
||||
let gridEntries = chatMediaInputGridEntries(view: view, savedStickers: savedStickers, recentStickers: recentStickers, peerSpecificPack: nil, canInstallPeerSpecificPack: .none, trendingPacks: trendingPacks, installedPacks: installedPacks, hasSearch: false, hasAccessories: false, strings: strings, theme: theme)
|
||||
let gridEntries = chatMediaInputGridEntries(view: view, savedStickers: savedStickers, recentStickers: recentStickers, peerSpecificPack: nil, canInstallPeerSpecificPack: .none, trendingPacks: [], installedPacks: installedPacks, hasSearch: false, hasAccessories: false, strings: strings, theme: theme)
|
||||
|
||||
let (previousPanelEntries, previousGridEntries) = previousStickerEntries.swap((panelEntries, gridEntries))
|
||||
return (view, preparedChatMediaInputPanelEntryTransition(context: context, from: previousPanelEntries, to: panelEntries, inputNodeInteraction: stickersInputNodeInteraction, scrollToItem: nil), previousPanelEntries.isEmpty, preparedChatMediaInputGridEntryTransition(account: context.account, view: view, from: previousGridEntries, to: gridEntries, update: update, interfaceInteraction: controllerInteraction, inputNodeInteraction: stickersInputNodeInteraction, trendingInteraction: trendingInteraction), previousGridEntries.isEmpty)
|
||||
|
@ -1492,6 +1492,8 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
|
||||
private var presentationData: PresentationData
|
||||
|
||||
fileprivate let cachedDataPromise = Promise<CachedPeerData?>()
|
||||
|
||||
let scrollNode: ASScrollNode
|
||||
|
||||
let headerNode: PeerInfoHeaderNode
|
||||
@ -1848,7 +1850,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
}
|
||||
|
||||
return items
|
||||
})
|
||||
}, minHeight: nil)
|
||||
})))
|
||||
}
|
||||
if strongSelf.searchDisplayController == nil {
|
||||
@ -1982,7 +1984,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
}
|
||||
|
||||
return items
|
||||
})
|
||||
}, minHeight: nil)
|
||||
})))
|
||||
}
|
||||
|
||||
@ -2842,6 +2844,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
return
|
||||
}
|
||||
strongSelf.updateData(data)
|
||||
strongSelf.cachedDataPromise.set(.single(data.cachedData))
|
||||
})
|
||||
|
||||
if let _ = nearbyPeerDistance {
|
||||
@ -3422,7 +3425,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
return context.engine.peers.updatePeerDisplayPreviewsSetting(peerId: peerId, displayPreviews: displayPreviews) |> deliverOnMainQueue
|
||||
}
|
||||
|
||||
let exceptionController = notificationPeerExceptionController(context: context, peer: peer, mode: .users([:]), edit: true, updatePeerSound: { peerId, sound in
|
||||
let exceptionController = notificationPeerExceptionController(context: context, updatedPresentationData: strongSelf.controller?.updatedPresentationData, peer: peer, mode: .users([:]), edit: true, updatePeerSound: { peerId, sound in
|
||||
let _ = (updatePeerSound(peer.id, sound)
|
||||
|> deliverOnMainQueue).start(next: { _ in
|
||||
|
||||
@ -3690,7 +3693,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)
|
||||
}
|
||||
})
|
||||
})))
|
||||
@ -4432,7 +4435,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 {
|
||||
@ -4528,7 +4531,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 {
|
||||
@ -5284,7 +5287,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
let controller = WebSearchController(context: strongSelf.context, peer: peer, chatLocation: nil, configuration: searchBotsConfiguration, mode: .avatar(initialQuery: strongSelf.isSettings ? nil : peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), completion: { [weak self] result in
|
||||
let controller = WebSearchController(context: strongSelf.context, updatedPresentationData: strongSelf.controller?.updatedPresentationData, peer: peer, chatLocation: nil, configuration: searchBotsConfiguration, mode: .avatar(initialQuery: strongSelf.isSettings ? nil : peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), completion: { [weak self] result in
|
||||
assetsController?.dismiss()
|
||||
self?.updateProfilePhoto(result)
|
||||
}))
|
||||
@ -6502,6 +6505,7 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen {
|
||||
|
||||
fileprivate var presentationData: PresentationData
|
||||
private var presentationDataDisposable: Disposable?
|
||||
private let cachedDataPromise = Promise<CachedPeerData?>()
|
||||
|
||||
private let accountsAndPeers = Promise<((AccountContext, Peer)?, [(AccountContext, Peer, Int32)])>()
|
||||
private var accountsAndPeersValue: ((AccountContext, Peer)?, [(AccountContext, Peer, Int32)])?
|
||||
@ -6756,6 +6760,38 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen {
|
||||
self?.controllerNode.scrollToTop()
|
||||
}
|
||||
|
||||
let presentationDataSignal: Signal<PresentationData, NoError>
|
||||
if let updatedPresentationData = updatedPresentationData {
|
||||
presentationDataSignal = updatedPresentationData.signal
|
||||
} else {
|
||||
let themeEmoticon: Signal<String?, NoError> = self.cachedDataPromise.get()
|
||||
|> map { cachedData -> String? in
|
||||
if let cachedData = cachedData as? CachedUserData {
|
||||
return cachedData.themeEmoticon
|
||||
} else if let cachedData = cachedData as? CachedGroupData {
|
||||
return cachedData.themeEmoticon
|
||||
} else if let cachedData = cachedData as? CachedChannelData {
|
||||
return cachedData.themeEmoticon
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|> distinctUntilChanged
|
||||
|
||||
presentationDataSignal = combineLatest(queue: Queue.mainQueue(), context.sharedContext.presentationData, context.engine.themes.getChatThemes(accountManager: context.sharedContext.accountManager, onlyCached: false), themeEmoticon)
|
||||
|> map { presentationData, chatThemes, themeEmoticon -> PresentationData in
|
||||
var presentationData = presentationData
|
||||
if let themeEmoticon = themeEmoticon, let theme = chatThemes.first(where: { $0.emoji == themeEmoticon }) {
|
||||
let customTheme = presentationData.theme.overallDarkAppearance ? theme.darkTheme : theme.theme
|
||||
if let settings = customTheme.settings, let theme = makePresentationTheme(settings: settings) {
|
||||
presentationData = presentationData.withUpdated(theme: theme)
|
||||
presentationData = presentationData.withUpdated(chatWallpaper: theme.chat.defaultWallpaper)
|
||||
}
|
||||
}
|
||||
return presentationData
|
||||
}
|
||||
}
|
||||
|
||||
self.presentationDataDisposable = ((updatedPresentationData?.signal ?? context.sharedContext.presentationData)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] presentationData in
|
||||
if let strongSelf = self {
|
||||
@ -6789,6 +6825,7 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen {
|
||||
self.displayNode = PeerInfoScreenNode(controller: self, context: self.context, peerId: self.peerId, avatarInitiallyExpanded: self.avatarInitiallyExpanded, isOpenedFromChat: self.isOpenedFromChat, nearbyPeerDistance: self.nearbyPeerDistance, callMessages: self.callMessages, isSettings: self.isSettings, ignoreGroupInCommon: self.ignoreGroupInCommon)
|
||||
self.controllerNode.accountsAndPeers.set(self.accountsAndPeers.get() |> map { $0.1 })
|
||||
self.controllerNode.activeSessionsContextAndCount.set(self.activeSessionsContextAndCount.get())
|
||||
self.cachedDataPromise.set(self.controllerNode.cachedDataPromise.get())
|
||||
self._ready.set(self.controllerNode.ready.get())
|
||||
|
||||
super.displayNodeDidLoad()
|
||||
|
@ -147,6 +147,7 @@ public final class PeerSelectionControllerImpl: ViewController, PeerSelectionCon
|
||||
self.searchContentNode?.updateThemeAndPlaceholder(theme: self.presentationData.theme, placeholder: self.presentationData.strings.Common_Search)
|
||||
self.title = self.customTitle ?? self.presentationData.strings.Conversation_ForwardTitle
|
||||
self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil)
|
||||
self.peerSelectionNode.updatePresentationData(self.presentationData)
|
||||
}
|
||||
|
||||
override public func loadDisplayNode() {
|
||||
|
@ -60,14 +60,22 @@ final class PeerSelectionControllerNode: ASDisplayNode {
|
||||
var requestOpenMessageFromSearch: ((Peer, MessageId) -> Void)?
|
||||
var requestSend: (([Peer], [PeerId: Peer], NSAttributedString, PeerSelectionControllerSendMode, ChatInterfaceForwardOptionsState?) -> Void)?
|
||||
|
||||
private var presentationData: PresentationData
|
||||
private var presentationDataDisposable: Disposable?
|
||||
private var presentationData: PresentationData {
|
||||
didSet {
|
||||
self.presentationDataPromise.set(.single(self.presentationData))
|
||||
}
|
||||
}
|
||||
private var presentationDataPromise = Promise<PresentationData>()
|
||||
|
||||
private var readyValue = Promise<Bool>()
|
||||
var ready: Signal<Bool, NoError> {
|
||||
return self.readyValue.get()
|
||||
}
|
||||
|
||||
private var updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>) {
|
||||
return (self.presentationData, self.presentationDataPromise.get())
|
||||
}
|
||||
|
||||
init(context: AccountContext, presentationData: PresentationData, filter: ChatListNodePeersFilter, hasChatListSelector: Bool, hasContactSelector: Bool, hasGlobalSearch: Bool, forwardedMessageIds: [EngineMessage.Id], createNewGroup: (() -> Void)?, present: @escaping (ViewController, Any?) -> Void, presentInGlobalOverlay: @escaping (ViewController, Any?) -> Void, dismiss: @escaping () -> Void) {
|
||||
self.context = context
|
||||
self.present = present
|
||||
@ -154,17 +162,6 @@ final class PeerSelectionControllerNode: ASDisplayNode {
|
||||
}
|
||||
|
||||
self.addSubnode(self.chatListNode)
|
||||
self.presentationDataDisposable = (context.sharedContext.presentationData
|
||||
|> deliverOnMainQueue).start(next: { [weak self] presentationData in
|
||||
if let strongSelf = self {
|
||||
let previousTheme = strongSelf.presentationData.theme
|
||||
let previousStrings = strongSelf.presentationData.strings
|
||||
strongSelf.presentationData = presentationData
|
||||
if previousTheme !== presentationData.theme || previousStrings !== presentationData.strings {
|
||||
strongSelf.updateThemeAndStrings()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if hasChatListSelector && hasContactSelector {
|
||||
self.segmentedControlNode!.selectedIndexChanged = { [weak self] index in
|
||||
@ -308,8 +305,9 @@ final class PeerSelectionControllerNode: ASDisplayNode {
|
||||
self.readyValue.set(self.chatListNode.ready)
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.presentationDataDisposable?.dispose()
|
||||
func updatePresentationData(_ presentationData: PresentationData) {
|
||||
self.presentationData = presentationData
|
||||
self.updateThemeAndStrings()
|
||||
}
|
||||
|
||||
private func updateChatPresentationInterfaceState(animated: Bool = true, _ f: (ChatPresentationInterfaceState) -> ChatPresentationInterfaceState, completion: @escaping (ContainedViewLayoutTransition) -> Void = { _ in }) {
|
||||
@ -520,7 +518,7 @@ final class PeerSelectionControllerNode: ASDisplayNode {
|
||||
}
|
||||
|
||||
if self.chatListNode.supernode != nil {
|
||||
self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, contentNode: ChatListSearchContainerNode(context: self.context, filter: self.filter, groupId: .root, displaySearchFilters: false, openPeer: { [weak self] peer, chatPeer, _ in
|
||||
self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, contentNode: ChatListSearchContainerNode(context: self.context, updatedPresentationData: self.updatedPresentationData, filter: self.filter, groupId: .root, displaySearchFilters: false, openPeer: { [weak self] peer, chatPeer, _ in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
@ -597,7 +595,7 @@ final class PeerSelectionControllerNode: ASDisplayNode {
|
||||
if self.hasGlobalSearch {
|
||||
categories.insert(.global)
|
||||
}
|
||||
self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, contentNode: ContactsSearchContainerNode(context: self.context, onlyWriteable: true, categories: categories, addContact: nil, openPeer: { [weak self] peer in
|
||||
self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, contentNode: ContactsSearchContainerNode(context: self.context, updatedPresentationData: self.updatedPresentationData, onlyWriteable: true, categories: categories, addContact: nil, openPeer: { [weak self] peer in
|
||||
if let strongSelf = self {
|
||||
var updated = false
|
||||
var count = 0
|
||||
@ -693,7 +691,7 @@ final class PeerSelectionControllerNode: ASDisplayNode {
|
||||
self.recursivelyEnsureDisplaySynchronously(true)
|
||||
contactListNode.enableUpdates = true
|
||||
} else {
|
||||
let contactListNode = ContactListNode(context: context, presentation: .single(.natural(options: [], includeChatList: false)))
|
||||
let contactListNode = ContactListNode(context: self.context, updatedPresentationData: self.updatedPresentationData, presentation: .single(.natural(options: [], includeChatList: false)))
|
||||
self.contactListNode = contactListNode
|
||||
contactListNode.enableUpdates = true
|
||||
contactListNode.selectionStateUpdated = { [weak self] selectionState in
|
||||
|
@ -228,7 +228,7 @@ class PeerSelectionTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDel
|
||||
self.textPlaceholderNode.maximumNumberOfLines = 1
|
||||
self.textPlaceholderNode.isUserInteractionEnabled = false
|
||||
|
||||
self.actionButtons = ChatTextInputActionButtonsNode(theme: presentationInterfaceState.theme, strings: presentationInterfaceState.strings, presentController: presentController)
|
||||
self.actionButtons = ChatTextInputActionButtonsNode(presentationInterfaceState: presentationInterfaceState, presentationContext: nil, presentController: presentController)
|
||||
self.counterTextNode = ImmediateTextNode()
|
||||
self.counterTextNode.textAlignment = .center
|
||||
|
||||
@ -440,7 +440,7 @@ class PeerSelectionTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDel
|
||||
|
||||
self.theme = interfaceState.theme
|
||||
|
||||
self.actionButtons.updateTheme(theme: interfaceState.theme)
|
||||
self.actionButtons.updateTheme(theme: interfaceState.theme, wallpaper: interfaceState.chatWallpaper)
|
||||
|
||||
let textFieldMinHeight = calclulateTextFieldMinHeight(interfaceState, metrics: metrics)
|
||||
let minimalInputHeight: CGFloat = 2.0 + textFieldMinHeight
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit aaede253be4dbfdfcb2258cdb6faa843785192e6
|
||||
Subproject commit f76a9290fa502a8df473dd872aedf9a553b089cc
|
@ -215,14 +215,20 @@ public final class WallpaperBackgroundNode: ASDisplayNode {
|
||||
let shiftedContentsRect = CGRect(origin: CGPoint(x: rect.minX / containerSize.width, y: rect.minY / containerSize.height), size: CGSize(width: rect.width / containerSize.width, height: rect.height / containerSize.height))
|
||||
|
||||
transition.updateFrame(layer: self.contentNode.layer, frame: self.bounds)
|
||||
self.contentNode.layer.contentsRect = shiftedContentsRect
|
||||
transition.animateView {
|
||||
self.contentNode.layer.contentsRect = shiftedContentsRect
|
||||
}
|
||||
if let cleanWallpaperNode = self.cleanWallpaperNode {
|
||||
transition.updateFrame(layer: cleanWallpaperNode.layer, frame: self.bounds)
|
||||
cleanWallpaperNode.layer.contentsRect = shiftedContentsRect
|
||||
transition.animateView {
|
||||
cleanWallpaperNode.layer.contentsRect = shiftedContentsRect
|
||||
}
|
||||
}
|
||||
if let gradientWallpaperNode = self.gradientWallpaperNode {
|
||||
transition.updateFrame(layer: gradientWallpaperNode.layer, frame: self.bounds)
|
||||
gradientWallpaperNode.layer.contentsRect = shiftedContentsRect
|
||||
transition.animateView {
|
||||
gradientWallpaperNode.layer.contentsRect = shiftedContentsRect
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -7,6 +7,7 @@ import AsyncDisplayKit
|
||||
import TelegramCore
|
||||
import LegacyComponents
|
||||
import TelegramUIPreferences
|
||||
import TelegramPresentationData
|
||||
import AccountContext
|
||||
|
||||
public func requestContextResults(context: AccountContext, botId: PeerId, query: String, peerId: PeerId, offset: String = "", existingResults: ChatContextResultCollection? = nil, incompleteResults: Bool = false, staleCachedResults: Bool = false, limit: Int = 60) -> Signal<RequestChatContextResultsResult?, NoError> {
|
||||
@ -155,14 +156,14 @@ public final class WebSearchController: ViewController {
|
||||
}
|
||||
}
|
||||
|
||||
public init(context: AccountContext, peer: Peer?, chatLocation: ChatLocation?, configuration: SearchBotsConfiguration, mode: WebSearchControllerMode) {
|
||||
public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, peer: Peer?, chatLocation: ChatLocation?, configuration: SearchBotsConfiguration, mode: WebSearchControllerMode) {
|
||||
self.context = context
|
||||
self.mode = mode
|
||||
self.peer = peer
|
||||
self.chatLocation = chatLocation
|
||||
self.configuration = configuration
|
||||
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
let presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 }
|
||||
self.interfaceState = WebSearchInterfaceState(presentationData: presentationData)
|
||||
|
||||
var searchQuery: String?
|
||||
@ -199,7 +200,7 @@ public final class WebSearchController: ViewController {
|
||||
}
|
||||
|> distinctUntilChanged
|
||||
|
||||
self.disposable = ((combineLatest(settings, context.sharedContext.presentationData, gifProvider))
|
||||
self.disposable = ((combineLatest(settings, (updatedPresentationData?.signal ?? context.sharedContext.presentationData), gifProvider))
|
||||
|> deliverOnMainQueue).start(next: { [weak self] settings, presentationData, gifProvider in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
|
@ -25,7 +25,7 @@
|
||||
size_t height = 0;
|
||||
_animation->size(width, height);
|
||||
|
||||
if (width > 1024 || height > 1024) {
|
||||
if (width > 1536 || height > 1536) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"app": "8.0",
|
||||
"app": "8.0.1",
|
||||
"bazel": "4.0.0",
|
||||
"xcode": "12.5.1"
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user