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"; "Calls.StartNewCall" = "Start New Call";
"VoiceChat.VideoPreviewTitle" = "Video Preview"; "VoiceChat.VideoPreviewTitle" = "Video Preview";
"VoiceChat.VideoPreviewDescription" = "Place your face in the area above for better experience."; "VoiceChat.VideoPreviewDescription" = "Are you sure you want to share your video?";
"VoiceChat.VideoPreviewShare" = "Share 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 final class ChatGreetingData: Equatable {
public static func == (lhs: ChatGreetingData, rhs: ChatGreetingData) -> Bool { public static func == (lhs: ChatGreetingData, rhs: ChatGreetingData) -> Bool {
if let lhsSticker = lhs.sticker, let rhsSticker = rhs.sticker, !lhsSticker.isEqual(to: rhsSticker) { return lhs.uuid == rhs.uuid
return false
} else if (lhs.sticker == nil) != (rhs.sticker == nil) {
return false
}
return true
} }
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 self.sticker = sticker
} }
} }
@ -285,14 +282,13 @@ public final class NavigateToChatControllerParams {
public let activateMessageSearch: (ChatSearchDomain, String)? public let activateMessageSearch: (ChatSearchDomain, String)?
public let peekData: ChatPeekTimeout? public let peekData: ChatPeekTimeout?
public let peerNearbyData: ChatPeerNearbyData? public let peerNearbyData: ChatPeerNearbyData?
public let greetingData: ChatGreetingData?
public let reportReason: ReportReason? public let reportReason: ReportReason?
public let animated: Bool public let animated: Bool
public let options: NavigationAnimationOptions public let options: NavigationAnimationOptions
public let parentGroupId: PeerGroupId? public let parentGroupId: PeerGroupId?
public let completion: (ChatController) -> Void 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.navigationController = navigationController
self.chatController = chatController self.chatController = chatController
self.chatLocationContextHolder = chatLocationContextHolder self.chatLocationContextHolder = chatLocationContextHolder
@ -309,7 +305,6 @@ public final class NavigateToChatControllerParams {
self.activateMessageSearch = activateMessageSearch self.activateMessageSearch = activateMessageSearch
self.peekData = peekData self.peekData = peekData
self.peerNearbyData = peerNearbyData self.peerNearbyData = peerNearbyData
self.greetingData = greetingData
self.reportReason = reportReason self.reportReason = reportReason
self.animated = animated self.animated = animated
self.options = options self.options = options
@ -719,6 +714,7 @@ public protocol AccountContext: class {
var liveLocationManager: LiveLocationManager? { get } var liveLocationManager: LiveLocationManager? { get }
var peersNearbyManager: PeersNearbyManager? { get } var peersNearbyManager: PeersNearbyManager? { get }
var fetchManager: FetchManager { get } var fetchManager: FetchManager { get }
var prefetchManager: PrefetchManager? { get }
var downloadedMediaStoreManager: DownloadedMediaStoreManager { get } var downloadedMediaStoreManager: DownloadedMediaStoreManager { get }
var peerChannelMemberCategoriesContextsManager: PeerChannelMemberCategoriesContextsManager { get } var peerChannelMemberCategoriesContextsManager: PeerChannelMemberCategoriesContextsManager { get }
var wallpaperUploadManager: WallpaperUploadManager? { 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 cancelInteractiveFetches(category: FetchManagerCategory, location: FetchManagerLocation, locationKey: FetchManagerLocationKey, resource: MediaResource)
func fetchStatus(category: FetchManagerCategory, location: FetchManagerLocation, locationKey: FetchManagerLocationKey, resource: MediaResource) -> Signal<MediaResourceStatus, NoError> 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 } var incomingVideoSources: Signal<Set<String>, NoError> { get }
func makeIncomingVideoView(endpointId: String, completion: @escaping (PresentationCallVideoView?) -> Void) func makeIncomingVideoView(endpointId: String, completion: @escaping (PresentationCallVideoView?) -> Void)
func makeOutgoingVideoView(completion: @escaping (PresentationCallVideoView?) -> Void)
func loadMoreMembers(token: String) func loadMoreMembers(token: String)
} }

View File

@ -144,9 +144,6 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
private let featuredFiltersDisposable = MetaDisposable() private let featuredFiltersDisposable = MetaDisposable()
private var processedFeaturedFilters = false private var processedFeaturedFilters = false
private let preloadedSticker = Promise<TelegramMediaFile?>(nil)
private let preloadStickerDisposable = MetaDisposable()
private let isReorderingTabsValue = ValuePromise<Bool>(false) private let isReorderingTabsValue = ValuePromise<Bool>(false)
private var searchContentNode: NavigationBarSearchContentNode? private var searchContentNode: NavigationBarSearchContentNode?
@ -613,53 +610,43 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
scrollToEndIfExists = true scrollToEndIfExists = true
} }
let _ = (strongSelf.preloadedSticker.get() 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
|> take(1) self?.chatListDisplayNode.containerNode.currentItemNode.clearHighlightAnimated(true)
|> deliverOnMainQueue).start(next: { [weak self] greetingSticker in if let promoInfo = promoInfo {
if let strongSelf = self { switch promoInfo {
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 case .proxy:
self?.chatListDisplayNode.containerNode.currentItemNode.clearHighlightAnimated(true) let _ = (ApplicationSpecificNotice.getProxyAdsAcknowledgment(accountManager: strongSelf.context.sharedContext.accountManager)
if let promoInfo = promoInfo { |> deliverOnMainQueue).start(next: { value in
switch promoInfo { guard let strongSelf = self else {
case .proxy: return
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()
}
})
} }
} if !value {
})) controller.displayPromoAnnouncement(text: strongSelf.presentationData.strings.DialogList_AdNoticeAlert)
let _ = ApplicationSpecificNotice.setProxyAdsAcknowledgment(accountManager: strongSelf.context.sharedContext.accountManager).start()
if activateInput { }
strongSelf.prepareRandomGreetingSticker() })
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() super.displayNodeDidLoad()
Queue.mainQueue().after(1.0) { 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() { override public func tabBarDisabledAction() {
self.donePressed() self.donePressed()
} }

View File

@ -89,9 +89,6 @@ public class ContactsController: ViewController {
public var switchToChatsController: (() -> Void)? public var switchToChatsController: (() -> Void)?
private let preloadedSticker = Promise<TelegramMediaFile?>(nil)
private let preloadStickerDisposable = MetaDisposable()
public override func updateNavigationCustomData(_ data: Any?, progress: CGFloat, transition: ContainedViewLayoutTransition) { public override func updateNavigationCustomData(_ data: Any?, progress: CGFloat, transition: ContainedViewLayoutTransition) {
if self.isNodeLoaded { if self.isNodeLoaded {
self.contactsNode.contactListNode.updateSelectedChatLocation(data as? ChatLocation, progress: progress, transition: transition) self.contactsNode.contactListNode.updateSelectedChatLocation(data as? ChatLocation, progress: progress, transition: transition)
@ -235,24 +232,16 @@ public class ContactsController: ViewController {
scrollToEndIfExists = true scrollToEndIfExists = true
} }
let _ = (strongSelf.preloadedSticker.get() strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peer.id), purposefulAction: { [weak self] in
|> take(1) if fromSearch {
|> deliverOnMainQueue).start(next: { [weak self] greetingSticker in self?.deactivateSearch(animated: false)
if let strongSelf = self { self?.switchToChatsController?()
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()
} }
}) }, scrollToEndIfExists: scrollToEndIfExists, options: [.removeOnMasterDetails], completion: { [weak self] _ in
if let strongSelf = self {
strongSelf.contactsNode.contactListNode.listNode.clearHighlightAnimated(true)
}
}))
} }
case let .deviceContact(id, _): case let .deviceContact(id, _):
let _ = ((strongSelf.context.sharedContext.contactDataManager?.extendedData(stableId: id) ?? .single(nil)) let _ = ((strongSelf.context.sharedContext.contactDataManager?.extendedData(stableId: id) ?? .single(nil))
@ -423,14 +412,6 @@ public class ContactsController: ViewController {
self.contactsNode.contactListNode.enableUpdates = false 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) { override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
super.containerLayoutUpdated(layout, transition: transition) 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 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 var minActionsWidth: CGFloat = 250.0
if let minimalWidth = minimalWidth, minimalWidth > minActionsWidth {
minActionsWidth = minimalWidth
}
switch widthClass { switch widthClass {
case .compact: case .compact:
@ -517,10 +520,10 @@ final class ContextActionsContainerNode: ASDisplayNode {
} }
var contentSize = CGSize() 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 { 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 contentSize = additionalActionsSize
let bounds = CGRect(origin: CGPoint(), size: additionalActionsSize) let bounds = CGRect(origin: CGPoint(), size: additionalActionsSize)

View File

@ -5,6 +5,7 @@ import AsyncDisplayKit
import SwiftSignalKit import SwiftSignalKit
import AppBundle import AppBundle
import SemanticStatusNode import SemanticStatusNode
import AnimationUI
private let labelFont = Font.regular(13.0) private let labelFont = Font.regular(13.0)
@ -30,6 +31,8 @@ final class CallControllerButtonItemNode: HighlightTrackingButtonNode {
} }
enum Image { enum Image {
case cameraOff
case cameraOn
case camera case camera
case mute case mute
case flipCamera case flipCamera
@ -63,6 +66,7 @@ final class CallControllerButtonItemNode: HighlightTrackingButtonNode {
private let effectView: UIVisualEffectView private let effectView: UIVisualEffectView
private let contentBackgroundNode: ASImageNode private let contentBackgroundNode: ASImageNode
private let contentNode: ASImageNode private let contentNode: ASImageNode
private var animationNode: AnimationNode?
private let overlayHighlightNode: ASImageNode private let overlayHighlightNode: ASImageNode
private var statusNode: SemanticStatusNode? private var statusNode: SemanticStatusNode?
let textNode: ImmediateTextNode let textNode: ImmediateTextNode
@ -179,6 +183,33 @@ final class CallControllerButtonItemNode: HighlightTrackingButtonNode {
let contentBackgroundImage: UIImage? = nil 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 let contentImage = generateImage(CGSize(width: self.largeButtonSize, height: self.largeButtonSize), contextGenerator: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size)) context.clear(CGRect(origin: CGPoint(), size: size))
@ -219,6 +250,8 @@ final class CallControllerButtonItemNode: HighlightTrackingButtonNode {
var image: UIImage? var image: UIImage?
switch content.image { switch content.image {
case .cameraOff, .cameraOn:
image = nil
case .camera: case .camera:
image = generateTintedImage(image: UIImage(bundleImageName: "Call/CallCameraButton"), color: imageColor) image = generateTintedImage(image: UIImage(bundleImageName: "Call/CallCameraButton"), color: imageColor)
case .mute: case .mute:

View File

@ -2384,6 +2384,76 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
self.participantsContext?.lowerHand() 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() { public func requestVideo() {
if self.videoCapturer == nil { if self.videoCapturer == nil {
let videoCapturer = OngoingCallVideoCapturer() let videoCapturer = OngoingCallVideoCapturer()

View File

@ -1,283 +1,399 @@
import Foundation import Foundation
import UIKit import UIKit
import SwiftSignalKit
import AsyncDisplayKit
import Display import Display
import AsyncDisplayKit
import Postbox import Postbox
import TelegramCore import TelegramCore
import SyncCore import SyncCore
import TelegramPresentationData import SwiftSignalKit
import AccountContext import AccountContext
import UrlEscaping import TelegramPresentationData
import SolidRoundedButtonNode
import PresentationDataUtils
import UIKitRuntimeUtils
private final class VoiceChatCameraPreviewAlertContentNode: AlertContentNode { final class VoiceChatCameraPreviewController: ViewController {
private let strings: PresentationStrings private var controllerNode: VoiceChatCameraPreviewControllerNode {
private let title: String return self.displayNode as! VoiceChatCameraPreviewControllerNode
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
} }
init(theme: AlertControllerTheme, ptheme: PresentationTheme, strings: PresentationStrings, actions: [TextAlertAction], title: String, text: String) { private let context: AccountContext
self.strings = strings
self.title = title private var animatedIn = false
self.text = text
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() super.init(navigationBarPresentationData: nil)
self.backgroundNode.clipsToBounds = true
self.backgroundNode.backgroundColor = UIColor(rgb: 0x161619)
self.maskNode = ASImageNode() self.statusBar.statusBarStyle = .Ignore
self.maskNode.displaysAsynchronously = false
self.maskNode.contentMode = .scaleToFill self.blocksBackgroundWhenInOverlay = true
self.maskNode.image = generateImage(CGSize(width: 300.0, height: 400.0), rotatedContext: { size, context in
let bounds = CGRect(origin: CGPoint(), size: size) self.presentationDataDisposable = (context.sharedContext.presentationData
context.clear(bounds) |> deliverOnMainQueue).start(next: { [weak self] presentationData in
if let strongSelf = self {
context.setFillColor(UIColor(rgb: 0x000000, alpha: 0.3).cgColor) strongSelf.controllerNode.updatePresentationData(presentationData)
context.fill(bounds) }
context.setBlendMode(.clear)
context.fillEllipse(in: CGRect(x: 27.0, y: 37.0, width: 246.0, height: 300.0))
}) })
self.titleNode = ASTextNode() self.statusBar.statusBarStyle = .Ignore
self.titleNode.maximumNumberOfLines = 2 }
self.textNode = ASTextNode()
self.textNode.maximumNumberOfLines = 8 required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
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)
} }
deinit { deinit {
self.disposable.dispose() self.presentationDataDisposable?.dispose()
} }
override func updateTheme(_ theme: AlertControllerTheme) { override public func loadDisplayNode() {
self.titleNode.attributedText = NSAttributedString(string: self.title, font: Font.bold(17.0), textColor: theme.primaryColor, paragraphAlignment: .center) self.displayNode = VoiceChatCameraPreviewControllerNode(context: self.context, cameraNode: self.cameraNode)
self.textNode.attributedText = NSAttributedString(string: self.text, font: Font.regular(13.0), textColor: theme.primaryColor, paragraphAlignment: .center) 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 private class VoiceChatCameraPreviewControllerNode: ViewControllerTracingNode, UIScrollViewDelegate {
for actionNode in self.actionNodes { private let context: AccountContext
actionNode.updateTheme(theme) private var presentationData: PresentationData
}
for separatorNode in self.actionVerticalSeparators {
separatorNode.backgroundColor = theme.separatorColor
}
if let size = self.validLayout {
_ = self.updateLayout(size: size, transition: .immediate)
}
}
override func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { private let cameraNode: GroupVideoNode
var size = size private let dimNode: ASDisplayNode
size.width = min(size.width, 300.0) private let wrappingScrollNode: ASScrollNode
let measureSize = CGSize(width: size.width - 16.0 * 2.0, height: CGFloat.greatestFiniteMagnitude) 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.cameraNode = cameraNode
self.validLayout = size
var origin: CGPoint = CGPoint(x: 0.0, y: 18.0) self.wrappingScrollNode = ASScrollNode()
let spacing: CGFloat = 3.0 self.wrappingScrollNode.view.alwaysBounceVertical = true
self.wrappingScrollNode.view.delaysContentTouches = false
self.wrappingScrollNode.view.canCancelContentTouches = true
let videoHeight: CGFloat = 400.0 self.dimNode = ASDisplayNode()
origin.y += videoHeight self.dimNode.backgroundColor = UIColor(white: 0.0, alpha: 0.5)
let titleSize = self.titleNode.measure(measureSize) self.contentContainerNode = ASDisplayNode()
transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - titleSize.width) / 2.0), y: origin.y), size: titleSize)) self.contentContainerNode.isOpaque = false
origin.y += titleSize.height + 4.0
self.backgroundNode = ASDisplayNode()
self.backgroundNode.clipsToBounds = true
self.backgroundNode.cornerRadius = 16.0
let textSize = self.textNode.measure(measureSize) let backgroundColor = UIColor(rgb: 0x1c1c1e)
transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: origin.y), size: textSize)) let textColor: UIColor = .white
origin.y += textSize.height + 6.0 let buttonColor: UIColor = UIColor(rgb: 0x2b2b2f)
let buttonTextColor: UIColor = .white
let blurStyle: UIBlurEffect.Style = .dark
let actionButtonHeight: CGFloat = 44.0 self.effectNode = ASDisplayNode(viewBlock: {
var minActionsWidth: CGFloat = 0.0 return UIVisualEffectView(effect: UIBlurEffect(style: blurStyle))
let maxActionWidth: CGFloat = floor(size.width / CGFloat(self.actionNodes.count)) })
let actionTitleInsets: CGFloat = 8.0
var effectiveActionLayout = TextAlertContentActionLayout.horizontal self.contentBackgroundNode = ASDisplayNode()
for actionNode in self.actionNodes { self.contentBackgroundNode.backgroundColor = backgroundColor
let actionTitleSize = actionNode.titleNode.updateLayout(CGSize(width: maxActionWidth, height: actionButtonHeight))
if case .horizontal = effectiveActionLayout, actionTitleSize.height > actionButtonHeight * 0.6667 { let title = self.presentationData.strings.VoiceChat_VideoPreviewTitle
effectiveActionLayout = .vertical
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: self.screenButton.pressed = { [weak self] in
minActionsWidth += actionTitleSize.width + actionTitleInsets if let strongSelf = self {
case .vertical: strongSelf.shareScreen?()
minActionsWidth = max(minActionsWidth, actionTitleSize.width + actionTitleInsets) }
}
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) self.switchCameraButton.addTarget(self, action: #selector(self.switchCameraPressed), forControlEvents: .touchUpInside)
self.switchCameraButton.highligthedChanged = { [weak self] highlighted in
var contentWidth = max(titleSize.width, minActionsWidth) if let strongSelf = self {
contentWidth = max(contentWidth, 300.0) if highlighted {
let transition: ContainedViewLayoutTransition = .animated(duration: 0.3, curve: .spring)
var actionsHeight: CGFloat = 0.0 transition.updateSublayerTransformScale(node: strongSelf.switchCameraButton, scale: 0.9)
switch effectiveActionLayout { } else {
case .horizontal: let transition: ContainedViewLayoutTransition = .animated(duration: 0.5, curve: .spring)
actionsHeight = actionButtonHeight transition.updateSublayerTransformScale(node: strongSelf.switchCameraButton, scale: 1.0)
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)))
} }
} }
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 { let bottomInset: CGFloat = 10.0 + cleanInsets.bottom
var presentationData = sharedContext.currentPresentationData.with { $0 } let titleHeight: CGFloat = 54.0
if let forceTheme = forceTheme { var contentHeight = titleHeight + bottomInset + 52.0 + 17.0
presentationData = presentationData.withUpdated(theme: forceTheme) let innerContentHeight: CGFloat = layout.size.height - contentHeight - 160.0
} contentHeight = titleHeight + bottomInset + 52.0 + 17.0 + innerContentHeight + buttonOffset
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 width = horizontalContainerFillingSizeForLayout(layout: layout, sideInset: layout.safeInsets.left)
let controller = AlertController(theme: AlertControllerTheme(presentationData: presentationData), contentNode: contentNode) let previewInset: CGFloat = 16.0
let presentationDataDisposable = sharedContext.presentationData.start(next: { [weak controller, weak contentNode] presentationData in let sideInset = floor((layout.size.width - width) / 2.0)
var presentationData = presentationData let contentContainerFrame = CGRect(origin: CGPoint(x: sideInset, y: layout.size.height - contentHeight), size: CGSize(width: width, height: contentHeight))
if let forceTheme = forceTheme { let contentFrame = contentContainerFrame
presentationData = presentationData.withUpdated(theme: forceTheme)
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) transition.updateFrame(node: self.backgroundNode, frame: backgroundFrame)
}) transition.updateFrame(node: self.effectNode, frame: CGRect(origin: CGPoint(), size: backgroundFrame.size))
controller.dismissed = { transition.updateFrame(node: self.contentBackgroundNode, frame: CGRect(origin: CGPoint(), size: backgroundFrame.size))
presentationDataDisposable.dispose() 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) rotatedVideoFrame.size.height = ceil(rotatedVideoFrame.size.height)
var videoSize = rotatedVideoFrame.size var videoSize = rotatedVideoFrame.size
// CGSize(width: 1203, height: 677)
transition.updatePosition(layer: self.videoView.view.layer, position: rotatedVideoFrame.center) transition.updatePosition(layer: self.videoView.view.layer, position: rotatedVideoFrame.center)
transition.updateBounds(layer: self.videoView.view.layer, bounds: CGRect(origin: CGPoint(), size: videoSize)) 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 context: AccountContext
private let call: PresentationGroupCall private let call: PresentationGroupCall
private var backdropVideoNode: GroupVideoNode?
private var currentVideoNode: GroupVideoNode? private var currentVideoNode: GroupVideoNode?
private var candidateVideoNode: GroupVideoNode? private var candidateVideoNode: GroupVideoNode?
private let topCornersNode: ASImageNode private let topCornersNode: ASImageNode
@ -291,7 +291,6 @@ private final class MainVideoContainerNode: ASDisplayNode {
strongSelf.candidateVideoNode = nil strongSelf.candidateVideoNode = nil
let videoNode = GroupVideoNode(videoView: videoView) let videoNode = GroupVideoNode(videoView: videoView)
if let currentVideoNode = strongSelf.currentVideoNode { if let currentVideoNode = strongSelf.currentVideoNode {
currentVideoNode.removeFromSupernode() currentVideoNode.removeFromSupernode()
strongSelf.currentVideoNode = nil strongSelf.currentVideoNode = nil
@ -429,7 +428,8 @@ public final class VoiceChatController: ViewController {
var isMyPeer: Bool var isMyPeer: Bool
var ssrc: UInt32? var ssrc: UInt32?
var effectiveVideoEndpointId: String? var effectiveVideoEndpointId: String?
var presence: TelegramUserPresence? var hasVideo: Bool
var hasScreencast: Bool
var activityTimestamp: Int32 var activityTimestamp: Int32
var state: State var state: State
var muteState: GroupCallParticipantsContext.Participant.MuteState? var muteState: GroupCallParticipantsContext.Participant.MuteState?
@ -447,7 +447,8 @@ public final class VoiceChatController: ViewController {
isMyPeer: Bool, isMyPeer: Bool,
ssrc: UInt32?, ssrc: UInt32?,
effectiveVideoEndpointId: String?, effectiveVideoEndpointId: String?,
presence: TelegramUserPresence?, hasVideo: Bool,
hasScreencast: Bool,
activityTimestamp: Int32, activityTimestamp: Int32,
state: State, state: State,
muteState: GroupCallParticipantsContext.Participant.MuteState?, muteState: GroupCallParticipantsContext.Participant.MuteState?,
@ -464,7 +465,8 @@ public final class VoiceChatController: ViewController {
self.isMyPeer = isMyPeer self.isMyPeer = isMyPeer
self.ssrc = ssrc self.ssrc = ssrc
self.effectiveVideoEndpointId = effectiveVideoEndpointId self.effectiveVideoEndpointId = effectiveVideoEndpointId
self.presence = presence self.hasVideo = hasVideo
self.hasScreencast = hasScreencast
self.activityTimestamp = activityTimestamp self.activityTimestamp = activityTimestamp
self.state = state self.state = state
self.muteState = muteState self.muteState = muteState
@ -497,7 +499,10 @@ public final class VoiceChatController: ViewController {
if lhs.effectiveVideoEndpointId != rhs.effectiveVideoEndpointId { if lhs.effectiveVideoEndpointId != rhs.effectiveVideoEndpointId {
return false return false
} }
if lhs.presence != rhs.presence { if lhs.hasVideo != rhs.hasVideo {
return false
}
if lhs.hasScreencast != rhs.hasScreencast {
return false return false
} }
if lhs.activityTimestamp != rhs.activityTimestamp { if lhs.activityTimestamp != rhs.activityTimestamp {
@ -622,7 +627,7 @@ public final class VoiceChatController: ViewController {
}) })
case let .peer(peerEntry): case let .peer(peerEntry):
let peer = peerEntry.peer let peer = peerEntry.peer
var text: VoiceChatParticipantItem.ParticipantText var text: VoiceChatParticipantItem.ParticipantText
var expandedText: VoiceChatParticipantItem.ParticipantText? var expandedText: VoiceChatParticipantItem.ParticipantText?
let icon: VoiceChatParticipantItem.Icon let icon: VoiceChatParticipantItem.Icon
@ -632,11 +637,12 @@ public final class VoiceChatController: ViewController {
state = .listening state = .listening
} }
let textIcon: VoiceChatParticipantItem.ParticipantText.Icon? var textIcon = VoiceChatParticipantItem.ParticipantText.TextIcon()
if peerEntry.volume != nil { if peerEntry.hasVideo {
textIcon = .volume textIcon.insert(.video)
} else { }
textIcon = nil if peerEntry.hasScreencast {
textIcon.insert(.screen)
} }
let yourText: String let yourText: String
if (peerEntry.about?.isEmpty ?? true) && peer.smallProfileImage == nil { 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) text = .text(presentationData.strings.VoiceChat_StatusMutedForYou, textIcon, .destructive)
icon = .microphone(true, UIColor(rgb: 0xff3b30)) icon = .microphone(true, UIColor(rgb: 0xff3b30))
} else { } else {
if peerEntry.volume != nil {
textIcon.insert(.volume)
}
let volumeValue = peerEntry.volume.flatMap { $0 / 100 } let volumeValue = peerEntry.volume.flatMap { $0 / 100 }
if let volume = volumeValue, volume != 100 { if let volume = volumeValue, volume != 100 {
text = .text( presentationData.strings.VoiceChat_StatusSpeakingVolume("\(volume)%").0, textIcon, .constructive) text = .text( presentationData.strings.VoiceChat_StatusSpeakingVolume("\(volume)%").0, textIcon, .constructive)
@ -699,7 +708,7 @@ public final class VoiceChatController: ViewController {
let revealOptions: [VoiceChatParticipantItem.RevealOption] = [] 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 { if let endpointId = peerEntry.effectiveVideoEndpointId {
return interaction.getPeerVideo(endpointId, peerEntry.style != .list) return interaction.getPeerVideo(endpointId, peerEntry.style != .list)
} else { } else {
@ -789,7 +798,6 @@ public final class VoiceChatController: ViewController {
private var enqueuedTransitions: [ListTransition] = [] private var enqueuedTransitions: [ListTransition] = []
private var enqueuedTileTransitions: [ListTransition] = [] private var enqueuedTileTransitions: [ListTransition] = []
private var floatingHeaderOffset: CGFloat?
private var validLayout: (ContainerViewLayout, CGFloat)? private var validLayout: (ContainerViewLayout, CGFloat)?
private var didSetContentsReady: Bool = false private var didSetContentsReady: Bool = false
@ -871,8 +879,8 @@ public final class VoiceChatController: ViewController {
private var requestedVideoSources = Set<String>() private var requestedVideoSources = Set<String>()
private var videoNodes: [(String, GroupVideoNode)] = [] private var videoNodes: [(String, GroupVideoNode)] = []
private var currentDominantSpeakerWithVideo: (PeerId, String)? private var currentDominantSpeakerWithVideo: PeerId?
private var currentForcedSpeakerWithVideo: (PeerId, String)? private var currentForcedSpeakerWithVideo: PeerId?
private var effectiveSpeakerWithVideo: (PeerId, String)? private var effectiveSpeakerWithVideo: (PeerId, String)?
private var updateAvatarDisposable = MetaDisposable() private var updateAvatarDisposable = MetaDisposable()
@ -978,11 +986,13 @@ public final class VoiceChatController: ViewController {
self.bottomPanelBackgroundNode = ASDisplayNode() self.bottomPanelBackgroundNode = ASDisplayNode()
self.bottomPanelBackgroundNode.backgroundColor = panelBackgroundColor self.bottomPanelBackgroundNode.backgroundColor = panelBackgroundColor
self.bottomPanelBackgroundNode.isUserInteractionEnabled = false
self.bottomCornersNode = ASImageNode() self.bottomCornersNode = ASImageNode()
self.bottomCornersNode.displaysAsynchronously = false self.bottomCornersNode.displaysAsynchronously = false
self.bottomCornersNode.displayWithoutProcessing = true self.bottomCornersNode.displayWithoutProcessing = true
self.bottomCornersNode.image = cornersImage(top: false, bottom: true, dark: false) self.bottomCornersNode.image = cornersImage(top: false, bottom: true, dark: false)
self.bottomCornersNode.isUserInteractionEnabled = false
self.audioButton = CallControllerButtonItemNode() self.audioButton = CallControllerButtonItemNode()
self.cameraButton = CallControllerButtonItemNode() self.cameraButton = CallControllerButtonItemNode()
@ -1072,12 +1082,12 @@ public final class VoiceChatController: ViewController {
let _ = self?.call.updateMuteState(peerId: peerId, isMuted: isMuted) let _ = self?.call.updateMuteState(peerId: peerId, isMuted: isMuted)
}, pinPeer: { [weak self] peerId, endpointId in }, pinPeer: { [weak self] peerId, endpointId in
if let strongSelf = self { if let strongSelf = self {
if peerId != strongSelf.currentForcedSpeakerWithVideo?.0, let endpointId = endpointId { if peerId != strongSelf.currentForcedSpeakerWithVideo {
strongSelf.currentForcedSpeakerWithVideo = (peerId, endpointId) strongSelf.currentForcedSpeakerWithVideo = peerId
} else { } else {
strongSelf.currentForcedSpeakerWithVideo = nil strongSelf.currentForcedSpeakerWithVideo = nil
} }
strongSelf.updatePinnedParticipant() strongSelf.updatePinnedParticipant(waitForFullSize: false)
var updateLayout = false var updateLayout = false
if strongSelf.effectiveSpeakerWithVideo != nil && !strongSelf.isExpanded { if strongSelf.effectiveSpeakerWithVideo != nil && !strongSelf.isExpanded {
@ -1094,7 +1104,7 @@ public final class VoiceChatController: ViewController {
if let (layout, navigationHeight) = strongSelf.validLayout { if let (layout, navigationHeight) = strongSelf.validLayout {
strongSelf.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: .animated(duration: 0.3, curve: .easeInOut)) 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 strongSelf.animatingExpansion = false
}) })
} }
@ -1411,7 +1421,7 @@ public final class VoiceChatController: ViewController {
for (endpointId, _) in strongSelf.videoNodes { for (endpointId, _) in strongSelf.videoNodes {
if entry.effectiveVideoEndpointId == endpointId { 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) return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Pin"), color: theme.actionSheet.primaryTextColor)
}, action: { _, f in }, action: { _, f in
guard let strongSelf = self else { guard let strongSelf = self else {
@ -1705,8 +1715,6 @@ public final class VoiceChatController: ViewController {
self.topPanelNode.addSubnode(self.closeButton) self.topPanelNode.addSubnode(self.closeButton)
self.topPanelNode.addSubnode(self.topCornersNode) self.topPanelNode.addSubnode(self.topCornersNode)
self.bottomPanelNode.addSubnode(self.bottomCornersNode)
self.bottomPanelNode.addSubnode(self.bottomPanelBackgroundNode)
self.bottomPanelNode.addSubnode(self.audioButton) self.bottomPanelNode.addSubnode(self.audioButton)
if let _ = self.mainVideoContainerNode { if let _ = self.mainVideoContainerNode {
self.bottomPanelNode.addSubnode(self.cameraButton) self.bottomPanelNode.addSubnode(self.cameraButton)
@ -1731,6 +1739,8 @@ public final class VoiceChatController: ViewController {
self.contentContainer.addSubnode(self.leftBorderNode) self.contentContainer.addSubnode(self.leftBorderNode)
self.contentContainer.addSubnode(self.rightBorderNode) self.contentContainer.addSubnode(self.rightBorderNode)
self.contentContainer.addSubnode(self.bottomPanelCoverNode) self.contentContainer.addSubnode(self.bottomPanelCoverNode)
self.contentContainer.addSubnode(self.bottomCornersNode)
self.contentContainer.addSubnode(self.bottomPanelBackgroundNode)
self.contentContainer.addSubnode(self.bottomPanelNode) self.contentContainer.addSubnode(self.bottomPanelNode)
self.contentContainer.addSubnode(self.timerNode) self.contentContainer.addSubnode(self.timerNode)
self.contentContainer.addSubnode(self.scheduleTextNode) self.contentContainer.addSubnode(self.scheduleTextNode)
@ -1887,12 +1897,12 @@ public final class VoiceChatController: ViewController {
} }
} }
if let (peerId, endpointId, _) = maxLevelWithVideo { if let (peerId, _, _) = maxLevelWithVideo {
/*if strongSelf.currentDominantSpeakerWithVideo?.0 != peerId || strongSelf.currentDominantSpeakerWithVideo?.1 != endpointId { if strongSelf.currentDominantSpeakerWithVideo != peerId {
strongSelf.currentDominantSpeakerWithVideo = (peerId, endpointId) strongSelf.currentDominantSpeakerWithVideo = peerId
strongSelf.call.setFullSizeVideo(endpointId: endpointId)
strongSelf.mainVideoContainerNode?.updatePeer(peer: (peerId: peerId, source: endpointId), waitForFullSize: true) strongSelf.updatePinnedParticipant(waitForFullSize: true)
}*/ }
} }
strongSelf.itemInteraction?.updateAudioLevels(levels) strongSelf.itemInteraction?.updateAudioLevels(levels)
@ -1937,7 +1947,7 @@ public final class VoiceChatController: ViewController {
if let strongSelf = self { if let strongSelf = self {
strongSelf.currentContentOffset = offset strongSelf.currentContentOffset = offset
if !strongSelf.animatingExpansion && !strongSelf.animatingInsertion && strongSelf.panGestureArguments == nil && !strongSelf.animatingAppearance { 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 let (peerId, endpointId) = strongSelf.effectiveSpeakerWithVideo {
if !validSources.contains(endpointId) { if !validSources.contains(endpointId) {
if peerId == strongSelf.currentForcedSpeakerWithVideo?.0 { if peerId == strongSelf.currentForcedSpeakerWithVideo {
strongSelf.currentForcedSpeakerWithVideo = nil strongSelf.currentForcedSpeakerWithVideo = nil
} }
if peerId == strongSelf.currentDominantSpeakerWithVideo?.0 { if peerId == strongSelf.currentDominantSpeakerWithVideo {
strongSelf.currentDominantSpeakerWithVideo = nil strongSelf.currentDominantSpeakerWithVideo = nil
} }
strongSelf.updatePinnedParticipant() strongSelf.updatePinnedParticipant(waitForFullSize: false)
} }
} }
@ -2070,7 +2080,7 @@ public final class VoiceChatController: ViewController {
})) }))
self.titleNode.tapped = { [weak self] in self.titleNode.tapped = { [weak self] in
if let strongSelf = self { if let strongSelf = self, !strongSelf.isScheduling {
if strongSelf.callState?.canManageCall ?? false { if strongSelf.callState?.canManageCall ?? false {
strongSelf.openTitleEditing() strongSelf.openTitleEditing()
} else if !strongSelf.titleNode.recordingIconNode.isHidden { } else if !strongSelf.titleNode.recordingIconNode.isHidden {
@ -2152,7 +2162,7 @@ public final class VoiceChatController: ViewController {
if let (layout, navigationHeight) = strongSelf.validLayout { if let (layout, navigationHeight) = strongSelf.validLayout {
strongSelf.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: .animated(duration: 0.3, curve: .easeInOut)) 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 { if let (peerId, _) = minimalVisiblePeerid {
@ -2200,7 +2210,7 @@ public final class VoiceChatController: ViewController {
if let (layout, navigationHeight) = strongSelf.validLayout { if let (layout, navigationHeight) = strongSelf.validLayout {
strongSelf.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: .animated(duration: 0.3, curve: .easeInOut)) 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 { if let (peerId, _) = minimalVisiblePeerid {
@ -2224,7 +2234,7 @@ public final class VoiceChatController: ViewController {
if let (layout, navigationHeight) = strongSelf.validLayout { if let (layout, navigationHeight) = strongSelf.validLayout {
strongSelf.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: .animated(duration: 0.3, curve: .easeInOut)) 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.disableVideo()
self.call.disableScreencast() self.call.disableScreencast()
} else { } else {
#if DEBUG self.call.makeOutgoingVideoView { [weak self] view in
//self.call.requestScreencast() guard let strongSelf = self, let view = view else {
self.call.requestVideo() return
return; }
#endif let cameraNode = GroupVideoNode(videoView: view)
let controller = VoiceChatCameraPreviewController(context: strongSelf.context, cameraNode: cameraNode, shareCamera: { [weak self] videoNode in
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 if let strongSelf = self {
self?.call.requestVideo() strongSelf.call.requestVideo()
}) }
self.controller?.present(controller, in: .window(.root)) }, 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 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 { guard let (layout, _) = self.validLayout else {
return 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 isLandscape = false
var effectiveDisplayMode = self.displayMode var effectiveDisplayMode = self.displayMode
if case .compact = layout.metrics.widthClass, layout.size.width > layout.size.height { 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 let topInset: CGFloat
if let (panInitialTopInset, panOffset) = self.panGestureArguments { if let (panInitialTopInset, panOffset) = self.panGestureArguments {
if self.isExpanded { if self.isExpanded {
@ -3319,9 +3343,8 @@ public final class VoiceChatController: ViewController {
} }
} }
let currentContentOffset = self.currentContentOffset ?? 0.0
let offset = (bottomEdge.isZero ? 0.0 : offset) + topInset let offset = (bottomEdge.isZero ? 0.0 : currentContentOffset) + topInset
self.floatingHeaderOffset = offset
if bottomEdge.isZero { if bottomEdge.isZero {
bottomEdge = self.listNode.frame.minY + 46.0 + 56.0 bottomEdge = self.listNode.frame.minY + 46.0 + 56.0
@ -3331,7 +3354,7 @@ public final class VoiceChatController: ViewController {
let panelOffset = max(layoutTopInset, rawPanelOffset) let panelOffset = max(layoutTopInset, rawPanelOffset)
let topPanelFrame: CGRect let topPanelFrame: CGRect
if isLandscape { 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 { } else {
topPanelFrame = CGRect(origin: CGPoint(x: 0.0, y: panelOffset), size: CGSize(width: size.width, height: topPanelHeight)) 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 leftBorderFrame: CGRect
let rightBorderFrame: CGRect let rightBorderFrame: CGRect
if isLandscape { if isLandscape {
leftBorderFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.safeInsets.left, 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 - layout.safeInsets.right, y: 0.0), size: CGSize(width: layout.safeInsets.right, 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 { } else {
leftBorderFrame = CGRect(origin: CGPoint(x: 0.0, y: topPanelFrame.maxY - 16.0), size: CGSize(width: sideInset, height: layout.size.height)) 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)) 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)) 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 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 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 let previousBottomCornersFrame = self.bottomCornersNode.frame
if !bottomCornersFrame.equalTo(previousBottomCornersFrame) { if !bottomCornersFrame.equalTo(previousBottomCornersFrame) {
self.bottomCornersNode.frame = bottomCornersFrame 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) topEdgeFrame = CGRect(x: 0.0, y: 0.0, width: size.width, height: topPanelHeight)
} }
var isLandscape = false
var effectiveDisplayMode = self.displayMode var effectiveDisplayMode = self.displayMode
if case .compact = layout.metrics.widthClass, layout.size.width > layout.size.height { if case .compact = layout.metrics.widthClass, layout.size.width > layout.size.height {
isLandscape = true
if case .fullscreen = effectiveDisplayMode { if case .fullscreen = effectiveDisplayMode {
} else { } else {
effectiveDisplayMode = .fullscreen(controlsHidden: false) effectiveDisplayMode = .fullscreen(controlsHidden: false)
@ -3510,7 +3535,11 @@ public final class VoiceChatController: ViewController {
let backgroundColor: UIColor let backgroundColor: UIColor
if case .fullscreen = effectiveDisplayMode { if case .fullscreen = effectiveDisplayMode {
backgroundColor = fullscreenBackgroundColor if isLandscape {
backgroundColor = isFullscreen ? panelBackgroundColor : secondaryPanelBackgroundColor
} else {
backgroundColor = fullscreenBackgroundColor
}
} else if self.isScheduling || self.callState?.scheduleTimestamp != nil { } else if self.isScheduling || self.callState?.scheduleTimestamp != nil {
backgroundColor = panelBackgroundColor backgroundColor = panelBackgroundColor
} else { } else {
@ -3556,7 +3585,7 @@ public final class VoiceChatController: ViewController {
} }
private func updateTitle(slide: Bool = false, transition: ContainedViewLayoutTransition) { private func updateTitle(slide: Bool = false, transition: ContainedViewLayoutTransition) {
guard let (layout, _) = self.validLayout else { guard let _ = self.validLayout else {
return return
} }
var title = self.currentTitle var title = self.currentTitle
@ -3583,12 +3612,7 @@ public final class VoiceChatController: ViewController {
} }
} }
var size = layout.size self.titleNode.update(size: CGSize(width: self.titleNode.bounds.width, height: 44.0), title: title, subtitle: subtitle, slide: slide, transition: transition)
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)
} }
private func updateButtons(animated: Bool) { 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 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) 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) self.validLayout = (layout, navigationHeight)
var size = layout.size var size = layout.size
let contentWidth: CGFloat
if case .regular = layout.metrics.widthClass { if case .regular = layout.metrics.widthClass {
size.width = floor(min(size.width, size.height) * 0.5) 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 let isScheduled = self.isScheduling || self.callState?.scheduleTimestamp != nil
@ -3728,7 +3756,7 @@ public final class VoiceChatController: ViewController {
if !self.isFullscreen { if !self.isFullscreen {
self.isExpanded = true self.isExpanded = true
self.updateIsFullscreen(true) self.updateIsFullscreen(true)
self.tileListNode.isHidden = false // self.tileListNode.isHidden = false
} }
if case .fullscreen = effectiveDisplayMode { if case .fullscreen = effectiveDisplayMode {
} else { } 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) 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.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 - 20.0 - 28.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.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)) 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 layoutTopInset: CGFloat = max(layout.statusBarHeight ?? 0.0, layout.safeInsets.top)
let sideInset: CGFloat = 16.0 let sideInset: CGFloat = 16.0
var insets = UIEdgeInsets() var insets = UIEdgeInsets()
insets.left = layout.safeInsets.left + sideInset insets.left = sideInset + (isLandscape ? 0.0 : layout.safeInsets.left)
insets.right = layout.safeInsets.right + sideInset 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 { 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 if let _ = self.panGestureArguments {
} else { } else {
let topEdgeFrame: CGRect let topEdgeFrame: CGRect
if self.isFullscreen { if self.isFullscreen {
let offset: CGFloat topEdgeFrame = CGRect(x: 0.0, y: -topEdgeOffset, width: size.width, height: topPanelHeight + topEdgeOffset)
if let statusBarHeight = layout.statusBarHeight {
offset = statusBarHeight
} else {
offset = 44.0
}
topEdgeFrame = CGRect(x: 0.0, y: -offset, width: size.width, height: topPanelHeight + offset)
} else { } else {
topEdgeFrame = CGRect(x: 0.0, y: 0.0, width: size.width, height: topPanelHeight) 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 listTopInset = layoutTopInset + topPanelHeight
var topCornersY = topPanelHeight var topCornersY = topPanelHeight
if isLandscape { if isLandscape {
listTopInset = 0.0 listTopInset = topPanelHeight
topCornersY = -50.0 // topCornersY = -50.0
} else if self.mainVideoContainerNode != nil && self.isFullscreen { } else if self.mainVideoContainerNode != nil && self.isFullscreen {
let videoContainerHeight = min(mainVideoHeight, layout.size.width) let videoContainerHeight = min(mainVideoHeight, layout.size.width)
listTopInset += videoContainerHeight listTopInset += videoContainerHeight
topCornersY += 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 let topInset: CGFloat
if let (panInitialTopInset, panOffset) = self.panGestureArguments { if let (panInitialTopInset, panOffset) = self.panGestureArguments {
if self.isExpanded { if self.isExpanded {
@ -3812,7 +3843,7 @@ public final class VoiceChatController: ViewController {
topInset = listSize.height - 46.0 - floor(56.0 * 3.5) 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) 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 }) 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.transform = tileListTransform
self.tileListNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: nil, updateSizeAndInsets: tileListUpdateSizeAndInsets, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) 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)) 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 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)) let bottomPanelCoverFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - bottomPanelCoverHeight), size: CGSize(width: size.width, height: bottomPanelCoverHeight))
if isLandscape { if isLandscape {
transition.updateAlpha(node: self.closeButton, alpha: 0.0) // transition.updateAlpha(node: self.closeButton, alpha: 0.0)
transition.updateAlpha(node: self.optionsButton, alpha: 0.0) // transition.updateAlpha(node: self.optionsButton, alpha: 0.0)
transition.updateAlpha(node: self.titleNode, 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)) 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 { } else {
transition.updateAlpha(node: self.closeButton, 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.optionsButton, alpha: self.optionsButton.isUserInteractionEnabled ? 1.0 : 0.0)
transition.updateAlpha(node: self.titleNode, alpha: 1.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.bottomPanelCoverNode, frame: bottomPanelCoverFrame)
transition.updateFrame(node: self.bottomPanelNode, frame: bottomPanelFrame) transition.updateFrame(node: self.bottomPanelNode, frame: bottomPanelFrame)
if let pickerView = self.pickerView { 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)) 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) 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) transition.updateFrame(node: self.timerNode, frame: timerFrame)
self.timerNode.update(size: timerFrame.size, scheduleTime: self.callState?.scheduleTimestamp, transition: .immediate) self.timerNode.update(size: timerFrame.size, scheduleTime: self.callState?.scheduleTimestamp, transition: .immediate)
@ -3905,16 +3937,26 @@ public final class VoiceChatController: ViewController {
forthButtonFrame = rightButtonFrame forthButtonFrame = rightButtonFrame
case let .fullscreen(controlsHidden): case let .fullscreen(controlsHidden):
smallButtons = true smallButtons = true
let sideInset: CGFloat = 26.0
if isLandscape { 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) 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) forthButtonFrame = CGRect(origin: CGPoint(x: x, y: sideInset), size: sideButtonSize)
let thirdButtonPreFrame = CGRect(origin: CGPoint(x: x, y: sideInset + sideButtonSize.height + spacing), 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) 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) firstButtonFrame = CGRect(origin: CGPoint(x: x, y: layout.size.height - sideInset - sideButtonSize.height), size: sideButtonSize)
} else { } else {
let sideInset: CGFloat = 26.0
let spacing = floor((layout.size.width - sideInset * 2.0 - sideButtonSize.width * 4.0) / 3.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) 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) 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 { guard let (layout, navigationHeight) = self.validLayout else {
return return
} }
self.updateFloatingHeaderOffset(offset: 0.0, transition: .immediate) self.updateDecorationsLayout(transition: .immediate)
self.animatingAppearance = true self.animatingAppearance = true
@ -4172,9 +4214,7 @@ public final class VoiceChatController: ViewController {
itemsCount -= 1 itemsCount -= 1
} }
itemsHeight += CGFloat(itemsCount) * 56.0 itemsHeight += CGFloat(itemsCount) * 56.0
let layoutTopInset: CGFloat = max(layout.statusBarHeight ?? 0.0, layout.safeInsets.top)
let sideInset: CGFloat = 16.0 let sideInset: CGFloat = 16.0
var insets = UIEdgeInsets() var insets = UIEdgeInsets()
insets.left = layout.safeInsets.left + sideInset 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) 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 listTopInset = layoutTopInset + topPanelHeight
let listSize = CGSize(width: size.width, height: layout.size.height - listTopInset - bottomPanelHeight) let listSize = CGSize(width: size.width, height: layout.size.height - listTopInset - bottomPanelHeight)
@ -4207,9 +4248,9 @@ public final class VoiceChatController: ViewController {
return return
} }
if isFirstTime { if isFirstTime {
strongSelf.updateFloatingHeaderOffset(offset: strongSelf.currentContentOffset ?? 0.0, transition: .immediate) strongSelf.updateDecorationsLayout(transition: .immediate)
} else if strongSelf.animatingInsertion { } 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 strongSelf.animatingInsertion = false
if !strongSelf.didSetContentsReady { 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)) 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? var pinnedEntry: ListEntry?
for member in callMembers.0 { for member in callMembers.0 {
if processedPeerIds.contains(member.peer.id) { if processedPeerIds.contains(member.peer.id) {
continue continue
@ -4339,7 +4375,8 @@ public final class VoiceChatController: ViewController {
isMyPeer: self.callState?.myPeerId == member.peer.id, isMyPeer: self.callState?.myPeerId == member.peer.id,
ssrc: member.ssrc, ssrc: member.ssrc,
effectiveVideoEndpointId: member.presentationEndpointId ?? member.videoEndpointId, effectiveVideoEndpointId: member.presentationEndpointId ?? member.videoEndpointId,
presence: nil, hasVideo: member.videoEndpointId != nil,
hasScreencast: member.presentationEndpointId != nil,
activityTimestamp: Int32.max - 1 - index, activityTimestamp: Int32.max - 1 - index,
state: memberState, state: memberState,
muteState: memberMuteState, muteState: memberMuteState,
@ -4359,7 +4396,8 @@ public final class VoiceChatController: ViewController {
isMyPeer: self.callState?.myPeerId == member.peer.id, isMyPeer: self.callState?.myPeerId == member.peer.id,
ssrc: member.ssrc, ssrc: member.ssrc,
effectiveVideoEndpointId: member.presentationEndpointId ?? member.videoEndpointId, effectiveVideoEndpointId: member.presentationEndpointId ?? member.videoEndpointId,
presence: nil, hasVideo: member.videoEndpointId != nil,
hasScreencast: member.presentationEndpointId != nil,
activityTimestamp: Int32.max - 1 - index, activityTimestamp: Int32.max - 1 - index,
state: memberState, state: memberState,
muteState: memberMuteState, muteState: memberMuteState,
@ -4377,11 +4415,12 @@ public final class VoiceChatController: ViewController {
if memberPeer.id == self.effectiveSpeakerWithVideo?.0 { if memberPeer.id == self.effectiveSpeakerWithVideo?.0 {
pinnedEntry = .peer(PeerEntry( pinnedEntry = .peer(PeerEntry(
peer: memberPeer, peer: memberPeer,
about: member.about, about: nil,
isMyPeer: self.callState?.myPeerId == member.peer.id, isMyPeer: self.callState?.myPeerId == member.peer.id,
ssrc: member.ssrc, ssrc: member.ssrc,
effectiveVideoEndpointId: member.presentationEndpointId ?? member.videoEndpointId, effectiveVideoEndpointId: member.presentationEndpointId ?? member.videoEndpointId,
presence: nil, hasVideo: false,
hasScreencast: false,
activityTimestamp: Int32.max - 1 - index, activityTimestamp: Int32.max - 1 - index,
state: memberState, state: memberState,
muteState: memberMuteState, muteState: memberMuteState,
@ -4408,7 +4447,8 @@ public final class VoiceChatController: ViewController {
isMyPeer: false, isMyPeer: false,
ssrc: nil, ssrc: nil,
effectiveVideoEndpointId: nil, effectiveVideoEndpointId: nil,
presence: nil, hasVideo: false,
hasScreencast: false,
activityTimestamp: Int32.max - 1 - index, activityTimestamp: Int32.max - 1 - index,
state: .invited, state: .invited,
muteState: nil, muteState: nil,
@ -4467,20 +4507,20 @@ public final class VoiceChatController: ViewController {
self.enqueueTileTransition(tileTransition) self.enqueueTileTransition(tileTransition)
} }
private func updatePinnedParticipant() { private func updatePinnedParticipant(waitForFullSize: Bool) {
let effectivePinnedParticipant = self.currentForcedSpeakerWithVideo ?? self.currentDominantSpeakerWithVideo 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 return
} }
if let (peerId, _) = effectivePinnedParticipant { if let peerId = effectivePinnedParticipant {
for entry in self.currentEntries { for entry in self.currentEntries {
switch entry { switch entry {
case let .peer(peer): case let .peer(peer):
if peer.peer.id == peerId, let endpointId = peer.effectiveVideoEndpointId { if peer.peer.id == peerId, let endpointId = peer.effectiveVideoEndpointId {
self.effectiveSpeakerWithVideo = (peerId, endpointId) self.effectiveSpeakerWithVideo = (peerId, endpointId)
self.call.setFullSizeVideo(endpointId: 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: default:
break break
@ -4577,7 +4617,7 @@ public final class VoiceChatController: ViewController {
if let (layout, navigationHeight) = self.validLayout { if let (layout, navigationHeight) = self.validLayout {
self.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: .immediate) self.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: .immediate)
self.updateFloatingHeaderOffset(offset: self.currentContentOffset ?? 0.0, transition: .immediate) self.updateDecorationsLayout(transition: .immediate)
} }
if !self.isExpanded { if !self.isExpanded {
@ -4625,7 +4665,7 @@ public final class VoiceChatController: ViewController {
if let (layout, navigationHeight) = self.validLayout { if let (layout, navigationHeight) = self.validLayout {
self.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: .animated(duration: 0.3, curve: .easeInOut)) 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 self.animatingExpansion = false
}) })
} else { } else {
@ -4635,7 +4675,7 @@ public final class VoiceChatController: ViewController {
if let (layout, navigationHeight) = self.validLayout { if let (layout, navigationHeight) = self.validLayout {
self.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: .animated(duration: 0.3, curve: .easeInOut)) 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 self.animatingExpansion = false
}) })
} }
@ -4663,7 +4703,7 @@ public final class VoiceChatController: ViewController {
if let (layout, navigationHeight) = self.validLayout { if let (layout, navigationHeight) = self.validLayout {
self.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: .animated(duration: 0.3, curve: .easeInOut)) 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 self.animatingExpansion = false
}) })
} else if !isScheduling { } else if !isScheduling {
@ -4674,7 +4714,7 @@ public final class VoiceChatController: ViewController {
if let (layout, navigationHeight) = self.validLayout { if let (layout, navigationHeight) = self.validLayout {
self.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: .animated(duration: 0.3, curve: .easeInOut)) 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 self.animatingExpansion = false
}) })
} }
@ -4698,7 +4738,7 @@ public final class VoiceChatController: ViewController {
if let (layout, navigationHeight) = self.validLayout { if let (layout, navigationHeight) = self.validLayout {
self.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: .animated(duration: 0.3, curve: .easeInOut)) 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 self.animatingExpansion = false
}) })
default: default:

View File

@ -25,10 +25,21 @@ final class VoiceChatParticipantItem: ListViewItem {
case tile(isLandscape: Bool) case tile(isLandscape: Bool)
} }
enum ParticipantText { enum ParticipantText: Equatable {
public enum Icon { public struct TextIcon: OptionSet {
case volume public var rawValue: Int32
case video
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 { public enum TextColor {
@ -39,7 +50,7 @@ final class VoiceChatParticipantItem: ListViewItem {
} }
case presence case presence
case text(String, Icon?, TextColor) case text(String, TextIcon, TextColor)
case none case none
} }
@ -179,7 +190,7 @@ private let borderImage = generateImage(CGSize(width: tileSize.width, height: ti
context.clear(bounds) context.clear(bounds)
context.setLineWidth(borderLineWidth) 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.addPath(UIBezierPath(roundedRect: bounds.insetBy(dx: (borderLineWidth - UIScreenPixel) / 2.0, dy: (borderLineWidth - UIScreenPixel) / 2.0), cornerRadius: backgroundCornerRadius - UIScreenPixel).cgPath)
context.strokePath() context.strokePath()
@ -196,23 +207,109 @@ private let fadeImage = generateImage(CGSize(width: 1.0, height: 30.0), rotatedC
}) })
private class VoiceChatParticipantStatusNode: ASDisplayNode { private class VoiceChatParticipantStatusNode: ASDisplayNode {
private let iconNode: ASImageNode private var iconNodes: [ASImageNode]
private let textNode: TextNode private let textNode: TextNode
private var currentParams: (CGSize, VoiceChatParticipantItem.ParticipantText)?
override init() { override init() {
self.iconNode = ASImageNode() self.iconNodes = []
self.iconNode.displaysAsynchronously = false
self.textNode = TextNode() self.textNode = TextNode()
self.textNode.isUserInteractionEnabled = false
self.textNode.contentMode = .left
self.textNode.contentsScale = UIScreen.main.scale
super.init() super.init()
self.addSubnode(self.iconNode)
self.addSubnode(self.textNode) 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 pinIconNode: ASImageNode
private let contentWrapperNode: ASDisplayNode private let contentWrapperNode: ASDisplayNode
private let titleNode: TextNode private let titleNode: TextNode
private let statusIconNode: ASImageNode private let statusNode: VoiceChatParticipantStatusNode
private let statusNode: TextNode private let expandedStatusNode: VoiceChatParticipantStatusNode
private let expandedStatusNode: TextNode
private var credibilityIconNode: ASImageNode? private var credibilityIconNode: ASImageNode?
private var avatarTransitionNode: ASImageNode? private var avatarTransitionNode: ASImageNode?
@ -332,18 +428,11 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
self.titleNode.contentMode = .left self.titleNode.contentMode = .left
self.titleNode.contentsScale = UIScreen.main.scale self.titleNode.contentsScale = UIScreen.main.scale
self.statusIconNode = ASImageNode() self.statusNode = VoiceChatParticipantStatusNode()
self.statusIconNode.displaysAsynchronously = false
self.statusNode = TextNode()
self.statusNode.isUserInteractionEnabled = false 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.isUserInteractionEnabled = false
self.expandedStatusNode.contentMode = .left
self.expandedStatusNode.contentsScale = UIScreen.main.scale
self.expandedStatusNode.alpha = 0.0 self.expandedStatusNode.alpha = 0.0
self.actionContainerNode = ASDisplayNode() self.actionContainerNode = ASDisplayNode()
@ -366,7 +455,6 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
self.offsetContainerNode.addSubnode(self.videoContainerNode) self.offsetContainerNode.addSubnode(self.videoContainerNode)
self.offsetContainerNode.addSubnode(self.contentWrapperNode) self.offsetContainerNode.addSubnode(self.contentWrapperNode)
self.contentWrapperNode.addSubnode(self.titleNode) self.contentWrapperNode.addSubnode(self.titleNode)
self.contentWrapperNode.addSubnode(self.statusIconNode)
self.contentWrapperNode.addSubnode(self.statusNode) self.contentWrapperNode.addSubnode(self.statusNode)
self.contentWrapperNode.addSubnode(self.expandedStatusNode) self.contentWrapperNode.addSubnode(self.expandedStatusNode)
self.contentWrapperNode.addSubnode(self.actionContainerNode) 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) { func asyncLayout() -> (_ item: VoiceChatParticipantItem, _ params: ListViewItemLayoutParams, _ first: Bool, _ last: Bool) -> (ListViewItemNodeLayout, (Bool, Bool) -> Void) {
let makeTitleLayout = TextNode.asyncLayout(self.titleNode) let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
let makeStatusLayout = TextNode.asyncLayout(self.statusNode) let makeStatusLayout = self.statusNode.asyncLayout()
let makeExpandedStatusLayout = TextNode.asyncLayout(self.expandedStatusNode) let makeExpandedStatusLayout = self.expandedStatusNode.asyncLayout()
var currentDisabledOverlayNode = self.disabledOverlayNode var currentDisabledOverlayNode = self.disabledOverlayNode
let currentItem = self.layoutParams?.0 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) var titleFont = item.style == .list ? Font.regular(17.0) : Font.regular(12.0)
let statusFont = Font.regular(14.0)
var titleAttributedString: NSAttributedString? var titleAttributedString: NSAttributedString?
var statusAttributedString: NSAttributedString?
var expandedStatusAttributedString: NSAttributedString?
let rightInset: CGFloat = params.rightInset let rightInset: CGFloat = params.rightInset
@ -960,60 +1045,15 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
} }
var wavesColor = UIColor(rgb: 0x34c759) var wavesColor = UIColor(rgb: 0x34c759)
switch item.text { if case let .text(_, _, textColor) = 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
switch textColor { switch textColor {
case .generic: case .accent:
textColorValue = item.presentationData.theme.list.itemSecondaryTextColor wavesColor = accentColor
case .accent: case .destructive:
textColorValue = item.presentationData.theme.list.itemAccentColor wavesColor = destructiveColor
case .constructive: default:
textColorValue = constructiveColor break
case .destructive:
textColorValue = destructiveColor
} }
expandedStatusAttributedString = NSAttributedString(string: text, font: statusFont, textColor: textColorValue)
} else {
expandedStatusAttributedString = statusAttributedString
} }
let leftInset: CGFloat = 58.0 + params.leftInset let leftInset: CGFloat = 58.0 + params.leftInset
@ -1051,13 +1091,14 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
constrainedWidth = params.width - 24.0 - 10.0 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 (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 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 contentSize: CGSize
let insets: UIEdgeInsets let insets: UIEdgeInsets
@ -1122,6 +1163,7 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
return (layout, { [weak self] synchronousLoad, animated in return (layout, { [weak self] synchronousLoad, animated in
if let strongSelf = self { if let strongSelf = self {
var hadItem = strongSelf.layoutParams?.0 != nil
strongSelf.layoutParams = (item, params, first, last) strongSelf.layoutParams = (item, params, first, last)
strongSelf.currentTitle = titleAttributedString?.string strongSelf.currentTitle = titleAttributedString?.string
strongSelf.wavesColor = wavesColor 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 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 var extractedVerticalOffset: CGFloat = 0.0
if item.peer.smallProfileImage != nil || strongSelf.videoNode != nil { if item.peer.smallProfileImage != nil || strongSelf.videoNode != nil {
extractedVerticalOffset = extractedRect.width extractedVerticalOffset = extractedRect.width
@ -1209,9 +1251,9 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
strongSelf.accessibilityLabel = titleAttributedString?.string strongSelf.accessibilityLabel = titleAttributedString?.string
var combinedValueString = "" var combinedValueString = ""
if let statusString = statusAttributedString?.string, !statusString.isEmpty { // if let statusString = statusAttributedString?.string, !statusString.isEmpty {
combinedValueString.append(statusString) // combinedValueString.append(statusString)
} // }
strongSelf.accessibilityValue = combinedValueString strongSelf.accessibilityValue = combinedValueString
@ -1222,8 +1264,8 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
} }
let transition: ContainedViewLayoutTransition let transition: ContainedViewLayoutTransition
if animated { if animated && hadItem {
transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring) transition = ContainedViewLayoutTransition.animated(duration: 0.3, curve: .easeInOut)
} else { } else {
transition = .immediate 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.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.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.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.size)) transition.updateFrame(node: strongSelf.expandedStatusNode, frame: CGRect(origin: CGPoint(x: leftInset, y: strongSelf.titleNode.frame.maxY + titleSpacing), size: expandedStatusLayout))
if let currentCredibilityIconImage = currentCredibilityIconImage { if let currentCredibilityIconImage = currentCredibilityIconImage {
let iconNode: ASImageNode 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) { func update(size: CGSize, title: String, subtitle: String, slide: Bool, transition: ContainedViewLayoutTransition) {
guard !size.width.isZero else {
return
}
var titleUpdated = false var titleUpdated = false
if let previousTitle = self.titleNode.attributedText?.string { if let previousTitle = self.titleNode.attributedText?.string {
titleUpdated = previousTitle != title titleUpdated = previousTitle != title

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

View File

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

View File

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

View File

@ -1,7 +1,7 @@
{ {
"images" : [ "images" : [
{ {
"filename" : "volsmall.pdf", "filename" : "ic_voicecamera.pdf",
"idiom" : "universal" "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 engine: TelegramEngine
public let fetchManager: FetchManager public let fetchManager: FetchManager
private let prefetchManager: PrefetchManager? public let prefetchManager: PrefetchManager?
public var keyShortcutsController: KeyShortcutsController? public var keyShortcutsController: KeyShortcutsController?
@ -172,7 +172,7 @@ public final class AccountContextImpl: AccountContext {
} }
self.fetchManager = FetchManagerImpl(postbox: account.postbox, storeManager: self.downloadedMediaStoreManager) self.fetchManager = FetchManagerImpl(postbox: account.postbox, storeManager: self.downloadedMediaStoreManager)
if sharedContext.applicationBindings.isMainApp && !temp { 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.wallpaperUploadManager = WallpaperUploadManagerImpl(sharedContext: sharedContext, account: account, presentationData: sharedContext.presentationData)
self.themeUpdateManager = ThemeUpdateManagerImpl(sharedContext: sharedContext, account: account) self.themeUpdateManager = ThemeUpdateManagerImpl(sharedContext: sharedContext, account: account)
} else { } else {

View File

@ -418,7 +418,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
private var importStateDisposable: Disposable? 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 let _ = ChatControllerCount.modify { value in
return value + 1 return value + 1
} }
@ -463,7 +463,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
self.stickerSettings = ChatInterfaceStickerSettings(loopAnimatedStickers: false) 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) self.presentationInterfaceStatePromise = ValuePromise(self.presentationInterfaceState)
var mediaAccessoryPanelVisibility = MediaAccessoryPanelVisibility.none var mediaAccessoryPanelVisibility = MediaAccessoryPanelVisibility.none

View File

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

View File

@ -71,7 +71,7 @@ public func navigateToChatControllerImpl(_ params: NavigateToChatControllerParam
}) })
} }
} else { } 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 controller.purposefulAction = params.purposefulAction
if let search = params.activateMessageSearch { if let search = params.activateMessageSearch {

View File

@ -1532,9 +1532,6 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
private var groupMembersSearchContext: GroupMembersSearchContext? private var groupMembersSearchContext: GroupMembersSearchContext?
private let preloadedSticker = Promise<TelegramMediaFile?>(nil)
private let preloadStickerDisposable = MetaDisposable()
private let displayAsPeersPromise = Promise<[FoundPeer]>([]) private let displayAsPeersPromise = Promise<[FoundPeer]>([])
fileprivate let accountsAndPeers = Promise<[(Account, Peer, Int32)]>() fileprivate let accountsAndPeers = Promise<[(Account, Peer, Int32)]>()
@ -2838,24 +2835,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
if let _ = nearbyPeerDistance { if let _ = nearbyPeerDistance {
self.preloadHistoryDisposable.set(self.context.account.addAdditionalPreloadHistoryPeerId(peerId: peerId)) self.preloadHistoryDisposable.set(self.context.account.addAdditionalPreloadHistoryPeerId(peerId: peerId))
self.preloadedSticker.set(.single(nil) self.context.prefetchManager?.prepareNextGreetingSticker()
|> 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())
} }
} }
@ -2870,7 +2850,6 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
self.selectAddMemberDisposable.dispose() self.selectAddMemberDisposable.dispose()
self.addMemberDisposable.dispose() self.addMemberDisposable.dispose()
self.preloadHistoryDisposable.dispose() self.preloadHistoryDisposable.dispose()
self.preloadStickerDisposable.dispose()
self.resolvePeerByNameDisposable?.dispose() self.resolvePeerByNameDisposable?.dispose()
self.navigationActionDisposable.dispose() self.navigationActionDisposable.dispose()
self.enqueueMediaMessageDisposable.dispose() self.enqueueMediaMessageDisposable.dispose()
@ -3356,24 +3335,18 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
switch key { switch key {
case .message: case .message:
if let navigationController = controller.navigationController as? NavigationController { if let navigationController = controller.navigationController as? NavigationController {
let _ = (self.preloadedSticker.get() 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
|> take(1) if let strongSelf = self, strongSelf.nearbyPeerDistance != nil {
|> deliverOnMainQueue).start(next: { [weak self] sticker in var viewControllers = navigationController.viewControllers
if let strongSelf = self { viewControllers = viewControllers.filter { controller in
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 controller is PeerInfoScreen {
if strongSelf.nearbyPeerDistance != nil { return false
var viewControllers = navigationController.viewControllers
viewControllers = viewControllers.filter { controller in
if controller is PeerInfoScreen {
return false
}
return true
}
navigationController.setViewControllers(viewControllers, animated: false)
} }
})) return true
}
navigationController.setViewControllers(viewControllers, animated: false)
} }
}) }))
} }
case .discussion: case .discussion:
if let cachedData = self.data?.cachedData as? CachedChannelData, case let .known(maybeLinkedDiscussionPeerId) = cachedData.linkedDiscussionPeerId, let linkedDiscussionPeerId = maybeLinkedDiscussionPeerId { 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() { private func openChatWithMessageSearch() {
if let navigationController = (self.controller?.navigationController as? NavigationController) { if let navigationController = (self.controller?.navigationController as? NavigationController) {
let _ = (self.preloadedSticker.get() 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
|> take(1) if let strongSelf = self, strongSelf.nearbyPeerDistance != nil {
|> deliverOnMainQueue).start(next: { [weak self] sticker in var viewControllers = navigationController.viewControllers
if let strongSelf = self { viewControllers = viewControllers.filter { controller in
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 controller is PeerInfoScreen {
if strongSelf.nearbyPeerDistance != nil { return false
var viewControllers = navigationController.viewControllers
viewControllers = viewControllers.filter { controller in
if controller is PeerInfoScreen {
return false
}
return true
}
navigationController.setViewControllers(viewControllers, animated: false)
} }
})) return true
}
navigationController.setViewControllers(viewControllers, animated: false)
} }
}) }))
} }
} }
@ -4233,24 +4200,18 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
private func openChat() { private func openChat() {
if let navigationController = self.controller?.navigationController as? NavigationController { if let navigationController = self.controller?.navigationController as? NavigationController {
let _ = (self.preloadedSticker.get() 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
|> take(1) if let strongSelf = self, strongSelf.nearbyPeerDistance != nil {
|> deliverOnMainQueue).start(next: { [weak self] sticker in var viewControllers = navigationController.viewControllers
if let strongSelf = self { viewControllers = viewControllers.filter { controller in
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 controller is PeerInfoScreen {
if strongSelf.nearbyPeerDistance != nil { return false
var viewControllers = navigationController.viewControllers
viewControllers = viewControllers.filter { controller in
if controller is PeerInfoScreen {
return false
}
return true
}
navigationController.setViewControllers(viewControllers, animated: false)
} }
})) return true
}
navigationController.setViewControllers(viewControllers, animated: false)
} }
}) }))
} }
} }

View File

@ -6,6 +6,7 @@ import SyncCore
import TelegramUIPreferences import TelegramUIPreferences
import AccountContext import AccountContext
import PhotoResources import PhotoResources
import StickerResources
import Emoji import Emoji
import UniversalMediaPlayer import UniversalMediaPlayer
@ -21,18 +22,23 @@ public enum PrefetchMediaItem {
case animatedEmojiSticker(TelegramMediaFile) case animatedEmojiSticker(TelegramMediaFile)
} }
private final class PrefetchManagerImpl { private final class PrefetchManagerInnerImpl {
private let queue: Queue private let queue: Queue
private let account: Account private let account: Account
private let engine: TelegramEngine
private let fetchManager: FetchManager private let fetchManager: FetchManager
private var listDisposable: Disposable? private var listDisposable: Disposable?
private var contexts: [MediaId: PrefetchMediaContext] = [:] 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.queue = queue
self.account = account self.account = account
self.engine = engine
self.fetchManager = fetchManager self.fetchManager = fetchManager
let networkType = account.networkType 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 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() let queue = Queue.mainQueue()
self.queue = queue self.queue = queue
self.impl = QueueLocalObject(queue: queue, generate: { 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