Merge commit '34ec6b26174789e5f12bc6a32b2e0faaf140db75'

This commit is contained in:
Ali 2021-04-30 23:46:44 +04:00
commit ffc9deba9f
35 changed files with 4108 additions and 3845 deletions

View File

@ -6463,5 +6463,6 @@ Sorry for the inconvenience.";
"Calls.StartNewCall" = "Start New Call";
"VoiceChat.VideoPreviewTitle" = "Video Preview";
"VoiceChat.VideoPreviewDescription" = "Place your face in the area above for better experience.";
"VoiceChat.VideoPreviewShare" = "Share Video";
"VoiceChat.VideoPreviewDescription" = "Are you sure you want to share your video?";
"VoiceChat.VideoPreviewShareCamera" = "Share Camera Video";
"VoiceChat.VideoPreviewShareScreen" = "Share Screen";

View File

@ -219,17 +219,14 @@ public final class ChatPeerNearbyData: Equatable {
public final class ChatGreetingData: Equatable {
public static func == (lhs: ChatGreetingData, rhs: ChatGreetingData) -> Bool {
if let lhsSticker = lhs.sticker, let rhsSticker = rhs.sticker, !lhsSticker.isEqual(to: rhsSticker) {
return false
} else if (lhs.sticker == nil) != (rhs.sticker == nil) {
return false
}
return true
return lhs.uuid == rhs.uuid
}
public let sticker: TelegramMediaFile?
public let uuid: UUID
public let sticker: Signal<TelegramMediaFile?, NoError>
public init(sticker: TelegramMediaFile?) {
public init(uuid: UUID, sticker: Signal<TelegramMediaFile?, NoError>) {
self.uuid = uuid
self.sticker = sticker
}
}
@ -285,14 +282,13 @@ public final class NavigateToChatControllerParams {
public let activateMessageSearch: (ChatSearchDomain, String)?
public let peekData: ChatPeekTimeout?
public let peerNearbyData: ChatPeerNearbyData?
public let greetingData: ChatGreetingData?
public let reportReason: ReportReason?
public let animated: Bool
public let options: NavigationAnimationOptions
public let parentGroupId: PeerGroupId?
public let completion: (ChatController) -> Void
public init(navigationController: NavigationController, chatController: ChatController? = nil, context: AccountContext, chatLocation: ChatLocation, chatLocationContextHolder: Atomic<ChatLocationContextHolder?> = Atomic<ChatLocationContextHolder?>(value: nil), subject: ChatControllerSubject? = nil, botStart: ChatControllerInitialBotStart? = nil, updateTextInputState: ChatTextInputState? = nil, activateInput: Bool = false, keepStack: NavigateToChatKeepStack = .default, useExisting: Bool = true, purposefulAction: (() -> Void)? = nil, scrollToEndIfExists: Bool = false, activateMessageSearch: (ChatSearchDomain, String)? = nil, peekData: ChatPeekTimeout? = nil, peerNearbyData: ChatPeerNearbyData? = nil, greetingData: ChatGreetingData? = nil, reportReason: ReportReason? = nil, animated: Bool = true, options: NavigationAnimationOptions = [], parentGroupId: PeerGroupId? = nil, completion: @escaping (ChatController) -> Void = { _ in }) {
public init(navigationController: NavigationController, chatController: ChatController? = nil, context: AccountContext, chatLocation: ChatLocation, chatLocationContextHolder: Atomic<ChatLocationContextHolder?> = Atomic<ChatLocationContextHolder?>(value: nil), subject: ChatControllerSubject? = nil, botStart: ChatControllerInitialBotStart? = nil, updateTextInputState: ChatTextInputState? = nil, activateInput: Bool = false, keepStack: NavigateToChatKeepStack = .default, useExisting: Bool = true, purposefulAction: (() -> Void)? = nil, scrollToEndIfExists: Bool = false, activateMessageSearch: (ChatSearchDomain, String)? = nil, peekData: ChatPeekTimeout? = nil, peerNearbyData: ChatPeerNearbyData? = nil, reportReason: ReportReason? = nil, animated: Bool = true, options: NavigationAnimationOptions = [], parentGroupId: PeerGroupId? = nil, completion: @escaping (ChatController) -> Void = { _ in }) {
self.navigationController = navigationController
self.chatController = chatController
self.chatLocationContextHolder = chatLocationContextHolder
@ -309,7 +305,6 @@ public final class NavigateToChatControllerParams {
self.activateMessageSearch = activateMessageSearch
self.peekData = peekData
self.peerNearbyData = peerNearbyData
self.greetingData = greetingData
self.reportReason = reportReason
self.animated = animated
self.options = options
@ -719,6 +714,7 @@ public protocol AccountContext: class {
var liveLocationManager: LiveLocationManager? { get }
var peersNearbyManager: PeersNearbyManager? { get }
var fetchManager: FetchManager { get }
var prefetchManager: PrefetchManager? { get }
var downloadedMediaStoreManager: DownloadedMediaStoreManager { get }
var peerChannelMemberCategoriesContextsManager: PeerChannelMemberCategoriesContextsManager { get }
var wallpaperUploadManager: WallpaperUploadManager? { get }

View File

@ -158,3 +158,8 @@ public protocol FetchManager {
func cancelInteractiveFetches(category: FetchManagerCategory, location: FetchManagerLocation, locationKey: FetchManagerLocationKey, resource: MediaResource)
func fetchStatus(category: FetchManagerCategory, location: FetchManagerLocation, locationKey: FetchManagerLocationKey, resource: MediaResource) -> Signal<MediaResourceStatus, NoError>
}
public protocol PrefetchManager {
var preloadedGreetingSticker: ChatGreetingData { get }
func prepareNextGreetingSticker()
}

View File

@ -372,6 +372,7 @@ public protocol PresentationGroupCall: class {
var incomingVideoSources: Signal<Set<String>, NoError> { get }
func makeIncomingVideoView(endpointId: String, completion: @escaping (PresentationCallVideoView?) -> Void)
func makeOutgoingVideoView(completion: @escaping (PresentationCallVideoView?) -> Void)
func loadMoreMembers(token: String)
}

View File

@ -144,9 +144,6 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
private let featuredFiltersDisposable = MetaDisposable()
private var processedFeaturedFilters = false
private let preloadedSticker = Promise<TelegramMediaFile?>(nil)
private let preloadStickerDisposable = MetaDisposable()
private let isReorderingTabsValue = ValuePromise<Bool>(false)
private var searchContentNode: NavigationBarSearchContentNode?
@ -613,53 +610,43 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
scrollToEndIfExists = true
}
let _ = (strongSelf.preloadedSticker.get()
|> take(1)
|> deliverOnMainQueue).start(next: { [weak self] greetingSticker in
if let strongSelf = self {
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peer.id), activateInput: activateInput && !peer.isDeleted, scrollToEndIfExists: scrollToEndIfExists, greetingData: greetingSticker.flatMap({ ChatGreetingData(sticker: $0) }), animated: !scrollToEndIfExists, options: strongSelf.groupId == PeerGroupId.root ? [.removeOnMasterDetails] : [], parentGroupId: strongSelf.groupId, completion: { [weak self] controller in
self?.chatListDisplayNode.containerNode.currentItemNode.clearHighlightAnimated(true)
if let promoInfo = promoInfo {
switch promoInfo {
case .proxy:
let _ = (ApplicationSpecificNotice.getProxyAdsAcknowledgment(accountManager: strongSelf.context.sharedContext.accountManager)
|> deliverOnMainQueue).start(next: { value in
guard let strongSelf = self else {
return
}
if !value {
controller.displayPromoAnnouncement(text: strongSelf.presentationData.strings.DialogList_AdNoticeAlert)
let _ = ApplicationSpecificNotice.setProxyAdsAcknowledgment(accountManager: strongSelf.context.sharedContext.accountManager).start()
}
})
case let .psa(type, _):
let _ = (ApplicationSpecificNotice.getPsaAcknowledgment(accountManager: strongSelf.context.sharedContext.accountManager, peerId: peer.id)
|> deliverOnMainQueue).start(next: { value in
guard let strongSelf = self else {
return
}
if !value {
var text = strongSelf.presentationData.strings.ChatList_GenericPsaAlert
let key = "ChatList.PsaAlert.\(type)"
if let string = strongSelf.presentationData.strings.primaryComponent.dict[key] {
text = string
} else if let string = strongSelf.presentationData.strings.secondaryComponent?.dict[key] {
text = string
}
controller.displayPromoAnnouncement(text: text)
let _ = ApplicationSpecificNotice.setPsaAcknowledgment(accountManager: strongSelf.context.sharedContext.accountManager, peerId: peer.id).start()
}
})
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peer.id), activateInput: activateInput && !peer.isDeleted, scrollToEndIfExists: scrollToEndIfExists, animated: !scrollToEndIfExists, options: strongSelf.groupId == PeerGroupId.root ? [.removeOnMasterDetails] : [], parentGroupId: strongSelf.groupId, completion: { [weak self] controller in
self?.chatListDisplayNode.containerNode.currentItemNode.clearHighlightAnimated(true)
if let promoInfo = promoInfo {
switch promoInfo {
case .proxy:
let _ = (ApplicationSpecificNotice.getProxyAdsAcknowledgment(accountManager: strongSelf.context.sharedContext.accountManager)
|> deliverOnMainQueue).start(next: { value in
guard let strongSelf = self else {
return
}
}
}))
if activateInput {
strongSelf.prepareRandomGreetingSticker()
if !value {
controller.displayPromoAnnouncement(text: strongSelf.presentationData.strings.DialogList_AdNoticeAlert)
let _ = ApplicationSpecificNotice.setProxyAdsAcknowledgment(accountManager: strongSelf.context.sharedContext.accountManager).start()
}
})
case let .psa(type, _):
let _ = (ApplicationSpecificNotice.getPsaAcknowledgment(accountManager: strongSelf.context.sharedContext.accountManager, peerId: peer.id)
|> deliverOnMainQueue).start(next: { value in
guard let strongSelf = self else {
return
}
if !value {
var text = strongSelf.presentationData.strings.ChatList_GenericPsaAlert
let key = "ChatList.PsaAlert.\(type)"
if let string = strongSelf.presentationData.strings.primaryComponent.dict[key] {
text = string
} else if let string = strongSelf.presentationData.strings.secondaryComponent?.dict[key] {
text = string
}
controller.displayPromoAnnouncement(text: text)
let _ = ApplicationSpecificNotice.setPsaAcknowledgment(accountManager: strongSelf.context.sharedContext.accountManager, peerId: peer.id).start()
}
})
}
}
})
}))
}
}
}
@ -1126,7 +1113,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
super.displayNodeDidLoad()
Queue.mainQueue().after(1.0) {
self.prepareRandomGreetingSticker()
self.context.prefetchManager?.prepareNextGreetingSticker()
}
}
@ -2763,28 +2750,6 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
}
}
private func prepareRandomGreetingSticker() {
let context = self.context
self.preloadedSticker.set(.single(nil)
|> then(context.engine.stickers.randomGreetingSticker()
|> map { item in
return item?.file
}))
self.preloadStickerDisposable.set((self.preloadedSticker.get()
|> mapToSignal { sticker -> Signal<Void, NoError> in
if let sticker = sticker {
let _ = freeMediaFileInteractiveFetched(account: context.account, fileReference: .standalone(media: sticker)).start()
return chatMessageAnimationData(postbox: context.account.postbox, resource: sticker.resource, fitzModifier: nil, width: 384, height: 384, synchronousLoad: false)
|> mapToSignal { _ -> Signal<Void, NoError> in
return .complete()
}
} else {
return .complete()
}
}).start())
}
override public func tabBarDisabledAction() {
self.donePressed()
}

View File

@ -89,9 +89,6 @@ public class ContactsController: ViewController {
public var switchToChatsController: (() -> Void)?
private let preloadedSticker = Promise<TelegramMediaFile?>(nil)
private let preloadStickerDisposable = MetaDisposable()
public override func updateNavigationCustomData(_ data: Any?, progress: CGFloat, transition: ContainedViewLayoutTransition) {
if self.isNodeLoaded {
self.contactsNode.contactListNode.updateSelectedChatLocation(data as? ChatLocation, progress: progress, transition: transition)
@ -235,24 +232,16 @@ public class ContactsController: ViewController {
scrollToEndIfExists = true
}
let _ = (strongSelf.preloadedSticker.get()
|> take(1)
|> deliverOnMainQueue).start(next: { [weak self] greetingSticker in
if let strongSelf = self {
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peer.id), purposefulAction: { [weak self] in
if fromSearch {
self?.deactivateSearch(animated: false)
self?.switchToChatsController?()
}
}, scrollToEndIfExists: scrollToEndIfExists, greetingData: greetingSticker.flatMap({ ChatGreetingData(sticker: $0) }), options: [.removeOnMasterDetails], completion: { [weak self] _ in
if let strongSelf = self {
strongSelf.contactsNode.contactListNode.listNode.clearHighlightAnimated(true)
}
}))
strongSelf.prepareRandomGreetingSticker()
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peer.id), purposefulAction: { [weak self] in
if fromSearch {
self?.deactivateSearch(animated: false)
self?.switchToChatsController?()
}
})
}, scrollToEndIfExists: scrollToEndIfExists, options: [.removeOnMasterDetails], completion: { [weak self] _ in
if let strongSelf = self {
strongSelf.contactsNode.contactListNode.listNode.clearHighlightAnimated(true)
}
}))
}
case let .deviceContact(id, _):
let _ = ((strongSelf.context.sharedContext.contactDataManager?.extendedData(stableId: id) ?? .single(nil))
@ -423,14 +412,6 @@ public class ContactsController: ViewController {
self.contactsNode.contactListNode.enableUpdates = false
}
public override func displayNodeDidLoad() {
super.displayNodeDidLoad()
Queue.mainQueue().after(1.0) {
self.prepareRandomGreetingSticker()
}
}
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
super.containerLayoutUpdated(layout, transition: transition)
@ -525,26 +506,4 @@ public class ContactsController: ViewController {
}
})
}
private func prepareRandomGreetingSticker() {
let context = self.context
self.preloadedSticker.set(.single(nil)
|> then(context.engine.stickers.randomGreetingSticker()
|> map { item in
return item?.file
}))
self.preloadStickerDisposable.set((self.preloadedSticker.get()
|> mapToSignal { sticker -> Signal<Void, NoError> in
if let sticker = sticker {
let _ = freeMediaFileInteractiveFetched(account: context.account, fileReference: .standalone(media: sticker)).start()
return chatMessageAnimationData(postbox: context.account.postbox, resource: sticker.resource, fitzModifier: nil, width: 384, height: 384, synchronousLoad: false)
|> mapToSignal { _ -> Signal<Void, NoError> in
return .complete()
}
} else {
return .complete()
}
}).start())
}
}

View File

@ -165,8 +165,11 @@ private final class InnerActionsContainerNode: ASDisplayNode {
gesture.isEnabled = self.panSelectionGestureEnabled
}
func updateLayout(widthClass: ContainerViewLayoutSizeClass, constrainedWidth: CGFloat, transition: ContainedViewLayoutTransition) -> CGSize {
func updateLayout(widthClass: ContainerViewLayoutSizeClass, constrainedWidth: CGFloat, minimalWidth: CGFloat?, transition: ContainedViewLayoutTransition) -> CGSize {
var minActionsWidth: CGFloat = 250.0
if let minimalWidth = minimalWidth, minimalWidth > minActionsWidth {
minActionsWidth = minimalWidth
}
switch widthClass {
case .compact:
@ -517,10 +520,10 @@ final class ContextActionsContainerNode: ASDisplayNode {
}
var contentSize = CGSize()
let actionsSize = self.actionsNode.updateLayout(widthClass: widthClass, constrainedWidth: constrainedWidth, transition: transition)
let actionsSize = self.actionsNode.updateLayout(widthClass: widthClass, constrainedWidth: constrainedWidth, minimalWidth: nil, transition: transition)
if let additionalActionsNode = self.additionalActionsNode, let additionalShadowNode = self.additionalShadowNode {
let additionalActionsSize = additionalActionsNode.updateLayout(widthClass: widthClass, constrainedWidth: actionsSize.width, transition: transition)
let additionalActionsSize = additionalActionsNode.updateLayout(widthClass: widthClass, constrainedWidth: actionsSize.width, minimalWidth: actionsSize.width, transition: transition)
contentSize = additionalActionsSize
let bounds = CGRect(origin: CGPoint(), size: additionalActionsSize)

View File

@ -5,6 +5,7 @@ import AsyncDisplayKit
import SwiftSignalKit
import AppBundle
import SemanticStatusNode
import AnimationUI
private let labelFont = Font.regular(13.0)
@ -30,6 +31,8 @@ final class CallControllerButtonItemNode: HighlightTrackingButtonNode {
}
enum Image {
case cameraOff
case cameraOn
case camera
case mute
case flipCamera
@ -63,6 +66,7 @@ final class CallControllerButtonItemNode: HighlightTrackingButtonNode {
private let effectView: UIVisualEffectView
private let contentBackgroundNode: ASImageNode
private let contentNode: ASImageNode
private var animationNode: AnimationNode?
private let overlayHighlightNode: ASImageNode
private var statusNode: SemanticStatusNode?
let textNode: ImmediateTextNode
@ -179,6 +183,33 @@ final class CallControllerButtonItemNode: HighlightTrackingButtonNode {
let contentBackgroundImage: UIImage? = nil
var animationName: String?
switch content.image {
case .cameraOff:
animationName = "anim_cameraoff"
case .cameraOn:
animationName = "anim_cameraon"
default:
break
}
if let animationName = animationName {
let animationFrame = CGRect(origin: CGPoint(), size: CGSize(width: self.largeButtonSize, height: self.largeButtonSize))
if self.animationNode == nil {
let animationNode = AnimationNode(animation: animationName, colors: nil, scale: 1.0)
self.animationNode = animationNode
self.contentContainer.insertSubnode(animationNode, aboveSubnode: self.contentNode)
}
if let animationNode = self.animationNode {
animationNode.frame = animationFrame
if previousContent == nil {
animationNode.seekToEnd()
} else if previousContent?.image != content.image {
animationNode.play()
}
}
}
let contentImage = generateImage(CGSize(width: self.largeButtonSize, height: self.largeButtonSize), contextGenerator: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
@ -219,6 +250,8 @@ final class CallControllerButtonItemNode: HighlightTrackingButtonNode {
var image: UIImage?
switch content.image {
case .cameraOff, .cameraOn:
image = nil
case .camera:
image = generateTintedImage(image: UIImage(bundleImageName: "Call/CallCameraButton"), color: imageColor)
case .mute:

View File

@ -2384,6 +2384,76 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
self.participantsContext?.lowerHand()
}
public func makeOutgoingVideoView(completion: @escaping (PresentationCallVideoView?) -> Void) {
if self.videoCapturer == nil {
let videoCapturer = OngoingCallVideoCapturer()
self.videoCapturer = videoCapturer
}
self.videoCapturer?.makeOutgoingVideoView(completion: { view in
if let view = view {
let setOnFirstFrameReceived = view.setOnFirstFrameReceived
let setOnOrientationUpdated = view.setOnOrientationUpdated
let setOnIsMirroredUpdated = view.setOnIsMirroredUpdated
completion(PresentationCallVideoView(
holder: view,
view: view.view,
setOnFirstFrameReceived: { f in
setOnFirstFrameReceived(f)
},
getOrientation: { [weak view] in
if let view = view {
let mappedValue: PresentationCallVideoView.Orientation
switch view.getOrientation() {
case .rotation0:
mappedValue = .rotation0
case .rotation90:
mappedValue = .rotation90
case .rotation180:
mappedValue = .rotation180
case .rotation270:
mappedValue = .rotation270
}
return mappedValue
} else {
return .rotation0
}
},
getAspect: { [weak view] in
if let view = view {
return view.getAspect()
} else {
return 0.0
}
},
setOnOrientationUpdated: { f in
setOnOrientationUpdated { value, aspect in
let mappedValue: PresentationCallVideoView.Orientation
switch value {
case .rotation0:
mappedValue = .rotation0
case .rotation90:
mappedValue = .rotation90
case .rotation180:
mappedValue = .rotation180
case .rotation270:
mappedValue = .rotation270
}
f?(mappedValue, aspect)
}
},
setOnIsMirroredUpdated: { f in
setOnIsMirroredUpdated { value in
f?(value)
}
}
))
} else {
completion(nil)
}
})
}
public func requestVideo() {
if self.videoCapturer == nil {
let videoCapturer = OngoingCallVideoCapturer()

View File

@ -1,283 +1,399 @@
import Foundation
import UIKit
import SwiftSignalKit
import AsyncDisplayKit
import Display
import AsyncDisplayKit
import Postbox
import TelegramCore
import SyncCore
import TelegramPresentationData
import SwiftSignalKit
import AccountContext
import UrlEscaping
import TelegramPresentationData
import SolidRoundedButtonNode
import PresentationDataUtils
import UIKitRuntimeUtils
private final class VoiceChatCameraPreviewAlertContentNode: AlertContentNode {
private let strings: PresentationStrings
private let title: String
private let text: String
private let backgroundNode: ASDisplayNode
private let maskNode: ASImageNode
private let titleNode: ASTextNode
private let textNode: ASTextNode
private let actionNodesSeparator: ASDisplayNode
private let actionNodes: [TextAlertContentActionNode]
private let actionVerticalSeparators: [ASDisplayNode]
private let disposable = MetaDisposable()
private var validLayout: CGSize?
private let hapticFeedback = HapticFeedback()
override var dismissOnOutsideTap: Bool {
return self.isUserInteractionEnabled
final class VoiceChatCameraPreviewController: ViewController {
private var controllerNode: VoiceChatCameraPreviewControllerNode {
return self.displayNode as! VoiceChatCameraPreviewControllerNode
}
init(theme: AlertControllerTheme, ptheme: PresentationTheme, strings: PresentationStrings, actions: [TextAlertAction], title: String, text: String) {
self.strings = strings
self.title = title
self.text = text
private let context: AccountContext
private var animatedIn = false
private let cameraNode: GroupVideoNode
private let shareCamera: (ASDisplayNode) -> Void
private let switchCamera: () -> Void
private let shareScreen: () -> Void
private var presentationDataDisposable: Disposable?
init(context: AccountContext, cameraNode: GroupVideoNode, shareCamera: @escaping (ASDisplayNode) -> Void, switchCamera: @escaping () -> Void, shareScreen: @escaping () -> Void) {
self.context = context
self.cameraNode = cameraNode
self.shareCamera = shareCamera
self.switchCamera = switchCamera
self.shareScreen = shareScreen
self.backgroundNode = ASDisplayNode()
self.backgroundNode.clipsToBounds = true
self.backgroundNode.backgroundColor = UIColor(rgb: 0x161619)
super.init(navigationBarPresentationData: nil)
self.maskNode = ASImageNode()
self.maskNode.displaysAsynchronously = false
self.maskNode.contentMode = .scaleToFill
self.maskNode.image = generateImage(CGSize(width: 300.0, height: 400.0), rotatedContext: { size, context in
let bounds = CGRect(origin: CGPoint(), size: size)
context.clear(bounds)
context.setFillColor(UIColor(rgb: 0x000000, alpha: 0.3).cgColor)
context.fill(bounds)
context.setBlendMode(.clear)
context.fillEllipse(in: CGRect(x: 27.0, y: 37.0, width: 246.0, height: 300.0))
self.statusBar.statusBarStyle = .Ignore
self.blocksBackgroundWhenInOverlay = true
self.presentationDataDisposable = (context.sharedContext.presentationData
|> deliverOnMainQueue).start(next: { [weak self] presentationData in
if let strongSelf = self {
strongSelf.controllerNode.updatePresentationData(presentationData)
}
})
self.titleNode = ASTextNode()
self.titleNode.maximumNumberOfLines = 2
self.textNode = ASTextNode()
self.textNode.maximumNumberOfLines = 8
self.actionNodesSeparator = ASDisplayNode()
self.actionNodesSeparator.isLayerBacked = true
self.actionNodes = actions.map { action -> TextAlertContentActionNode in
return TextAlertContentActionNode(theme: theme, action: action)
}
var actionVerticalSeparators: [ASDisplayNode] = []
if actions.count > 1 {
for _ in 0 ..< actions.count - 1 {
let separatorNode = ASDisplayNode()
separatorNode.isLayerBacked = true
actionVerticalSeparators.append(separatorNode)
}
}
self.actionVerticalSeparators = actionVerticalSeparators
super.init()
self.addSubnode(self.backgroundNode)
self.addSubnode(self.titleNode)
self.addSubnode(self.textNode)
self.addSubnode(self.actionNodesSeparator)
for actionNode in self.actionNodes {
self.addSubnode(actionNode)
}
for separatorNode in self.actionVerticalSeparators {
self.addSubnode(separatorNode)
}
self.backgroundNode.addSubnode(self.maskNode)
self.updateTheme(theme)
self.statusBar.statusBarStyle = .Ignore
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
self.disposable.dispose()
self.presentationDataDisposable?.dispose()
}
override func updateTheme(_ theme: AlertControllerTheme) {
self.titleNode.attributedText = NSAttributedString(string: self.title, font: Font.bold(17.0), textColor: theme.primaryColor, paragraphAlignment: .center)
self.textNode.attributedText = NSAttributedString(string: self.text, font: Font.regular(13.0), textColor: theme.primaryColor, paragraphAlignment: .center)
override public func loadDisplayNode() {
self.displayNode = VoiceChatCameraPreviewControllerNode(context: self.context, cameraNode: self.cameraNode)
self.controllerNode.shareCamera = { [weak self] in
if let strongSelf = self {
strongSelf.shareCamera(strongSelf.cameraNode)
strongSelf.dismiss()
}
}
self.controllerNode.switchCamera = { [weak self] in
self?.switchCamera()
}
self.controllerNode.shareScreen = { [weak self] in
self?.shareScreen()
self?.dismiss()
}
self.controllerNode.dismiss = { [weak self] in
self?.presentingViewController?.dismiss(animated: false, completion: nil)
}
self.controllerNode.cancel = { [weak self] in
self?.dismiss()
}
}
override public func loadView() {
super.loadView()
}
override public func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if !self.animatedIn {
self.animatedIn = true
self.controllerNode.animateIn()
}
}
override public func dismiss(completion: (() -> Void)? = nil) {
self.controllerNode.animateOut(completion: completion)
}
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
super.containerLayoutUpdated(layout, transition: transition)
self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationHeight, transition: transition)
}
}
self.actionNodesSeparator.backgroundColor = theme.separatorColor
for actionNode in self.actionNodes {
actionNode.updateTheme(theme)
}
for separatorNode in self.actionVerticalSeparators {
separatorNode.backgroundColor = theme.separatorColor
}
if let size = self.validLayout {
_ = self.updateLayout(size: size, transition: .immediate)
}
}
private class VoiceChatCameraPreviewControllerNode: ViewControllerTracingNode, UIScrollViewDelegate {
private let context: AccountContext
private var presentationData: PresentationData
override func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
var size = size
size.width = min(size.width, 300.0)
let measureSize = CGSize(width: size.width - 16.0 * 2.0, height: CGFloat.greatestFiniteMagnitude)
private let cameraNode: GroupVideoNode
private let dimNode: ASDisplayNode
private let wrappingScrollNode: ASScrollNode
private let contentContainerNode: ASDisplayNode
private let effectNode: ASDisplayNode
private let backgroundNode: ASDisplayNode
private let contentBackgroundNode: ASDisplayNode
private let titleNode: ASTextNode
private let previewContainerNode: ASDisplayNode
private let cameraButton: SolidRoundedButtonNode
private let screenButton: SolidRoundedButtonNode
private let cancelButton: SolidRoundedButtonNode
private let switchCameraButton: HighlightTrackingButtonNode
private let switchCameraEffectView: UIVisualEffectView
private let switchCameraIconNode: ASImageNode
private var containerLayout: (ContainerViewLayout, CGFloat)?
var shareCamera: (() -> Void)?
var switchCamera: (() -> Void)?
var shareScreen: (() -> Void)?
var dismiss: (() -> Void)?
var cancel: (() -> Void)?
init(context: AccountContext, cameraNode: GroupVideoNode) {
self.context = context
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
let hadValidLayout = self.validLayout != nil
self.validLayout = size
self.cameraNode = cameraNode
var origin: CGPoint = CGPoint(x: 0.0, y: 18.0)
let spacing: CGFloat = 3.0
self.wrappingScrollNode = ASScrollNode()
self.wrappingScrollNode.view.alwaysBounceVertical = true
self.wrappingScrollNode.view.delaysContentTouches = false
self.wrappingScrollNode.view.canCancelContentTouches = true
let videoHeight: CGFloat = 400.0
origin.y += videoHeight
self.dimNode = ASDisplayNode()
self.dimNode.backgroundColor = UIColor(white: 0.0, alpha: 0.5)
let titleSize = self.titleNode.measure(measureSize)
transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - titleSize.width) / 2.0), y: origin.y), size: titleSize))
origin.y += titleSize.height + 4.0
self.contentContainerNode = ASDisplayNode()
self.contentContainerNode.isOpaque = false
self.backgroundNode = ASDisplayNode()
self.backgroundNode.clipsToBounds = true
self.backgroundNode.cornerRadius = 16.0
let textSize = self.textNode.measure(measureSize)
transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: origin.y), size: textSize))
origin.y += textSize.height + 6.0
let backgroundColor = UIColor(rgb: 0x1c1c1e)
let textColor: UIColor = .white
let buttonColor: UIColor = UIColor(rgb: 0x2b2b2f)
let buttonTextColor: UIColor = .white
let blurStyle: UIBlurEffect.Style = .dark
let actionButtonHeight: CGFloat = 44.0
var minActionsWidth: CGFloat = 0.0
let maxActionWidth: CGFloat = floor(size.width / CGFloat(self.actionNodes.count))
let actionTitleInsets: CGFloat = 8.0
self.effectNode = ASDisplayNode(viewBlock: {
return UIVisualEffectView(effect: UIBlurEffect(style: blurStyle))
})
var effectiveActionLayout = TextAlertContentActionLayout.horizontal
for actionNode in self.actionNodes {
let actionTitleSize = actionNode.titleNode.updateLayout(CGSize(width: maxActionWidth, height: actionButtonHeight))
if case .horizontal = effectiveActionLayout, actionTitleSize.height > actionButtonHeight * 0.6667 {
effectiveActionLayout = .vertical
self.contentBackgroundNode = ASDisplayNode()
self.contentBackgroundNode.backgroundColor = backgroundColor
let title = self.presentationData.strings.VoiceChat_VideoPreviewTitle
self.titleNode = ASTextNode()
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.bold(17.0), textColor: textColor)
self.cameraButton = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(theme: self.presentationData.theme), font: .bold, height: 52.0, cornerRadius: 11.0, gloss: false)
self.cameraButton.title = self.presentationData.strings.VoiceChat_VideoPreviewShareCamera
self.screenButton = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(backgroundColor: buttonColor, foregroundColor: buttonTextColor), font: .bold, height: 52.0, cornerRadius: 11.0, gloss: false)
self.screenButton.title = self.presentationData.strings.VoiceChat_VideoPreviewShareScreen
self.cancelButton = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(backgroundColor: buttonColor, foregroundColor: buttonTextColor), font: .regular, height: 52.0, cornerRadius: 11.0, gloss: false)
self.cancelButton.title = self.presentationData.strings.Common_Cancel
self.previewContainerNode = ASDisplayNode()
self.previewContainerNode.cornerRadius = 11.0
self.previewContainerNode.backgroundColor = .black
self.switchCameraButton = HighlightTrackingButtonNode()
self.switchCameraEffectView = UIVisualEffectView(effect: UIBlurEffect(style: .dark))
self.switchCameraEffectView.clipsToBounds = true
self.switchCameraEffectView.layer.cornerRadius = 24.0
self.switchCameraEffectView.isUserInteractionEnabled = false
self.switchCameraIconNode = ASImageNode()
self.switchCameraIconNode.displaysAsynchronously = false
self.switchCameraIconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Call/CallSwitchCameraButton"), color: .white)
self.switchCameraIconNode.contentMode = .center
super.init()
self.backgroundColor = nil
self.isOpaque = false
self.dimNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimTapGesture(_:))))
self.addSubnode(self.dimNode)
self.wrappingScrollNode.view.delegate = self
self.addSubnode(self.wrappingScrollNode)
self.wrappingScrollNode.addSubnode(self.backgroundNode)
self.wrappingScrollNode.addSubnode(self.contentContainerNode)
self.backgroundNode.addSubnode(self.effectNode)
self.backgroundNode.addSubnode(self.contentBackgroundNode)
self.contentContainerNode.addSubnode(self.titleNode)
self.contentContainerNode.addSubnode(self.cameraButton)
self.contentContainerNode.addSubnode(self.screenButton)
self.contentContainerNode.addSubnode(self.cancelButton)
self.contentContainerNode.addSubnode(self.previewContainerNode)
self.previewContainerNode.addSubnode(self.cameraNode)
self.previewContainerNode.addSubnode(self.switchCameraButton)
self.switchCameraButton.view.addSubview(self.switchCameraEffectView)
self.switchCameraButton.addSubnode(self.switchCameraIconNode)
self.cameraButton.pressed = { [weak self] in
if let strongSelf = self {
strongSelf.shareCamera?()
}
switch effectiveActionLayout {
case .horizontal:
minActionsWidth += actionTitleSize.width + actionTitleInsets
case .vertical:
minActionsWidth = max(minActionsWidth, actionTitleSize.width + actionTitleInsets)
}
self.screenButton.pressed = { [weak self] in
if let strongSelf = self {
strongSelf.shareScreen?()
}
}
self.cancelButton.pressed = { [weak self] in
if let strongSelf = self {
strongSelf.cancel?()
}
}
let insets = UIEdgeInsets(top: 18.0, left: 18.0, bottom: 18.0, right: 18.0)
var contentWidth = max(titleSize.width, minActionsWidth)
contentWidth = max(contentWidth, 300.0)
var actionsHeight: CGFloat = 0.0
switch effectiveActionLayout {
case .horizontal:
actionsHeight = actionButtonHeight
case .vertical:
actionsHeight = actionButtonHeight * CGFloat(self.actionNodes.count)
}
let resultWidth = contentWidth
let resultSize = CGSize(width: resultWidth, height: titleSize.height + textSize.height + spacing + videoHeight + spacing + actionsHeight + insets.top + insets.bottom)
transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: resultWidth, height: videoHeight)))
transition.updateFrame(node: self.maskNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: resultWidth, height: videoHeight)))
transition.updateFrame(node: self.actionNodesSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel)))
var actionOffset: CGFloat = 0.0
let actionWidth: CGFloat = floor(resultSize.width / CGFloat(self.actionNodes.count))
var separatorIndex = -1
var nodeIndex = 0
for actionNode in self.actionNodes {
if separatorIndex >= 0 {
let separatorNode = self.actionVerticalSeparators[separatorIndex]
switch effectiveActionLayout {
case .horizontal:
transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: actionOffset - UIScreenPixel, y: resultSize.height - actionsHeight), size: CGSize(width: UIScreenPixel, height: actionsHeight - UIScreenPixel)))
case .vertical:
transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel)))
self.switchCameraButton.addTarget(self, action: #selector(self.switchCameraPressed), forControlEvents: .touchUpInside)
self.switchCameraButton.highligthedChanged = { [weak self] highlighted in
if let strongSelf = self {
if highlighted {
let transition: ContainedViewLayoutTransition = .animated(duration: 0.3, curve: .spring)
transition.updateSublayerTransformScale(node: strongSelf.switchCameraButton, scale: 0.9)
} else {
let transition: ContainedViewLayoutTransition = .animated(duration: 0.5, curve: .spring)
transition.updateSublayerTransformScale(node: strongSelf.switchCameraButton, scale: 1.0)
}
}
separatorIndex += 1
let currentActionWidth: CGFloat
switch effectiveActionLayout {
case .horizontal:
if nodeIndex == self.actionNodes.count - 1 {
currentActionWidth = resultSize.width - actionOffset
} else {
currentActionWidth = actionWidth
}
case .vertical:
currentActionWidth = resultSize.width
}
let actionNodeFrame: CGRect
switch effectiveActionLayout {
case .horizontal:
actionNodeFrame = CGRect(origin: CGPoint(x: actionOffset, y: resultSize.height - actionsHeight), size: CGSize(width: currentActionWidth, height: actionButtonHeight))
actionOffset += currentActionWidth
case .vertical:
actionNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset), size: CGSize(width: currentActionWidth, height: actionButtonHeight))
actionOffset += actionButtonHeight
}
transition.updateFrame(node: actionNode, frame: actionNodeFrame)
nodeIndex += 1
}
if !hadValidLayout {
Queue.mainQueue().after(0.1) {
self.maskNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
self.maskNode.layer.animateSpring(from: 1.4 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.4, initialVelocity: 0.0, damping: 104.0)
}
}
return resultSize
}
}
@objc private func switchCameraPressed() {
self.switchCamera?()
}
func updatePresentationData(_ presentationData: PresentationData) {
self.presentationData = presentationData
}
override func didLoad() {
super.didLoad()
if #available(iOSApplicationExtension 11.0, iOS 11.0, *) {
self.wrappingScrollNode.view.contentInsetAdjustmentBehavior = .never
}
}
@objc func dimTapGesture(_ recognizer: UITapGestureRecognizer) {
if case .ended = recognizer.state {
self.cancel?()
}
}
func animateIn() {
self.dimNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
let offset = self.bounds.size.height - self.contentBackgroundNode.frame.minY
let dimPosition = self.dimNode.layer.position
self.dimNode.layer.animatePosition(from: CGPoint(x: dimPosition.x, y: dimPosition.y - offset), to: dimPosition, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring)
let transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring)
let targetBounds = self.bounds
self.bounds = self.bounds.offsetBy(dx: 0.0, dy: -offset)
transition.animateView({
self.bounds = targetBounds
})
}
func animateOut(completion: (() -> Void)? = nil) {
var dimCompleted = false
var offsetCompleted = false
let internalCompletion: () -> Void = { [weak self] in
if let strongSelf = self, dimCompleted && offsetCompleted {
strongSelf.dismiss?()
}
completion?()
}
self.dimNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { _ in
dimCompleted = true
internalCompletion()
})
let offset = self.bounds.size.height - self.contentBackgroundNode.frame.minY
let dimPosition = self.dimNode.layer.position
self.dimNode.layer.animatePosition(from: dimPosition, to: CGPoint(x: dimPosition.x, y: dimPosition.y - offset), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
self.layer.animateBoundsOriginYAdditive(from: 0.0, to: -offset, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { _ in
offsetCompleted = true
internalCompletion()
})
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if self.bounds.contains(point) {
if !self.contentBackgroundNode.bounds.contains(self.convert(point, to: self.contentBackgroundNode)) {
return self.dimNode.view
}
}
return super.hitTest(point, with: event)
}
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
let contentOffset = scrollView.contentOffset
let additionalTopHeight = max(0.0, -contentOffset.y)
if additionalTopHeight >= 30.0 {
self.cancel?()
}
}
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
self.containerLayout = (layout, navigationBarHeight)
var insets = layout.insets(options: [.statusBar, .input])
let cleanInsets = layout.insets(options: [.statusBar])
insets.top = max(10.0, insets.top)
let buttonOffset: CGFloat = 120.0
func voiceChatCameraPreviewController(sharedContext: SharedAccountContext, account: Account, forceTheme: PresentationTheme?, title: String, text: String, apply: @escaping () -> Void) -> AlertController {
var presentationData = sharedContext.currentPresentationData.with { $0 }
if let forceTheme = forceTheme {
presentationData = presentationData.withUpdated(theme: forceTheme)
}
var dismissImpl: ((Bool) -> Void)?
var applyImpl: (() -> Void)?
let actions: [TextAlertAction] = [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {
dismissImpl?(true)
}), TextAlertAction(type: .defaultAction, title: presentationData.strings.VoiceChat_VideoPreviewShare, action: {
applyImpl?()
})]
let contentNode = VoiceChatCameraPreviewAlertContentNode(theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, strings: presentationData.strings, actions: actions, title: title, text: text)
applyImpl = { [weak contentNode] in
guard let contentNode = contentNode else {
return
}
dismissImpl?(true)
let bottomInset: CGFloat = 10.0 + cleanInsets.bottom
let titleHeight: CGFloat = 54.0
var contentHeight = titleHeight + bottomInset + 52.0 + 17.0
let innerContentHeight: CGFloat = layout.size.height - contentHeight - 160.0
contentHeight = titleHeight + bottomInset + 52.0 + 17.0 + innerContentHeight + buttonOffset
}
let controller = AlertController(theme: AlertControllerTheme(presentationData: presentationData), contentNode: contentNode)
let presentationDataDisposable = sharedContext.presentationData.start(next: { [weak controller, weak contentNode] presentationData in
var presentationData = presentationData
if let forceTheme = forceTheme {
presentationData = presentationData.withUpdated(theme: forceTheme)
let width = horizontalContainerFillingSizeForLayout(layout: layout, sideInset: layout.safeInsets.left)
let previewInset: CGFloat = 16.0
let sideInset = floor((layout.size.width - width) / 2.0)
let contentContainerFrame = CGRect(origin: CGPoint(x: sideInset, y: layout.size.height - contentHeight), size: CGSize(width: width, height: contentHeight))
let contentFrame = contentContainerFrame
var backgroundFrame = CGRect(origin: CGPoint(x: contentFrame.minX, y: contentFrame.minY), size: CGSize(width: contentFrame.width, height: contentFrame.height + 2000.0))
if backgroundFrame.minY < contentFrame.minY {
backgroundFrame.origin.y = contentFrame.minY
}
controller?.theme = AlertControllerTheme(presentationData: presentationData)
})
controller.dismissed = {
presentationDataDisposable.dispose()
transition.updateFrame(node: self.backgroundNode, frame: backgroundFrame)
transition.updateFrame(node: self.effectNode, frame: CGRect(origin: CGPoint(), size: backgroundFrame.size))
transition.updateFrame(node: self.contentBackgroundNode, frame: CGRect(origin: CGPoint(), size: backgroundFrame.size))
transition.updateFrame(node: self.wrappingScrollNode, frame: CGRect(origin: CGPoint(), size: layout.size))
transition.updateFrame(node: self.dimNode, frame: CGRect(origin: CGPoint(), size: layout.size))
let titleSize = self.titleNode.measure(CGSize(width: width, height: titleHeight))
let titleFrame = CGRect(origin: CGPoint(x: floor((contentFrame.width - titleSize.width) / 2.0), y: 18.0), size: titleSize)
transition.updateFrame(node: self.titleNode, frame: titleFrame)
let previewSize = CGSize(width: contentFrame.width - previewInset * 2.0, height: contentHeight - 243.0 - bottomInset)
transition.updateFrame(node: self.previewContainerNode, frame: CGRect(origin: CGPoint(x: previewInset, y: 56.0), size: previewSize))
self.cameraNode.frame = CGRect(origin: CGPoint(), size: previewSize)
self.cameraNode.updateLayout(size: previewSize, isLandscape: false, transition: .immediate)
let switchCameraFrame = CGRect(x: previewSize.width - 48.0 - 16.0, y: previewSize.height - 48.0 - 16.0, width: 48.0, height: 48.0)
transition.updateFrame(node: self.switchCameraButton, frame: switchCameraFrame)
transition.updateFrame(view: self.switchCameraEffectView, frame: CGRect(origin: CGPoint(), size: switchCameraFrame.size))
transition.updateFrame(node: self.switchCameraIconNode, frame: CGRect(origin: CGPoint(), size: switchCameraFrame.size))
let buttonInset: CGFloat = 16.0
let cameraButtonHeight = self.cameraButton.updateLayout(width: contentFrame.width - buttonInset * 2.0, transition: transition)
transition.updateFrame(node: self.cameraButton, frame: CGRect(x: buttonInset, y: contentHeight - cameraButtonHeight - insets.bottom - 16.0 - buttonOffset, width: contentFrame.width, height: cameraButtonHeight))
let screenButtonHeight = self.screenButton.updateLayout(width: contentFrame.width - buttonInset * 2.0, transition: transition)
transition.updateFrame(node: self.screenButton, frame: CGRect(x: buttonInset, y: contentHeight - cameraButtonHeight - 8.0 - screenButtonHeight - insets.bottom - 16.0, width: contentFrame.width, height: screenButtonHeight))
let cancelButtonHeight = self.cancelButton.updateLayout(width: contentFrame.width - buttonInset * 2.0, transition: transition)
transition.updateFrame(node: self.cancelButton, frame: CGRect(x: buttonInset, y: contentHeight - cancelButtonHeight - insets.bottom - 16.0, width: contentFrame.width, height: cancelButtonHeight))
transition.updateFrame(node: self.contentContainerNode, frame: contentContainerFrame)
}
dismissImpl = { [weak controller, weak contentNode] animated in
if animated {
controller?.dismissAnimated()
} else {
controller?.dismiss()
}
}
return controller
}

View File

@ -177,8 +177,6 @@ final class GroupVideoNode: ASDisplayNode {
rotatedVideoFrame.size.height = ceil(rotatedVideoFrame.size.height)
var videoSize = rotatedVideoFrame.size
// CGSize(width: 1203, height: 677)
transition.updatePosition(layer: self.videoView.view.layer, position: rotatedVideoFrame.center)
transition.updateBounds(layer: self.videoView.view.layer, bounds: CGRect(origin: CGPoint(), size: videoSize))
@ -194,6 +192,8 @@ private final class MainVideoContainerNode: ASDisplayNode {
private let context: AccountContext
private let call: PresentationGroupCall
private var backdropVideoNode: GroupVideoNode?
private var currentVideoNode: GroupVideoNode?
private var candidateVideoNode: GroupVideoNode?
private let topCornersNode: ASImageNode
@ -291,7 +291,6 @@ private final class MainVideoContainerNode: ASDisplayNode {
strongSelf.candidateVideoNode = nil
let videoNode = GroupVideoNode(videoView: videoView)
if let currentVideoNode = strongSelf.currentVideoNode {
currentVideoNode.removeFromSupernode()
strongSelf.currentVideoNode = nil
@ -429,7 +428,8 @@ public final class VoiceChatController: ViewController {
var isMyPeer: Bool
var ssrc: UInt32?
var effectiveVideoEndpointId: String?
var presence: TelegramUserPresence?
var hasVideo: Bool
var hasScreencast: Bool
var activityTimestamp: Int32
var state: State
var muteState: GroupCallParticipantsContext.Participant.MuteState?
@ -447,7 +447,8 @@ public final class VoiceChatController: ViewController {
isMyPeer: Bool,
ssrc: UInt32?,
effectiveVideoEndpointId: String?,
presence: TelegramUserPresence?,
hasVideo: Bool,
hasScreencast: Bool,
activityTimestamp: Int32,
state: State,
muteState: GroupCallParticipantsContext.Participant.MuteState?,
@ -464,7 +465,8 @@ public final class VoiceChatController: ViewController {
self.isMyPeer = isMyPeer
self.ssrc = ssrc
self.effectiveVideoEndpointId = effectiveVideoEndpointId
self.presence = presence
self.hasVideo = hasVideo
self.hasScreencast = hasScreencast
self.activityTimestamp = activityTimestamp
self.state = state
self.muteState = muteState
@ -497,7 +499,10 @@ public final class VoiceChatController: ViewController {
if lhs.effectiveVideoEndpointId != rhs.effectiveVideoEndpointId {
return false
}
if lhs.presence != rhs.presence {
if lhs.hasVideo != rhs.hasVideo {
return false
}
if lhs.hasScreencast != rhs.hasScreencast {
return false
}
if lhs.activityTimestamp != rhs.activityTimestamp {
@ -622,7 +627,7 @@ public final class VoiceChatController: ViewController {
})
case let .peer(peerEntry):
let peer = peerEntry.peer
var text: VoiceChatParticipantItem.ParticipantText
var expandedText: VoiceChatParticipantItem.ParticipantText?
let icon: VoiceChatParticipantItem.Icon
@ -632,11 +637,12 @@ public final class VoiceChatController: ViewController {
state = .listening
}
let textIcon: VoiceChatParticipantItem.ParticipantText.Icon?
if peerEntry.volume != nil {
textIcon = .volume
} else {
textIcon = nil
var textIcon = VoiceChatParticipantItem.ParticipantText.TextIcon()
if peerEntry.hasVideo {
textIcon.insert(.video)
}
if peerEntry.hasScreencast {
textIcon.insert(.screen)
}
let yourText: String
if (peerEntry.about?.isEmpty ?? true) && peer.smallProfileImage == nil {
@ -671,6 +677,9 @@ public final class VoiceChatController: ViewController {
text = .text(presentationData.strings.VoiceChat_StatusMutedForYou, textIcon, .destructive)
icon = .microphone(true, UIColor(rgb: 0xff3b30))
} else {
if peerEntry.volume != nil {
textIcon.insert(.volume)
}
let volumeValue = peerEntry.volume.flatMap { $0 / 100 }
if let volume = volumeValue, volume != 100 {
text = .text( presentationData.strings.VoiceChat_StatusSpeakingVolume("\(volume)%").0, textIcon, .constructive)
@ -699,7 +708,7 @@ public final class VoiceChatController: ViewController {
let revealOptions: [VoiceChatParticipantItem.RevealOption] = []
return VoiceChatParticipantItem(presentationData: ItemListPresentationData(presentationData), dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, context: context, peer: peer, ssrc: peerEntry.ssrc, presence: peerEntry.presence, text: text, expandedText: expandedText, icon: icon, style: peerEntry.style, enabled: true, transparent: transparent, pinned: peerEntry.pinned, selectable: true, getAudioLevel: { return interaction.getAudioLevel(peer.id) }, getVideo: {
return VoiceChatParticipantItem(presentationData: ItemListPresentationData(presentationData), dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, context: context, peer: peer, ssrc: peerEntry.ssrc, presence: nil, text: text, expandedText: expandedText, icon: icon, style: peerEntry.style, enabled: true, transparent: transparent, pinned: peerEntry.pinned, selectable: true, getAudioLevel: { return interaction.getAudioLevel(peer.id) }, getVideo: {
if let endpointId = peerEntry.effectiveVideoEndpointId {
return interaction.getPeerVideo(endpointId, peerEntry.style != .list)
} else {
@ -789,7 +798,6 @@ public final class VoiceChatController: ViewController {
private var enqueuedTransitions: [ListTransition] = []
private var enqueuedTileTransitions: [ListTransition] = []
private var floatingHeaderOffset: CGFloat?
private var validLayout: (ContainerViewLayout, CGFloat)?
private var didSetContentsReady: Bool = false
@ -871,8 +879,8 @@ public final class VoiceChatController: ViewController {
private var requestedVideoSources = Set<String>()
private var videoNodes: [(String, GroupVideoNode)] = []
private var currentDominantSpeakerWithVideo: (PeerId, String)?
private var currentForcedSpeakerWithVideo: (PeerId, String)?
private var currentDominantSpeakerWithVideo: PeerId?
private var currentForcedSpeakerWithVideo: PeerId?
private var effectiveSpeakerWithVideo: (PeerId, String)?
private var updateAvatarDisposable = MetaDisposable()
@ -978,11 +986,13 @@ public final class VoiceChatController: ViewController {
self.bottomPanelBackgroundNode = ASDisplayNode()
self.bottomPanelBackgroundNode.backgroundColor = panelBackgroundColor
self.bottomPanelBackgroundNode.isUserInteractionEnabled = false
self.bottomCornersNode = ASImageNode()
self.bottomCornersNode.displaysAsynchronously = false
self.bottomCornersNode.displayWithoutProcessing = true
self.bottomCornersNode.image = cornersImage(top: false, bottom: true, dark: false)
self.bottomCornersNode.isUserInteractionEnabled = false
self.audioButton = CallControllerButtonItemNode()
self.cameraButton = CallControllerButtonItemNode()
@ -1072,12 +1082,12 @@ public final class VoiceChatController: ViewController {
let _ = self?.call.updateMuteState(peerId: peerId, isMuted: isMuted)
}, pinPeer: { [weak self] peerId, endpointId in
if let strongSelf = self {
if peerId != strongSelf.currentForcedSpeakerWithVideo?.0, let endpointId = endpointId {
strongSelf.currentForcedSpeakerWithVideo = (peerId, endpointId)
if peerId != strongSelf.currentForcedSpeakerWithVideo {
strongSelf.currentForcedSpeakerWithVideo = peerId
} else {
strongSelf.currentForcedSpeakerWithVideo = nil
}
strongSelf.updatePinnedParticipant()
strongSelf.updatePinnedParticipant(waitForFullSize: false)
var updateLayout = false
if strongSelf.effectiveSpeakerWithVideo != nil && !strongSelf.isExpanded {
@ -1094,7 +1104,7 @@ public final class VoiceChatController: ViewController {
if let (layout, navigationHeight) = strongSelf.validLayout {
strongSelf.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: .animated(duration: 0.3, curve: .easeInOut))
}
strongSelf.updateFloatingHeaderOffset(offset: strongSelf.currentContentOffset ?? 0.0, transition: .animated(duration: 0.3, curve: .easeInOut), completion: {
strongSelf.updateDecorationsLayout(transition: .animated(duration: 0.3, curve: .easeInOut), completion: {
strongSelf.animatingExpansion = false
})
}
@ -1411,7 +1421,7 @@ public final class VoiceChatController: ViewController {
for (endpointId, _) in strongSelf.videoNodes {
if entry.effectiveVideoEndpointId == endpointId {
items.append(.action(ContextMenuActionItem(text: strongSelf.currentForcedSpeakerWithVideo?.0 == peer.id ? strongSelf.presentationData.strings.VoiceChat_UnpinVideo : strongSelf.presentationData.strings.VoiceChat_PinVideo, icon: { theme in
items.append(.action(ContextMenuActionItem(text: strongSelf.currentForcedSpeakerWithVideo == peer.id ? strongSelf.presentationData.strings.VoiceChat_UnpinVideo : strongSelf.presentationData.strings.VoiceChat_PinVideo, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Pin"), color: theme.actionSheet.primaryTextColor)
}, action: { _, f in
guard let strongSelf = self else {
@ -1705,8 +1715,6 @@ public final class VoiceChatController: ViewController {
self.topPanelNode.addSubnode(self.closeButton)
self.topPanelNode.addSubnode(self.topCornersNode)
self.bottomPanelNode.addSubnode(self.bottomCornersNode)
self.bottomPanelNode.addSubnode(self.bottomPanelBackgroundNode)
self.bottomPanelNode.addSubnode(self.audioButton)
if let _ = self.mainVideoContainerNode {
self.bottomPanelNode.addSubnode(self.cameraButton)
@ -1731,6 +1739,8 @@ public final class VoiceChatController: ViewController {
self.contentContainer.addSubnode(self.leftBorderNode)
self.contentContainer.addSubnode(self.rightBorderNode)
self.contentContainer.addSubnode(self.bottomPanelCoverNode)
self.contentContainer.addSubnode(self.bottomCornersNode)
self.contentContainer.addSubnode(self.bottomPanelBackgroundNode)
self.contentContainer.addSubnode(self.bottomPanelNode)
self.contentContainer.addSubnode(self.timerNode)
self.contentContainer.addSubnode(self.scheduleTextNode)
@ -1887,12 +1897,12 @@ public final class VoiceChatController: ViewController {
}
}
if let (peerId, endpointId, _) = maxLevelWithVideo {
/*if strongSelf.currentDominantSpeakerWithVideo?.0 != peerId || strongSelf.currentDominantSpeakerWithVideo?.1 != endpointId {
strongSelf.currentDominantSpeakerWithVideo = (peerId, endpointId)
strongSelf.call.setFullSizeVideo(endpointId: endpointId)
strongSelf.mainVideoContainerNode?.updatePeer(peer: (peerId: peerId, source: endpointId), waitForFullSize: true)
}*/
if let (peerId, _, _) = maxLevelWithVideo {
if strongSelf.currentDominantSpeakerWithVideo != peerId {
strongSelf.currentDominantSpeakerWithVideo = peerId
strongSelf.updatePinnedParticipant(waitForFullSize: true)
}
}
strongSelf.itemInteraction?.updateAudioLevels(levels)
@ -1937,7 +1947,7 @@ public final class VoiceChatController: ViewController {
if let strongSelf = self {
strongSelf.currentContentOffset = offset
if !strongSelf.animatingExpansion && !strongSelf.animatingInsertion && strongSelf.panGestureArguments == nil && !strongSelf.animatingAppearance {
strongSelf.updateFloatingHeaderOffset(offset: offset, transition: transition)
strongSelf.updateDecorationsLayout(transition: transition)
}
}
}
@ -2052,13 +2062,13 @@ public final class VoiceChatController: ViewController {
if let (peerId, endpointId) = strongSelf.effectiveSpeakerWithVideo {
if !validSources.contains(endpointId) {
if peerId == strongSelf.currentForcedSpeakerWithVideo?.0 {
if peerId == strongSelf.currentForcedSpeakerWithVideo {
strongSelf.currentForcedSpeakerWithVideo = nil
}
if peerId == strongSelf.currentDominantSpeakerWithVideo?.0 {
if peerId == strongSelf.currentDominantSpeakerWithVideo {
strongSelf.currentDominantSpeakerWithVideo = nil
}
strongSelf.updatePinnedParticipant()
strongSelf.updatePinnedParticipant(waitForFullSize: false)
}
}
@ -2070,7 +2080,7 @@ public final class VoiceChatController: ViewController {
}))
self.titleNode.tapped = { [weak self] in
if let strongSelf = self {
if let strongSelf = self, !strongSelf.isScheduling {
if strongSelf.callState?.canManageCall ?? false {
strongSelf.openTitleEditing()
} else if !strongSelf.titleNode.recordingIconNode.isHidden {
@ -2152,7 +2162,7 @@ public final class VoiceChatController: ViewController {
if let (layout, navigationHeight) = strongSelf.validLayout {
strongSelf.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: .animated(duration: 0.3, curve: .easeInOut))
strongSelf.updateFloatingHeaderOffset(offset: strongSelf.currentContentOffset ?? 0.0, transition: .animated(duration: 0.3, curve: .easeInOut))
strongSelf.updateDecorationsLayout(transition: .animated(duration: 0.3, curve: .easeInOut))
}
}
if let (peerId, _) = minimalVisiblePeerid {
@ -2200,7 +2210,7 @@ public final class VoiceChatController: ViewController {
if let (layout, navigationHeight) = strongSelf.validLayout {
strongSelf.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: .animated(duration: 0.3, curve: .easeInOut))
strongSelf.updateFloatingHeaderOffset(offset: strongSelf.currentContentOffset ?? 0.0, transition: .animated(duration: 0.3, curve: .easeInOut))
strongSelf.updateDecorationsLayout(transition: .animated(duration: 0.3, curve: .easeInOut))
}
}
if let (peerId, _) = minimalVisiblePeerid {
@ -2224,7 +2234,7 @@ public final class VoiceChatController: ViewController {
if let (layout, navigationHeight) = strongSelf.validLayout {
strongSelf.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: .animated(duration: 0.3, curve: .easeInOut))
strongSelf.updateFloatingHeaderOffset(offset: strongSelf.currentContentOffset ?? 0.0, transition: .animated(duration: 0.3, curve: .easeInOut))
strongSelf.updateDecorationsLayout(transition: .animated(duration: 0.3, curve: .easeInOut))
}
}
}
@ -3236,16 +3246,22 @@ public final class VoiceChatController: ViewController {
self.call.disableVideo()
self.call.disableScreencast()
} else {
#if DEBUG
//self.call.requestScreencast()
self.call.requestVideo()
return;
#endif
let controller = voiceChatCameraPreviewController(sharedContext: self.context.sharedContext, account: self.context.account, forceTheme: self.darkTheme, title: self.presentationData.strings.VoiceChat_VideoPreviewTitle, text: self.presentationData.strings.VoiceChat_VideoPreviewDescription, apply: { [weak self] in
self?.call.requestVideo()
})
self.controller?.present(controller, in: .window(.root))
self.call.makeOutgoingVideoView { [weak self] view in
guard let strongSelf = self, let view = view else {
return
}
let cameraNode = GroupVideoNode(videoView: view)
let controller = VoiceChatCameraPreviewController(context: strongSelf.context, cameraNode: cameraNode, shareCamera: { [weak self] videoNode in
if let strongSelf = self {
strongSelf.call.requestVideo()
}
}, switchCamera: { [weak self] in
self?.call.switchVideoCamera()
}, shareScreen: { [weak self] in
self?.call.requestScreencast()
})
strongSelf.controller?.present(controller, in: .window(.root))
}
}
}
@ -3271,20 +3287,11 @@ public final class VoiceChatController: ViewController {
}
private var bringVideoToBackOnCompletion = false
private func updateFloatingHeaderOffset(offset: CGFloat, transition: ContainedViewLayoutTransition, completion: (() -> Void)? = nil) {
private func updateDecorationsLayout(transition: ContainedViewLayoutTransition, completion: (() -> Void)? = nil) {
guard let (layout, _) = self.validLayout else {
return
}
let layoutTopInset: CGFloat = max(layout.statusBarHeight ?? 0.0, layout.safeInsets.top)
let listTopInset = layoutTopInset + topPanelHeight
let bottomPanelHeight = self.effectiveBottomAreaHeight + layout.intrinsicInsets.bottom
var size = layout.size
if case .regular = layout.metrics.widthClass {
size.width = floor(min(size.width, size.height) * 0.5)
}
var isLandscape = false
var effectiveDisplayMode = self.displayMode
if case .compact = layout.metrics.widthClass, layout.size.width > layout.size.height {
@ -3295,7 +3302,24 @@ public final class VoiceChatController: ViewController {
}
}
let listSize = CGSize(width: size.width, height: layout.size.height - listTopInset - bottomPanelHeight)
let layoutTopInset: CGFloat = max(layout.statusBarHeight ?? 0.0, layout.safeInsets.top)
let listTopInset = isLandscape ? topPanelHeight : layoutTopInset + topPanelHeight
let bottomPanelHeight = isLandscape ? layout.intrinsicInsets.bottom : self.effectiveBottomAreaHeight + layout.intrinsicInsets.bottom
var size = layout.size
if case .regular = layout.metrics.widthClass {
size.width = floor(min(size.width, size.height) * 0.5)
}
let contentWidth: CGFloat
if case .regular = layout.metrics.widthClass {
size.width = floor(min(size.width, size.height) * 0.5)
contentWidth = size.width
} else {
contentWidth = isLandscape ? min(530.0, size.width - 210.0) : size.width
}
let listSize = CGSize(width: contentWidth, height: layout.size.height - listTopInset - bottomPanelHeight)
let topInset: CGFloat
if let (panInitialTopInset, panOffset) = self.panGestureArguments {
if self.isExpanded {
@ -3319,9 +3343,8 @@ public final class VoiceChatController: ViewController {
}
}
let offset = (bottomEdge.isZero ? 0.0 : offset) + topInset
self.floatingHeaderOffset = offset
let currentContentOffset = self.currentContentOffset ?? 0.0
let offset = (bottomEdge.isZero ? 0.0 : currentContentOffset) + topInset
if bottomEdge.isZero {
bottomEdge = self.listNode.frame.minY + 46.0 + 56.0
@ -3331,7 +3354,7 @@ public final class VoiceChatController: ViewController {
let panelOffset = max(layoutTopInset, rawPanelOffset)
let topPanelFrame: CGRect
if isLandscape {
topPanelFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: 0.0))
topPanelFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: topPanelHeight))
} else {
topPanelFrame = CGRect(origin: CGPoint(x: 0.0, y: panelOffset), size: CGSize(width: size.width, height: topPanelHeight))
}
@ -3423,8 +3446,8 @@ public final class VoiceChatController: ViewController {
let leftBorderFrame: CGRect
let rightBorderFrame: CGRect
if isLandscape {
leftBorderFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.safeInsets.left, height: layout.size.height))
rightBorderFrame = CGRect(origin: CGPoint(x: size.width - layout.safeInsets.right, y: 0.0), size: CGSize(width: layout.safeInsets.right, height: layout.size.height))
leftBorderFrame = CGRect(origin: CGPoint(x: 0.0, y: topPanelFrame.maxY - 16.0), size: CGSize(width: (size.width - contentWidth) / 2.0 + sideInset, height: layout.size.height))
rightBorderFrame = CGRect(origin: CGPoint(x: size.width - (size.width - contentWidth) / 2.0 - sideInset, y: topPanelFrame.maxY - 16.0), size: CGSize(width: layout.safeInsets.right + (size.width - contentWidth) / 2.0 + sideInset, height: layout.size.height))
} else {
leftBorderFrame = CGRect(origin: CGPoint(x: 0.0, y: topPanelFrame.maxY - 16.0), size: CGSize(width: sideInset, height: layout.size.height))
rightBorderFrame = CGRect(origin: CGPoint(x: size.width - sideInset, y: topPanelFrame.maxY - 16.0), size: CGSize(width: sideInset, height: layout.size.height))
@ -3458,10 +3481,10 @@ public final class VoiceChatController: ViewController {
self.topPanelBackgroundNode.frame = CGRect(x: 0.0, y: topPanelHeight - 24.0, width: size.width, height: min(topPanelFrame.height, 24.0))
let listMaxY = listTopInset + listSize.height
let bottomOffset: CGFloat = min(0.0, bottomEdge - listMaxY)
let bottomOffset: CGFloat = min(0.0, bottomEdge - listMaxY) + layout.size.height - bottomPanelHeight
let bottomDelta = self.effectiveBottomAreaHeight - bottomAreaHeight
let bottomCornersFrame = CGRect(origin: CGPoint(x: sideInset, y: -50.0 + bottomOffset + bottomDelta), size: CGSize(width: size.width - sideInset * 2.0, height: 50.0))
let bottomCornersFrame = CGRect(origin: CGPoint(x: sideInset + floorToScreenPixels((size.width - contentWidth) / 2.0), y: -50.0 + bottomOffset + bottomDelta), size: CGSize(width: contentWidth - sideInset * 2.0, height: 50.0))
let previousBottomCornersFrame = self.bottomCornersNode.frame
if !bottomCornersFrame.equalTo(previousBottomCornersFrame) {
self.bottomCornersNode.frame = bottomCornersFrame
@ -3500,8 +3523,10 @@ public final class VoiceChatController: ViewController {
topEdgeFrame = CGRect(x: 0.0, y: 0.0, width: size.width, height: topPanelHeight)
}
var isLandscape = false
var effectiveDisplayMode = self.displayMode
if case .compact = layout.metrics.widthClass, layout.size.width > layout.size.height {
isLandscape = true
if case .fullscreen = effectiveDisplayMode {
} else {
effectiveDisplayMode = .fullscreen(controlsHidden: false)
@ -3510,7 +3535,11 @@ public final class VoiceChatController: ViewController {
let backgroundColor: UIColor
if case .fullscreen = effectiveDisplayMode {
backgroundColor = fullscreenBackgroundColor
if isLandscape {
backgroundColor = isFullscreen ? panelBackgroundColor : secondaryPanelBackgroundColor
} else {
backgroundColor = fullscreenBackgroundColor
}
} else if self.isScheduling || self.callState?.scheduleTimestamp != nil {
backgroundColor = panelBackgroundColor
} else {
@ -3556,7 +3585,7 @@ public final class VoiceChatController: ViewController {
}
private func updateTitle(slide: Bool = false, transition: ContainedViewLayoutTransition) {
guard let (layout, _) = self.validLayout else {
guard let _ = self.validLayout else {
return
}
var title = self.currentTitle
@ -3583,12 +3612,7 @@ public final class VoiceChatController: ViewController {
}
}
var size = layout.size
if case .regular = layout.metrics.widthClass {
size.width = floor(min(size.width, size.height) * 0.5)
}
self.titleNode.update(size: CGSize(width: size.width, height: 44.0), title: title, subtitle: subtitle, slide: slide, transition: transition)
self.titleNode.update(size: CGSize(width: self.titleNode.bounds.width, height: 44.0), title: title, subtitle: subtitle, slide: slide, transition: transition)
}
private func updateButtons(animated: Bool) {
@ -3688,7 +3712,7 @@ public final class VoiceChatController: ViewController {
}
let transition: ContainedViewLayoutTransition = animated ? .animated(duration: 0.3, curve: .linear) : .immediate
self.cameraButton.update(size: videoButtonSize, content: CallControllerButtonItemNode.Content(appearance: normalButtonAppearance, image: .camera), text: self.presentationData.strings.VoiceChat_Video, transition: transition)
self.cameraButton.update(size: videoButtonSize, content: CallControllerButtonItemNode.Content(appearance: normalButtonAppearance, image: .cameraOff), text: self.presentationData.strings.VoiceChat_Video, transition: transition)
self.switchCameraButton.update(size: videoButtonSize, content: CallControllerButtonItemNode.Content(appearance: normalButtonAppearance, image: .flipCamera), text: "", transition: transition)
@ -3709,8 +3733,12 @@ public final class VoiceChatController: ViewController {
self.validLayout = (layout, navigationHeight)
var size = layout.size
let contentWidth: CGFloat
if case .regular = layout.metrics.widthClass {
size.width = floor(min(size.width, size.height) * 0.5)
contentWidth = size.width
} else {
contentWidth = isLandscape ? min(530.0, size.width - 210.0) : size.width
}
let isScheduled = self.isScheduling || self.callState?.scheduleTimestamp != nil
@ -3728,7 +3756,7 @@ public final class VoiceChatController: ViewController {
if !self.isFullscreen {
self.isExpanded = true
self.updateIsFullscreen(true)
self.tileListNode.isHidden = false
// self.tileListNode.isHidden = false
}
if case .fullscreen = effectiveDisplayMode {
} else {
@ -3752,34 +3780,37 @@ public final class VoiceChatController: ViewController {
}
}
}
transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - contentWidth) / 2.0), y: 10.0), size: CGSize(width: contentWidth, height: 44.0)))
self.updateTitle(transition: transition)
transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 10.0), size: CGSize(width: size.width, height: 44.0)))
transition.updateFrame(node: self.optionsButton, frame: CGRect(origin: CGPoint(x: 20.0, y: 18.0), size: CGSize(width: 28.0, height: 28.0)))
transition.updateFrame(node: self.closeButton, frame: CGRect(origin: CGPoint(x: size.width - 20.0 - 28.0, y: 18.0), size: CGSize(width: 28.0, height: 28.0)))
transition.updateFrame(node: self.optionsButton, frame: CGRect(origin: CGPoint(x: 20.0 + floorToScreenPixels((size.width - contentWidth) / 2.0), y: 18.0), size: CGSize(width: 28.0, height: 28.0)))
transition.updateFrame(node: self.closeButton, frame: CGRect(origin: CGPoint(x: size.width - floorToScreenPixels((size.width - contentWidth) / 2.0) - 20.0 - 28.0, y: 18.0), size: CGSize(width: 28.0, height: 28.0)))
transition.updateFrame(node: self.dimNode, frame: CGRect(origin: CGPoint(), size: layout.size))
transition.updateFrame(node: self.contentContainer, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - size.width) / 2.0), y: 0.0), size: size))
let layoutTopInset: CGFloat = max(layout.statusBarHeight ?? 0.0, layout.safeInsets.top)
let sideInset: CGFloat = 16.0
var insets = UIEdgeInsets()
insets.left = layout.safeInsets.left + sideInset
insets.right = layout.safeInsets.right + sideInset
insets.left = sideInset + (isLandscape ? 0.0 : layout.safeInsets.left)
insets.right = sideInset + (isLandscape ? 0.0 : layout.safeInsets.right)
let topEdgeOffset: CGFloat
if let statusBarHeight = layout.statusBarHeight {
topEdgeOffset = statusBarHeight
} else {
topEdgeOffset = 44.0
}
if isLandscape {
transition.updateFrame(node: self.topPanelEdgeNode, frame: CGRect(x: 0.0, y: 0.0, width: size.width, height: 0.0))
transition.updateFrame(node: self.topPanelEdgeNode, frame: CGRect(x: 0.0, y: -topEdgeOffset, width: size.width, height: topPanelHeight + topEdgeOffset))
} else if let _ = self.panGestureArguments {
} else {
let topEdgeFrame: CGRect
if self.isFullscreen {
let offset: CGFloat
if let statusBarHeight = layout.statusBarHeight {
offset = statusBarHeight
} else {
offset = 44.0
}
topEdgeFrame = CGRect(x: 0.0, y: -offset, width: size.width, height: topPanelHeight + offset)
topEdgeFrame = CGRect(x: 0.0, y: -topEdgeOffset, width: size.width, height: topPanelHeight + topEdgeOffset)
} else {
topEdgeFrame = CGRect(x: 0.0, y: 0.0, width: size.width, height: topPanelHeight)
}
@ -3790,15 +3821,15 @@ public final class VoiceChatController: ViewController {
var listTopInset = layoutTopInset + topPanelHeight
var topCornersY = topPanelHeight
if isLandscape {
listTopInset = 0.0
topCornersY = -50.0
listTopInset = topPanelHeight
// topCornersY = -50.0
} else if self.mainVideoContainerNode != nil && self.isFullscreen {
let videoContainerHeight = min(mainVideoHeight, layout.size.width)
listTopInset += videoContainerHeight
topCornersY += videoContainerHeight
}
let listSize = CGSize(width: size.width, height: layout.size.height - listTopInset - bottomPanelHeight)
let listSize = CGSize(width: contentWidth, height: layout.size.height - listTopInset - (isLandscape ? layout.intrinsicInsets.bottom : bottomPanelHeight))
let topInset: CGFloat
if let (panInitialTopInset, panOffset) = self.panGestureArguments {
if self.isExpanded {
@ -3812,7 +3843,7 @@ public final class VoiceChatController: ViewController {
topInset = listSize.height - 46.0 - floor(56.0 * 3.5)
}
transition.updateFrame(node: self.listNode, frame: CGRect(origin: CGPoint(x: 0.0, y: listTopInset + topInset), size: listSize))
transition.updateFrame(node: self.listNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - contentWidth) / 2.0), y: listTopInset + topInset), size: listSize))
let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition)
self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: nil, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: listSize, insets: insets, duration: duration, curve: curve), stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
@ -3845,28 +3876,29 @@ public final class VoiceChatController: ViewController {
self.tileListNode.transform = tileListTransform
self.tileListNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: nil, updateSizeAndInsets: tileListUpdateSizeAndInsets, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
transition.updateFrame(node: self.topCornersNode, frame: CGRect(origin: CGPoint(x: sideInset, y: topCornersY), size: CGSize(width: size.width - sideInset * 2.0, height: 50.0)))
transition.updateFrame(node: self.topCornersNode, frame: CGRect(origin: CGPoint(x: sideInset + floorToScreenPixels((size.width - contentWidth) / 2.0), y: topCornersY), size: CGSize(width: contentWidth - sideInset * 2.0, height: 50.0)))
var bottomPanelFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - bottomPanelHeight), size: CGSize(width: size.width, height: bottomPanelHeight))
let bottomPanelCoverHeight = bottomAreaHeight + layout.intrinsicInsets.bottom
let bottomPanelCoverFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - bottomPanelCoverHeight), size: CGSize(width: size.width, height: bottomPanelCoverHeight))
if isLandscape {
transition.updateAlpha(node: self.closeButton, alpha: 0.0)
transition.updateAlpha(node: self.optionsButton, alpha: 0.0)
transition.updateAlpha(node: self.titleNode, alpha: 0.0)
// transition.updateAlpha(node: self.closeButton, alpha: 0.0)
// transition.updateAlpha(node: self.optionsButton, alpha: 0.0)
// transition.updateAlpha(node: self.titleNode, alpha: 0.0)
bottomPanelFrame = CGRect(origin: CGPoint(x: layout.size.width - fullscreenBottomAreaHeight - layout.safeInsets.right, y: 0.0), size: CGSize(width: fullscreenBottomAreaHeight + layout.safeInsets.right, height: layout.size.height))
} else {
transition.updateAlpha(node: self.closeButton, alpha: 1.0)
transition.updateAlpha(node: self.optionsButton, alpha: self.optionsButton.isUserInteractionEnabled ? 1.0 : 0.0)
transition.updateAlpha(node: self.titleNode, alpha: 1.0)
// transition.updateAlpha(node: self.closeButton, alpha: 1.0)
// transition.updateAlpha(node: self.optionsButton, alpha: self.optionsButton.isUserInteractionEnabled ? 1.0 : 0.0)
// transition.updateAlpha(node: self.titleNode, alpha: 1.0)
}
transition.updateAlpha(node: self.optionsButton, alpha: self.optionsButton.isUserInteractionEnabled ? 1.0 : 0.0)
transition.updateFrame(node: self.bottomPanelCoverNode, frame: bottomPanelCoverFrame)
transition.updateFrame(node: self.bottomPanelNode, frame: bottomPanelFrame)
if let pickerView = self.pickerView {
transition.updateFrame(view: pickerView, frame: CGRect(x: 0.0, y: layout.size.height - bottomPanelHeight - 216.0, width: size.width, height: 216.0))
}
let timerFrame = CGRect(x: 0.0, y: layout.size.height - bottomPanelHeight - 216.0, width: size.width, height: 216.0)
transition.updateFrame(node: self.timerNode, frame: timerFrame)
self.timerNode.update(size: timerFrame.size, scheduleTime: self.callState?.scheduleTimestamp, transition: .immediate)
@ -3905,16 +3937,26 @@ public final class VoiceChatController: ViewController {
forthButtonFrame = rightButtonFrame
case let .fullscreen(controlsHidden):
smallButtons = true
let sideInset: CGFloat = 26.0
if isLandscape {
let spacing = floor((layout.size.height - sideInset * 2.0 - sideButtonSize.height * 4.0) / 3.0)
let sideInset: CGFloat
let buttonsCount: Int
if self.mainVideoContainerNode == nil {
sideInset = 42.0
buttonsCount = 3
} else {
sideInset = 26.0
buttonsCount = 4
}
let spacing = floor((layout.size.height - sideInset * 2.0 - sideButtonSize.height * CGFloat(buttonsCount)) / (CGFloat(buttonsCount - 1)))
let x = controlsHidden ? fullscreenBottomAreaHeight + layout.safeInsets.right + 30.0: floor((fullscreenBottomAreaHeight - sideButtonSize.width) / 2.0)
forthButtonFrame = CGRect(origin: CGPoint(x: x, y: sideInset), size: sideButtonSize)
let thirdButtonPreFrame = CGRect(origin: CGPoint(x: x, y: sideInset + sideButtonSize.height + spacing), size: sideButtonSize)
thirdButtonFrame = CGRect(origin: CGPoint(x: floor(thirdButtonPreFrame.midX - centralButtonSize.width / 2.0), y: floor(thirdButtonPreFrame.midY - centralButtonSize.height / 2.0)), size: centralButtonSize)
secondButtonFrame = CGRect(origin: CGPoint(x: x, y: layout.size.height - sideInset - sideButtonSize.height - spacing - sideButtonSize.height), size: sideButtonSize)
secondButtonFrame = CGRect(origin: CGPoint(x: x, y: thirdButtonPreFrame.maxY + spacing), size: sideButtonSize)
firstButtonFrame = CGRect(origin: CGPoint(x: x, y: layout.size.height - sideInset - sideButtonSize.height), size: sideButtonSize)
} else {
let sideInset: CGFloat = 26.0
let spacing = floor((layout.size.width - sideInset * 2.0 - sideButtonSize.width * 4.0) / 3.0)
let y = controlsHidden ? self.effectiveBottomAreaHeight + layout.intrinsicInsets.bottom + 30.0: floor((self.effectiveBottomAreaHeight - sideButtonSize.height) / 2.0)
firstButtonFrame = CGRect(origin: CGPoint(x: sideInset, y: y), size: sideButtonSize)
@ -4047,7 +4089,7 @@ public final class VoiceChatController: ViewController {
guard let (layout, navigationHeight) = self.validLayout else {
return
}
self.updateFloatingHeaderOffset(offset: 0.0, transition: .immediate)
self.updateDecorationsLayout(transition: .immediate)
self.animatingAppearance = true
@ -4172,9 +4214,7 @@ public final class VoiceChatController: ViewController {
itemsCount -= 1
}
itemsHeight += CGFloat(itemsCount) * 56.0
let layoutTopInset: CGFloat = max(layout.statusBarHeight ?? 0.0, layout.safeInsets.top)
let sideInset: CGFloat = 16.0
var insets = UIEdgeInsets()
insets.left = layout.safeInsets.left + sideInset
@ -4185,7 +4225,8 @@ public final class VoiceChatController: ViewController {
size.width = floor(min(size.width, size.height) * 0.5)
}
let bottomPanelHeight = self.effectiveBottomAreaHeight + layout.intrinsicInsets.bottom
let bottomPanelHeight = self.isLandscape ? layout.intrinsicInsets.bottom : self.effectiveBottomAreaHeight + layout.intrinsicInsets.bottom
let layoutTopInset: CGFloat = max(layout.statusBarHeight ?? 0.0, layout.safeInsets.top)
let listTopInset = layoutTopInset + topPanelHeight
let listSize = CGSize(width: size.width, height: layout.size.height - listTopInset - bottomPanelHeight)
@ -4207,9 +4248,9 @@ public final class VoiceChatController: ViewController {
return
}
if isFirstTime {
strongSelf.updateFloatingHeaderOffset(offset: strongSelf.currentContentOffset ?? 0.0, transition: .immediate)
strongSelf.updateDecorationsLayout(transition: .immediate)
} else if strongSelf.animatingInsertion {
strongSelf.updateFloatingHeaderOffset(offset: strongSelf.currentContentOffset ?? 0.0, transition: .animated(duration: 0.2, curve: .easeInOut))
strongSelf.updateDecorationsLayout(transition: .animated(duration: 0.2, curve: .easeInOut))
}
strongSelf.animatingInsertion = false
if !strongSelf.didSetContentsReady {
@ -4275,12 +4316,7 @@ public final class VoiceChatController: ViewController {
entries.append(.invite(self.presentationData.theme, self.presentationData.strings, inviteIsLink ? self.presentationData.strings.VoiceChat_Share : self.presentationData.strings.VoiceChat_InviteMember, inviteIsLink))
}
if let _ = self.effectiveSpeakerWithVideo {
index += 1
}
var pinnedEntry: ListEntry?
for member in callMembers.0 {
if processedPeerIds.contains(member.peer.id) {
continue
@ -4339,7 +4375,8 @@ public final class VoiceChatController: ViewController {
isMyPeer: self.callState?.myPeerId == member.peer.id,
ssrc: member.ssrc,
effectiveVideoEndpointId: member.presentationEndpointId ?? member.videoEndpointId,
presence: nil,
hasVideo: member.videoEndpointId != nil,
hasScreencast: member.presentationEndpointId != nil,
activityTimestamp: Int32.max - 1 - index,
state: memberState,
muteState: memberMuteState,
@ -4359,7 +4396,8 @@ public final class VoiceChatController: ViewController {
isMyPeer: self.callState?.myPeerId == member.peer.id,
ssrc: member.ssrc,
effectiveVideoEndpointId: member.presentationEndpointId ?? member.videoEndpointId,
presence: nil,
hasVideo: member.videoEndpointId != nil,
hasScreencast: member.presentationEndpointId != nil,
activityTimestamp: Int32.max - 1 - index,
state: memberState,
muteState: memberMuteState,
@ -4377,11 +4415,12 @@ public final class VoiceChatController: ViewController {
if memberPeer.id == self.effectiveSpeakerWithVideo?.0 {
pinnedEntry = .peer(PeerEntry(
peer: memberPeer,
about: member.about,
about: nil,
isMyPeer: self.callState?.myPeerId == member.peer.id,
ssrc: member.ssrc,
effectiveVideoEndpointId: member.presentationEndpointId ?? member.videoEndpointId,
presence: nil,
hasVideo: false,
hasScreencast: false,
activityTimestamp: Int32.max - 1 - index,
state: memberState,
muteState: memberMuteState,
@ -4408,7 +4447,8 @@ public final class VoiceChatController: ViewController {
isMyPeer: false,
ssrc: nil,
effectiveVideoEndpointId: nil,
presence: nil,
hasVideo: false,
hasScreencast: false,
activityTimestamp: Int32.max - 1 - index,
state: .invited,
muteState: nil,
@ -4467,20 +4507,20 @@ public final class VoiceChatController: ViewController {
self.enqueueTileTransition(tileTransition)
}
private func updatePinnedParticipant() {
private func updatePinnedParticipant(waitForFullSize: Bool) {
let effectivePinnedParticipant = self.currentForcedSpeakerWithVideo ?? self.currentDominantSpeakerWithVideo
guard effectivePinnedParticipant?.0 != self.effectiveSpeakerWithVideo?.0 || effectivePinnedParticipant?.1 != self.effectiveSpeakerWithVideo?.1 else {
guard effectivePinnedParticipant != self.effectiveSpeakerWithVideo?.0 else {
return
}
if let (peerId, _) = effectivePinnedParticipant {
if let peerId = effectivePinnedParticipant {
for entry in self.currentEntries {
switch entry {
case let .peer(peer):
if peer.peer.id == peerId, let endpointId = peer.effectiveVideoEndpointId {
self.effectiveSpeakerWithVideo = (peerId, endpointId)
self.call.setFullSizeVideo(endpointId: endpointId)
self.mainVideoContainerNode?.updatePeer(peer: (peerId: peerId, endpointId: endpointId), waitForFullSize: false)
self.mainVideoContainerNode?.updatePeer(peer: (peerId: peerId, endpointId: endpointId), waitForFullSize: waitForFullSize)
}
default:
break
@ -4577,7 +4617,7 @@ public final class VoiceChatController: ViewController {
if let (layout, navigationHeight) = self.validLayout {
self.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: .immediate)
self.updateFloatingHeaderOffset(offset: self.currentContentOffset ?? 0.0, transition: .immediate)
self.updateDecorationsLayout(transition: .immediate)
}
if !self.isExpanded {
@ -4625,7 +4665,7 @@ public final class VoiceChatController: ViewController {
if let (layout, navigationHeight) = self.validLayout {
self.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: .animated(duration: 0.3, curve: .easeInOut))
}
self.updateFloatingHeaderOffset(offset: self.currentContentOffset ?? 0.0, transition: .animated(duration: 0.3, curve: .easeInOut), completion: {
self.updateDecorationsLayout(transition: .animated(duration: 0.3, curve: .easeInOut), completion: {
self.animatingExpansion = false
})
} else {
@ -4635,7 +4675,7 @@ public final class VoiceChatController: ViewController {
if let (layout, navigationHeight) = self.validLayout {
self.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: .animated(duration: 0.3, curve: .easeInOut))
}
self.updateFloatingHeaderOffset(offset: self.currentContentOffset ?? 0.0, transition: .animated(duration: 0.3, curve: .easeInOut), completion: {
self.updateDecorationsLayout(transition: .animated(duration: 0.3, curve: .easeInOut), completion: {
self.animatingExpansion = false
})
}
@ -4663,7 +4703,7 @@ public final class VoiceChatController: ViewController {
if let (layout, navigationHeight) = self.validLayout {
self.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: .animated(duration: 0.3, curve: .easeInOut))
}
self.updateFloatingHeaderOffset(offset: self.currentContentOffset ?? 0.0, transition: .animated(duration: 0.3, curve: .easeInOut), completion: {
self.updateDecorationsLayout(transition: .animated(duration: 0.3, curve: .easeInOut), completion: {
self.animatingExpansion = false
})
} else if !isScheduling {
@ -4674,7 +4714,7 @@ public final class VoiceChatController: ViewController {
if let (layout, navigationHeight) = self.validLayout {
self.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: .animated(duration: 0.3, curve: .easeInOut))
}
self.updateFloatingHeaderOffset(offset: self.currentContentOffset ?? 0.0, transition: .animated(duration: 0.3, curve: .easeInOut), completion: {
self.updateDecorationsLayout(transition: .animated(duration: 0.3, curve: .easeInOut), completion: {
self.animatingExpansion = false
})
}
@ -4698,7 +4738,7 @@ public final class VoiceChatController: ViewController {
if let (layout, navigationHeight) = self.validLayout {
self.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: .animated(duration: 0.3, curve: .easeInOut))
}
self.updateFloatingHeaderOffset(offset: self.currentContentOffset ?? 0.0, transition: .animated(duration: 0.3, curve: .easeInOut), completion: {
self.updateDecorationsLayout(transition: .animated(duration: 0.3, curve: .easeInOut), completion: {
self.animatingExpansion = false
})
default:

View File

@ -25,10 +25,21 @@ final class VoiceChatParticipantItem: ListViewItem {
case tile(isLandscape: Bool)
}
enum ParticipantText {
public enum Icon {
case volume
case video
enum ParticipantText: Equatable {
public struct TextIcon: OptionSet {
public var rawValue: Int32
public init(rawValue: Int32) {
self.rawValue = rawValue
}
public init() {
self.rawValue = 0
}
public static let volume = TextIcon(rawValue: 1 << 0)
public static let video = TextIcon(rawValue: 1 << 1)
public static let screen = TextIcon(rawValue: 1 << 2)
}
public enum TextColor {
@ -39,7 +50,7 @@ final class VoiceChatParticipantItem: ListViewItem {
}
case presence
case text(String, Icon?, TextColor)
case text(String, TextIcon, TextColor)
case none
}
@ -179,7 +190,7 @@ private let borderImage = generateImage(CGSize(width: tileSize.width, height: ti
context.clear(bounds)
context.setLineWidth(borderLineWidth)
context.setStrokeColor(accentColor.cgColor)
context.setStrokeColor(constructiveColor.cgColor)
context.addPath(UIBezierPath(roundedRect: bounds.insetBy(dx: (borderLineWidth - UIScreenPixel) / 2.0, dy: (borderLineWidth - UIScreenPixel) / 2.0), cornerRadius: backgroundCornerRadius - UIScreenPixel).cgPath)
context.strokePath()
@ -196,23 +207,109 @@ private let fadeImage = generateImage(CGSize(width: 1.0, height: 30.0), rotatedC
})
private class VoiceChatParticipantStatusNode: ASDisplayNode {
private let iconNode: ASImageNode
private var iconNodes: [ASImageNode]
private let textNode: TextNode
private var currentParams: (CGSize, VoiceChatParticipantItem.ParticipantText)?
override init() {
self.iconNode = ASImageNode()
self.iconNode.displaysAsynchronously = false
self.iconNodes = []
self.textNode = TextNode()
self.textNode.isUserInteractionEnabled = false
self.textNode.contentMode = .left
self.textNode.contentsScale = UIScreen.main.scale
super.init()
self.addSubnode(self.iconNode)
self.addSubnode(self.textNode)
}
func update() {
func asyncLayout() -> (_ size: CGSize, _ text: VoiceChatParticipantItem.ParticipantText, _ transparent: Bool) -> (CGSize, () -> Void) {
let makeTextLayout = TextNode.asyncLayout(self.textNode)
return { size, text, transparent in
let statusFont = Font.regular(14.0)
var attributedString: NSAttributedString?
var color: UIColor = .white
var hasVolume = false
var hasVideo = false
var hasScreen = false
switch text {
case let .text(text, textIcon, textColor):
hasVolume = textIcon.contains(.volume)
hasVideo = textIcon.contains(.video)
hasScreen = textIcon.contains(.screen)
var textColorValue: UIColor
switch textColor {
case .generic:
textColorValue = UIColor(rgb: 0x98989e)
case .accent:
textColorValue = accentColor
case .constructive:
textColorValue = constructiveColor
case .destructive:
textColorValue = destructiveColor
}
if transparent {
textColorValue = UIColor(rgb: 0xffffff, alpha: 0.65)
}
color = textColorValue
attributedString = NSAttributedString(string: text, font: statusFont, textColor: textColorValue)
default:
break
}
let iconSize = CGSize(width: 16.0, height: 16.0)
let spacing: CGFloat = 3.0
var icons: [UIImage] = []
if hasVolume, let image = generateTintedImage(image: UIImage(bundleImageName: "Call/StatusVolume"), color: color) {
icons.append(image)
}
if hasVideo, let image = generateTintedImage(image: UIImage(bundleImageName: "Call/StatusVideo"), color: color) {
icons.append(image)
}
if hasScreen, let image = generateTintedImage(image: UIImage(bundleImageName: "Call/StatusScreen"), color: color) {
icons.append(image)
}
let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: attributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: size.width - (iconSize.width + spacing) * CGFloat(icons.count), height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
var contentSize = textLayout.size
contentSize.width += (iconSize.width + spacing) * CGFloat(icons.count)
return (contentSize, { [weak self] in
guard let strongSelf = self else {
return
}
strongSelf.currentParams = (size, text)
for i in 0 ..< icons.count {
let iconNode: ASImageNode
if strongSelf.iconNodes.count >= i + 1 {
iconNode = strongSelf.iconNodes[i]
} else {
iconNode = ASImageNode()
strongSelf.addSubnode(iconNode)
strongSelf.iconNodes.append(iconNode)
}
iconNode.frame = CGRect(origin: CGPoint(x: (iconSize.width + spacing) * CGFloat(i), y: 0.0), size: iconSize)
iconNode.image = icons[i]
}
if strongSelf.iconNodes.count > icons.count {
for i in icons.count ..< strongSelf.iconNodes.count {
strongSelf.iconNodes[i].image = nil
}
}
let _ = textApply()
strongSelf.textNode.frame = CGRect(origin: CGPoint(x: (iconSize.width + spacing) * CGFloat(icons.count), y: 0.0), size: textLayout.size)
})
}
}
}
@ -237,9 +334,8 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
private let pinIconNode: ASImageNode
private let contentWrapperNode: ASDisplayNode
private let titleNode: TextNode
private let statusIconNode: ASImageNode
private let statusNode: TextNode
private let expandedStatusNode: TextNode
private let statusNode: VoiceChatParticipantStatusNode
private let expandedStatusNode: VoiceChatParticipantStatusNode
private var credibilityIconNode: ASImageNode?
private var avatarTransitionNode: ASImageNode?
@ -332,18 +428,11 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
self.titleNode.contentMode = .left
self.titleNode.contentsScale = UIScreen.main.scale
self.statusIconNode = ASImageNode()
self.statusIconNode.displaysAsynchronously = false
self.statusNode = TextNode()
self.statusNode = VoiceChatParticipantStatusNode()
self.statusNode.isUserInteractionEnabled = false
self.statusNode.contentMode = .left
self.statusNode.contentsScale = UIScreen.main.scale
self.expandedStatusNode = TextNode()
self.expandedStatusNode = VoiceChatParticipantStatusNode()
self.expandedStatusNode.isUserInteractionEnabled = false
self.expandedStatusNode.contentMode = .left
self.expandedStatusNode.contentsScale = UIScreen.main.scale
self.expandedStatusNode.alpha = 0.0
self.actionContainerNode = ASDisplayNode()
@ -366,7 +455,6 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
self.offsetContainerNode.addSubnode(self.videoContainerNode)
self.offsetContainerNode.addSubnode(self.contentWrapperNode)
self.contentWrapperNode.addSubnode(self.titleNode)
self.contentWrapperNode.addSubnode(self.statusIconNode)
self.contentWrapperNode.addSubnode(self.statusNode)
self.contentWrapperNode.addSubnode(self.expandedStatusNode)
self.contentWrapperNode.addSubnode(self.actionContainerNode)
@ -877,8 +965,8 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
func asyncLayout() -> (_ item: VoiceChatParticipantItem, _ params: ListViewItemLayoutParams, _ first: Bool, _ last: Bool) -> (ListViewItemNodeLayout, (Bool, Bool) -> Void) {
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
let makeStatusLayout = TextNode.asyncLayout(self.statusNode)
let makeExpandedStatusLayout = TextNode.asyncLayout(self.expandedStatusNode)
let makeStatusLayout = self.statusNode.asyncLayout()
let makeExpandedStatusLayout = self.expandedStatusNode.asyncLayout()
var currentDisabledOverlayNode = self.disabledOverlayNode
let currentItem = self.layoutParams?.0
@ -891,11 +979,8 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
}
var titleFont = item.style == .list ? Font.regular(17.0) : Font.regular(12.0)
let statusFont = Font.regular(14.0)
var titleAttributedString: NSAttributedString?
var statusAttributedString: NSAttributedString?
var expandedStatusAttributedString: NSAttributedString?
let rightInset: CGFloat = params.rightInset
@ -960,60 +1045,15 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
}
var wavesColor = UIColor(rgb: 0x34c759)
switch item.text {
case .presence:
if let user = item.peer as? TelegramUser, let botInfo = user.botInfo {
let botStatus: String
if botInfo.flags.contains(.hasAccessToChatHistory) {
botStatus = item.presentationData.strings.Bot_GroupStatusReadsHistory
} else {
botStatus = item.presentationData.strings.Bot_GroupStatusDoesNotReadHistory
}
statusAttributedString = NSAttributedString(string: botStatus, font: statusFont, textColor: item.presentationData.theme.list.itemSecondaryTextColor)
} else if let presence = item.presence as? TelegramUserPresence {
let timestamp = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970
let (string, _) = stringAndActivityForUserPresence(strings: item.presentationData.strings, dateTimeFormat: item.dateTimeFormat, presence: presence, relativeTo: Int32(timestamp))
statusAttributedString = NSAttributedString(string: string, font: statusFont, textColor: item.presentationData.theme.list.itemSecondaryTextColor)
} else {
statusAttributedString = NSAttributedString(string: item.presentationData.strings.LastSeen_Offline, font: statusFont, textColor: item.presentationData.theme.list.itemSecondaryTextColor)
}
case let .text(text, textIcon, textColor):
var textColorValue: UIColor
switch textColor {
case .generic:
textColorValue = item.presentationData.theme.list.itemSecondaryTextColor
case .accent:
textColorValue = item.presentationData.theme.list.itemAccentColor
wavesColor = textColorValue
case .constructive:
textColorValue = constructiveColor
case .destructive:
textColorValue = destructiveColor
wavesColor = textColorValue
}
if item.transparent && item.style == .list {
textColorValue = UIColor(rgb: 0xffffff, alpha: 0.65)
}
statusAttributedString = NSAttributedString(string: text, font: statusFont, textColor: textColorValue)
case .none:
break
}
if let expandedText = item.expandedText, case let .text(text, _, textColor) = expandedText {
let textColorValue: UIColor
if case let .text(_, _, textColor) = item.text {
switch textColor {
case .generic:
textColorValue = item.presentationData.theme.list.itemSecondaryTextColor
case .accent:
textColorValue = item.presentationData.theme.list.itemAccentColor
case .constructive:
textColorValue = constructiveColor
case .destructive:
textColorValue = destructiveColor
case .accent:
wavesColor = accentColor
case .destructive:
wavesColor = destructiveColor
default:
break
}
expandedStatusAttributedString = NSAttributedString(string: text, font: statusFont, textColor: textColorValue)
} else {
expandedStatusAttributedString = statusAttributedString
}
let leftInset: CGFloat = 58.0 + params.leftInset
@ -1051,13 +1091,14 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
constrainedWidth = params.width - 24.0 - 10.0
}
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: constrainedWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let (statusLayout, statusApply) = makeStatusLayout(TextNodeLayoutArguments(attributedString: statusAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - 8.0 - rightInset - 30.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let (expandedStatusLayout, expandedStatusApply) = makeExpandedStatusLayout(TextNodeLayoutArguments(attributedString: expandedStatusAttributedString, backgroundColor: nil, maximumNumberOfLines: 6, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - 8.0 - rightInset - expandedRightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let titleSpacing: CGFloat = statusLayout.size.height == 0.0 ? 0.0 : 1.0
let (statusLayout, statusApply) = makeStatusLayout(CGSize(width: params.width - leftInset - 8.0 - rightInset - 30.0, height: CGFloat.greatestFiniteMagnitude), item.text, item.transparent && item.style == .list)
let (expandedStatusLayout, expandedStatusApply) = makeExpandedStatusLayout(CGSize(width: params.width - leftInset - 8.0 - rightInset - expandedRightInset, height: CGFloat.greatestFiniteMagnitude), item.expandedText ?? item.text, false)
let titleSpacing: CGFloat = statusLayout.height == 0.0 ? 0.0 : 1.0
let minHeight: CGFloat = titleLayout.size.height + verticalInset * 2.0
let rawHeight: CGFloat = verticalInset * 2.0 + titleLayout.size.height + titleSpacing + statusLayout.size.height
let rawHeight: CGFloat = verticalInset * 2.0 + titleLayout.size.height + titleSpacing + statusLayout.height
let contentSize: CGSize
let insets: UIEdgeInsets
@ -1122,6 +1163,7 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
return (layout, { [weak self] synchronousLoad, animated in
if let strongSelf = self {
var hadItem = strongSelf.layoutParams?.0 != nil
strongSelf.layoutParams = (item, params, first, last)
strongSelf.currentTitle = titleAttributedString?.string
strongSelf.wavesColor = wavesColor
@ -1167,7 +1209,7 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
}
var extractedRect = CGRect(origin: CGPoint(), size: layout.contentSize).insetBy(dx: 16.0 + params.leftInset, dy: 0.0)
var extractedHeight = extractedRect.height + expandedStatusLayout.size.height - statusLayout.size.height
var extractedHeight = extractedRect.height + expandedStatusLayout.height - statusLayout.height
var extractedVerticalOffset: CGFloat = 0.0
if item.peer.smallProfileImage != nil || strongSelf.videoNode != nil {
extractedVerticalOffset = extractedRect.width
@ -1209,9 +1251,9 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
strongSelf.accessibilityLabel = titleAttributedString?.string
var combinedValueString = ""
if let statusString = statusAttributedString?.string, !statusString.isEmpty {
combinedValueString.append(statusString)
}
// if let statusString = statusAttributedString?.string, !statusString.isEmpty {
// combinedValueString.append(statusString)
// }
strongSelf.accessibilityValue = combinedValueString
@ -1222,8 +1264,8 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
}
let transition: ContainedViewLayoutTransition
if animated {
transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring)
if animated && hadItem {
transition = ContainedViewLayoutTransition.animated(duration: 0.3, curve: .easeInOut)
} else {
transition = .immediate
}
@ -1293,8 +1335,8 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
transition.updateFrame(node: strongSelf.bottomStripeNode, frame: CGRect(origin: CGPoint(x: leftInset, y: contentSize.height + -separatorHeight), size: CGSize(width: layoutSize.width - leftInset, height: separatorHeight)))
transition.updateFrame(node: strongSelf.titleNode, frame: titleFrame)
transition.updateFrame(node: strongSelf.statusNode, frame: CGRect(origin: CGPoint(x: leftInset, y: strongSelf.titleNode.frame.maxY + titleSpacing), size: statusLayout.size))
transition.updateFrame(node: strongSelf.expandedStatusNode, frame: CGRect(origin: CGPoint(x: leftInset, y: strongSelf.titleNode.frame.maxY + titleSpacing), size: expandedStatusLayout.size))
transition.updateFrame(node: strongSelf.statusNode, frame: CGRect(origin: CGPoint(x: leftInset, y: strongSelf.titleNode.frame.maxY + titleSpacing), size: statusLayout))
transition.updateFrame(node: strongSelf.expandedStatusNode, frame: CGRect(origin: CGPoint(x: leftInset, y: strongSelf.titleNode.frame.maxY + titleSpacing), size: expandedStatusLayout))
if let currentCredibilityIconImage = currentCredibilityIconImage {
let iconNode: ASImageNode

View File

@ -66,6 +66,9 @@ final class VoiceChatTitleNode: ASDisplayNode {
}
func update(size: CGSize, title: String, subtitle: String, slide: Bool, transition: ContainedViewLayoutTransition) {
guard !size.width.isZero else {
return
}
var titleUpdated = false
if let previousTitle = self.titleNode.attributedText?.string {
titleUpdated = previousTitle != title

View File

@ -1,17 +1,8 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "airpods-b515@3x.png",
"idiom" : "universal",
"scale" : "3x"
"filename" : "ic_call_airpodsmax.pdf",
"idiom" : "universal"
}
],
"info" : {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

View File

@ -1,7 +1,7 @@
{
"images" : [
{
"filename" : "ic_call_flip.pdf",
"filename" : "ic_cam_flip.pdf",
"idiom" : "universal"
}
],

View File

@ -1,7 +1,7 @@
{
"images" : [
{
"filename" : "ic_vc_camera.pdf",
"filename" : "ic_voicesharing.pdf",
"idiom" : "universal"
}
],

View File

@ -1,7 +1,7 @@
{
"images" : [
{
"filename" : "volsmall.pdf",
"filename" : "ic_voicecamera.pdf",
"idiom" : "universal"
}
],

View File

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

View File

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

View File

@ -112,7 +112,7 @@ public final class AccountContextImpl: AccountContext {
public let engine: TelegramEngine
public let fetchManager: FetchManager
private let prefetchManager: PrefetchManager?
public let prefetchManager: PrefetchManager?
public var keyShortcutsController: KeyShortcutsController?
@ -172,7 +172,7 @@ public final class AccountContextImpl: AccountContext {
}
self.fetchManager = FetchManagerImpl(postbox: account.postbox, storeManager: self.downloadedMediaStoreManager)
if sharedContext.applicationBindings.isMainApp && !temp {
self.prefetchManager = PrefetchManager(sharedContext: sharedContext, account: account, fetchManager: self.fetchManager)
self.prefetchManager = PrefetchManagerImpl(sharedContext: sharedContext, account: account, engine: self.engine, fetchManager: self.fetchManager)
self.wallpaperUploadManager = WallpaperUploadManagerImpl(sharedContext: sharedContext, account: account, presentationData: sharedContext.presentationData)
self.themeUpdateManager = ThemeUpdateManagerImpl(sharedContext: sharedContext, account: account)
} else {

View File

@ -418,7 +418,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
private var importStateDisposable: Disposable?
public init(context: AccountContext, chatLocation: ChatLocation, chatLocationContextHolder: Atomic<ChatLocationContextHolder?> = Atomic<ChatLocationContextHolder?>(value: nil), subject: ChatControllerSubject? = nil, botStart: ChatControllerInitialBotStart? = nil, mode: ChatControllerPresentationMode = .standard(previewing: false), peekData: ChatPeekTimeout? = nil, peerNearbyData: ChatPeerNearbyData? = nil, greetingData: ChatGreetingData? = nil) {
public init(context: AccountContext, chatLocation: ChatLocation, chatLocationContextHolder: Atomic<ChatLocationContextHolder?> = Atomic<ChatLocationContextHolder?>(value: nil), subject: ChatControllerSubject? = nil, botStart: ChatControllerInitialBotStart? = nil, mode: ChatControllerPresentationMode = .standard(previewing: false), peekData: ChatPeekTimeout? = nil, peerNearbyData: ChatPeerNearbyData? = nil) {
let _ = ChatControllerCount.modify { value in
return value + 1
}
@ -463,7 +463,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
self.stickerSettings = ChatInterfaceStickerSettings(loopAnimatedStickers: false)
self.presentationInterfaceState = ChatPresentationInterfaceState(chatWallpaper: self.presentationData.chatWallpaper, theme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder, limitsConfiguration: context.currentLimitsConfiguration.with { $0 }, fontSize: self.presentationData.chatFontSize, bubbleCorners: self.presentationData.chatBubbleCorners, accountPeerId: context.account.peerId, mode: mode, chatLocation: chatLocation, subject: subject, peerNearbyData: peerNearbyData, greetingData: greetingData, pendingUnpinnedAllMessages: false, activeGroupCallInfo: nil, hasActiveGroupCall: false, importState: nil)
self.presentationInterfaceState = ChatPresentationInterfaceState(chatWallpaper: self.presentationData.chatWallpaper, theme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder, limitsConfiguration: context.currentLimitsConfiguration.with { $0 }, fontSize: self.presentationData.chatFontSize, bubbleCorners: self.presentationData.chatBubbleCorners, accountPeerId: context.account.peerId, mode: mode, chatLocation: chatLocation, subject: subject, peerNearbyData: peerNearbyData, greetingData: context.prefetchManager?.preloadedGreetingSticker, pendingUnpinnedAllMessages: false, activeGroupCallInfo: nil, hasActiveGroupCall: false, importState: nil)
self.presentationInterfaceStatePromise = ValuePromise(self.presentationInterfaceState)
var mediaAccessoryPanelVisibility = MediaAccessoryPanelVisibility.none

View File

@ -161,7 +161,7 @@ private final class ChatEmptyNodeGreetingChatContent: ASDisplayNode, ChatEmptyNo
} else if !self.didSetupSticker {
let sticker: Signal<TelegramMediaFile?, NoError>
if let preloadedSticker = interfaceState.greetingData?.sticker {
sticker = .single(preloadedSticker)
sticker = preloadedSticker
} else {
sticker = self.context.engine.stickers.randomGreetingSticker()
|> map { item -> TelegramMediaFile? in
@ -338,7 +338,7 @@ private final class ChatEmptyNodeNearbyChatContent: ASDisplayNode, ChatEmptyNode
} else if !self.didSetupSticker {
let sticker: Signal<TelegramMediaFile?, NoError>
if let preloadedSticker = interfaceState.greetingData?.sticker {
sticker = .single(preloadedSticker)
sticker = preloadedSticker
} else {
sticker = self.context.engine.stickers.randomGreetingSticker()
|> map { item -> TelegramMediaFile? in
@ -876,6 +876,7 @@ final class ChatEmptyNode: ASDisplayNode {
node = ChatEmptyNodeNearbyChatContent(context: self.context, interaction: self.interaction)
case .greeting:
node = ChatEmptyNodeGreetingChatContent(context: self.context, interaction: self.interaction)
self.context.prefetchManager?.prepareNextGreetingSticker()
}
self.content = (contentType, node)
self.addSubnode(node)

View File

@ -71,7 +71,7 @@ public func navigateToChatControllerImpl(_ params: NavigateToChatControllerParam
})
}
} else {
controller = ChatControllerImpl(context: params.context, chatLocation: params.chatLocation, chatLocationContextHolder: params.chatLocationContextHolder, subject: params.subject, botStart: params.botStart, peekData: params.peekData, peerNearbyData: params.peerNearbyData, greetingData: params.greetingData)
controller = ChatControllerImpl(context: params.context, chatLocation: params.chatLocation, chatLocationContextHolder: params.chatLocationContextHolder, subject: params.subject, botStart: params.botStart, peekData: params.peekData, peerNearbyData: params.peerNearbyData)
}
controller.purposefulAction = params.purposefulAction
if let search = params.activateMessageSearch {

View File

@ -1532,9 +1532,6 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
private var groupMembersSearchContext: GroupMembersSearchContext?
private let preloadedSticker = Promise<TelegramMediaFile?>(nil)
private let preloadStickerDisposable = MetaDisposable()
private let displayAsPeersPromise = Promise<[FoundPeer]>([])
fileprivate let accountsAndPeers = Promise<[(Account, Peer, Int32)]>()
@ -2838,24 +2835,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
if let _ = nearbyPeerDistance {
self.preloadHistoryDisposable.set(self.context.account.addAdditionalPreloadHistoryPeerId(peerId: peerId))
self.preloadedSticker.set(.single(nil)
|> then(context.engine.stickers.randomGreetingSticker()
|> map { item in
return item?.file
}))
self.preloadStickerDisposable.set((self.preloadedSticker.get()
|> mapToSignal { sticker -> Signal<Void, NoError> in
if let sticker = sticker {
let _ = freeMediaFileInteractiveFetched(account: context.account, fileReference: .standalone(media: sticker)).start()
return chatMessageAnimationData(postbox: context.account.postbox, resource: sticker.resource, fitzModifier: nil, width: 384, height: 384, synchronousLoad: false)
|> mapToSignal { _ -> Signal<Void, NoError> in
return .complete()
}
} else {
return .complete()
}
}).start())
self.context.prefetchManager?.prepareNextGreetingSticker()
}
}
@ -2870,7 +2850,6 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
self.selectAddMemberDisposable.dispose()
self.addMemberDisposable.dispose()
self.preloadHistoryDisposable.dispose()
self.preloadStickerDisposable.dispose()
self.resolvePeerByNameDisposable?.dispose()
self.navigationActionDisposable.dispose()
self.enqueueMediaMessageDisposable.dispose()
@ -3356,24 +3335,18 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
switch key {
case .message:
if let navigationController = controller.navigationController as? NavigationController {
let _ = (self.preloadedSticker.get()
|> take(1)
|> deliverOnMainQueue).start(next: { [weak self] sticker in
if let strongSelf = self {
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(strongSelf.peerId), keepStack: strongSelf.nearbyPeerDistance != nil ? .always : .default, peerNearbyData: strongSelf.nearbyPeerDistance.flatMap({ ChatPeerNearbyData(distance: $0) }), greetingData: strongSelf.nearbyPeerDistance != nil ? sticker.flatMap({ ChatGreetingData(sticker: $0) }) : nil, completion: { _ in
if strongSelf.nearbyPeerDistance != nil {
var viewControllers = navigationController.viewControllers
viewControllers = viewControllers.filter { controller in
if controller is PeerInfoScreen {
return false
}
return true
}
navigationController.setViewControllers(viewControllers, animated: false)
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(self.peerId), keepStack: self.nearbyPeerDistance != nil ? .always : .default, peerNearbyData: self.nearbyPeerDistance.flatMap({ ChatPeerNearbyData(distance: $0) }), completion: { [weak self] _ in
if let strongSelf = self, strongSelf.nearbyPeerDistance != nil {
var viewControllers = navigationController.viewControllers
viewControllers = viewControllers.filter { controller in
if controller is PeerInfoScreen {
return false
}
}))
return true
}
navigationController.setViewControllers(viewControllers, animated: false)
}
})
}))
}
case .discussion:
if let cachedData = self.data?.cachedData as? CachedChannelData, case let .known(maybeLinkedDiscussionPeerId) = cachedData.linkedDiscussionPeerId, let linkedDiscussionPeerId = maybeLinkedDiscussionPeerId {
@ -3773,24 +3746,18 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
private func openChatWithMessageSearch() {
if let navigationController = (self.controller?.navigationController as? NavigationController) {
let _ = (self.preloadedSticker.get()
|> take(1)
|> deliverOnMainQueue).start(next: { [weak self] sticker in
if let strongSelf = self {
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(strongSelf.peerId), keepStack: strongSelf.nearbyPeerDistance != nil ? .always : .default, activateMessageSearch: (.everything, ""), peerNearbyData: strongSelf.nearbyPeerDistance.flatMap({ ChatPeerNearbyData(distance: $0) }), greetingData: strongSelf.nearbyPeerDistance != nil ? sticker.flatMap({ ChatGreetingData(sticker: $0) }) : nil, completion: { _ in
if strongSelf.nearbyPeerDistance != nil {
var viewControllers = navigationController.viewControllers
viewControllers = viewControllers.filter { controller in
if controller is PeerInfoScreen {
return false
}
return true
}
navigationController.setViewControllers(viewControllers, animated: false)
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(self.peerId), keepStack: self.nearbyPeerDistance != nil ? .always : .default, activateMessageSearch: (.everything, ""), peerNearbyData: self.nearbyPeerDistance.flatMap({ ChatPeerNearbyData(distance: $0) }), completion: { [weak self] _ in
if let strongSelf = self, strongSelf.nearbyPeerDistance != nil {
var viewControllers = navigationController.viewControllers
viewControllers = viewControllers.filter { controller in
if controller is PeerInfoScreen {
return false
}
}))
return true
}
navigationController.setViewControllers(viewControllers, animated: false)
}
})
}))
}
}
@ -4233,24 +4200,18 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
private func openChat() {
if let navigationController = self.controller?.navigationController as? NavigationController {
let _ = (self.preloadedSticker.get()
|> take(1)
|> deliverOnMainQueue).start(next: { [weak self] sticker in
if let strongSelf = self {
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(strongSelf.peerId), keepStack: strongSelf.nearbyPeerDistance != nil ? .always : .default, peerNearbyData: strongSelf.nearbyPeerDistance.flatMap({ ChatPeerNearbyData(distance: $0) }), greetingData: strongSelf.nearbyPeerDistance != nil ? sticker.flatMap({ ChatGreetingData(sticker: $0) }) : nil, completion: { _ in
if strongSelf.nearbyPeerDistance != nil {
var viewControllers = navigationController.viewControllers
viewControllers = viewControllers.filter { controller in
if controller is PeerInfoScreen {
return false
}
return true
}
navigationController.setViewControllers(viewControllers, animated: false)
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(self.peerId), keepStack: self.nearbyPeerDistance != nil ? .always : .default, peerNearbyData: self.nearbyPeerDistance.flatMap({ ChatPeerNearbyData(distance: $0) }), completion: { [weak self] _ in
if let strongSelf = self, strongSelf.nearbyPeerDistance != nil {
var viewControllers = navigationController.viewControllers
viewControllers = viewControllers.filter { controller in
if controller is PeerInfoScreen {
return false
}
}))
return true
}
navigationController.setViewControllers(viewControllers, animated: false)
}
})
}))
}
}

View File

@ -6,6 +6,7 @@ import SyncCore
import TelegramUIPreferences
import AccountContext
import PhotoResources
import StickerResources
import Emoji
import UniversalMediaPlayer
@ -21,18 +22,23 @@ public enum PrefetchMediaItem {
case animatedEmojiSticker(TelegramMediaFile)
}
private final class PrefetchManagerImpl {
private final class PrefetchManagerInnerImpl {
private let queue: Queue
private let account: Account
private let engine: TelegramEngine
private let fetchManager: FetchManager
private var listDisposable: Disposable?
private var contexts: [MediaId: PrefetchMediaContext] = [:]
init(queue: Queue, sharedContext: SharedAccountContext, account: Account, fetchManager: FetchManager) {
private let preloadGreetingStickerDisposable = MetaDisposable()
fileprivate let preloadedGreetingStickerPromise = Promise<TelegramMediaFile?>(nil)
init(queue: Queue, sharedContext: SharedAccountContext, account: Account, engine: TelegramEngine, fetchManager: FetchManager) {
self.queue = queue
self.account = account
self.engine = engine
self.fetchManager = fetchManager
let networkType = account.networkType
@ -226,18 +232,63 @@ private final class PrefetchManagerImpl {
}
}
}
fileprivate func prepareNextGreetingSticker() {
let account = self.account
let engine = self.engine
self.preloadedGreetingStickerPromise.set(.single(nil)
|> then(engine.stickers.randomGreetingSticker()
|> map { item in
return item?.file
}))
self.preloadGreetingStickerDisposable.set((self.preloadedGreetingStickerPromise.get()
|> mapToSignal { sticker -> Signal<Void, NoError> in
if let sticker = sticker {
let _ = freeMediaFileInteractiveFetched(account: account, fileReference: .standalone(media: sticker)).start()
return chatMessageAnimationData(postbox: account.postbox, resource: sticker.resource, fitzModifier: nil, width: 384, height: 384, synchronousLoad: false)
|> mapToSignal { _ -> Signal<Void, NoError> in
return .complete()
}
} else {
return .complete()
}
}).start())
}
}
final class PrefetchManager {
final class PrefetchManagerImpl: PrefetchManager {
private let queue: Queue
private let impl: QueueLocalObject<PrefetchManagerImpl>
private let impl: QueueLocalObject<PrefetchManagerInnerImpl>
private let uuid = Atomic<UUID>(value: UUID())
init(sharedContext: SharedAccountContext, account: Account, fetchManager: FetchManager) {
init(sharedContext: SharedAccountContext, account: Account, engine: TelegramEngine, fetchManager: FetchManager) {
let queue = Queue.mainQueue()
self.queue = queue
self.impl = QueueLocalObject(queue: queue, generate: {
return PrefetchManagerImpl(queue: queue, sharedContext: sharedContext, account: account, fetchManager: fetchManager)
return PrefetchManagerInnerImpl(queue: queue, sharedContext: sharedContext, account: account, engine: engine, fetchManager: fetchManager)
})
}
var preloadedGreetingSticker: ChatGreetingData {
let signal: Signal<TelegramMediaFile?, NoError> = Signal { subscriber in
let disposable = MetaDisposable()
self.impl.with { impl in
disposable.set((impl.preloadedGreetingStickerPromise.get() |> take(1)).start(next: { file in
subscriber.putNext(file)
subscriber.putCompletion()
}))
}
return disposable
}
return ChatGreetingData(uuid: uuid.with { $0 }, sticker: signal)
}
func prepareNextGreetingSticker() {
let _ = uuid.swap(UUID())
self.impl.with { impl in
impl.prepareNextGreetingSticker()
}
}
}

@ -1 +1 @@
Subproject commit 4c6ae0a13d9360d9ed51e78e4b79513bbedf5525
Subproject commit 94a9c7b4e49c943d1ca108e35779739ad99d695a