mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-08-02 00:17:02 +00:00
Merge commit '34ec6b26174789e5f12bc6a32b2e0faaf140db75'
This commit is contained in:
commit
ffc9deba9f
@ -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";
|
||||||
|
@ -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 }
|
||||||
|
@ -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()
|
||||||
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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,11 +610,7 @@ 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)
|
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] greetingSticker in
|
|
||||||
if let strongSelf = self {
|
|
||||||
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peer.id), activateInput: activateInput && !peer.isDeleted, scrollToEndIfExists: scrollToEndIfExists, greetingData: greetingSticker.flatMap({ ChatGreetingData(sticker: $0) }), animated: !scrollToEndIfExists, options: strongSelf.groupId == PeerGroupId.root ? [.removeOnMasterDetails] : [], parentGroupId: strongSelf.groupId, completion: { [weak self] controller in
|
|
||||||
self?.chatListDisplayNode.containerNode.currentItemNode.clearHighlightAnimated(true)
|
self?.chatListDisplayNode.containerNode.currentItemNode.clearHighlightAnimated(true)
|
||||||
if let promoInfo = promoInfo {
|
if let promoInfo = promoInfo {
|
||||||
switch promoInfo {
|
switch promoInfo {
|
||||||
@ -654,12 +647,6 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
|
||||||
if activateInput {
|
|
||||||
strongSelf.prepareRandomGreetingSticker()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -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()
|
||||||
}
|
}
|
||||||
|
@ -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()
|
|
||||||
|> take(1)
|
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] greetingSticker in
|
|
||||||
if let strongSelf = self {
|
|
||||||
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peer.id), purposefulAction: { [weak self] in
|
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peer.id), purposefulAction: { [weak self] in
|
||||||
if fromSearch {
|
if fromSearch {
|
||||||
self?.deactivateSearch(animated: false)
|
self?.deactivateSearch(animated: false)
|
||||||
self?.switchToChatsController?()
|
self?.switchToChatsController?()
|
||||||
}
|
}
|
||||||
}, scrollToEndIfExists: scrollToEndIfExists, greetingData: greetingSticker.flatMap({ ChatGreetingData(sticker: $0) }), options: [.removeOnMasterDetails], completion: { [weak self] _ in
|
}, scrollToEndIfExists: scrollToEndIfExists, options: [.removeOnMasterDetails], completion: { [weak self] _ in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.contactsNode.contactListNode.listNode.clearHighlightAnimated(true)
|
strongSelf.contactsNode.contactListNode.listNode.clearHighlightAnimated(true)
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
|
||||||
strongSelf.prepareRandomGreetingSticker()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
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())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
@ -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:
|
||||||
|
@ -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()
|
||||||
|
@ -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
|
|
||||||
self.text = text
|
|
||||||
|
|
||||||
self.backgroundNode = ASDisplayNode()
|
private var animatedIn = false
|
||||||
self.backgroundNode.clipsToBounds = true
|
|
||||||
self.backgroundNode.backgroundColor = UIColor(rgb: 0x161619)
|
|
||||||
|
|
||||||
self.maskNode = ASImageNode()
|
private let cameraNode: GroupVideoNode
|
||||||
self.maskNode.displaysAsynchronously = false
|
private let shareCamera: (ASDisplayNode) -> Void
|
||||||
self.maskNode.contentMode = .scaleToFill
|
private let switchCamera: () -> Void
|
||||||
self.maskNode.image = generateImage(CGSize(width: 300.0, height: 400.0), rotatedContext: { size, context in
|
private let shareScreen: () -> Void
|
||||||
let bounds = CGRect(origin: CGPoint(), size: size)
|
|
||||||
context.clear(bounds)
|
|
||||||
|
|
||||||
context.setFillColor(UIColor(rgb: 0x000000, alpha: 0.3).cgColor)
|
private var presentationDataDisposable: Disposable?
|
||||||
context.fill(bounds)
|
|
||||||
|
|
||||||
context.setBlendMode(.clear)
|
init(context: AccountContext, cameraNode: GroupVideoNode, shareCamera: @escaping (ASDisplayNode) -> Void, switchCamera: @escaping () -> Void, shareScreen: @escaping () -> Void) {
|
||||||
context.fillEllipse(in: CGRect(x: 27.0, y: 37.0, width: 246.0, height: 300.0))
|
self.context = context
|
||||||
|
self.cameraNode = cameraNode
|
||||||
|
self.shareCamera = shareCamera
|
||||||
|
self.switchCamera = switchCamera
|
||||||
|
self.shareScreen = shareScreen
|
||||||
|
|
||||||
|
super.init(navigationBarPresentationData: nil)
|
||||||
|
|
||||||
|
self.statusBar.statusBarStyle = .Ignore
|
||||||
|
|
||||||
|
self.blocksBackgroundWhenInOverlay = true
|
||||||
|
|
||||||
|
self.presentationDataDisposable = (context.sharedContext.presentationData
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak self] presentationData in
|
||||||
|
if let strongSelf = self {
|
||||||
|
strongSelf.controllerNode.updatePresentationData(presentationData)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
self.titleNode = ASTextNode()
|
self.statusBar.statusBarStyle = .Ignore
|
||||||
self.titleNode.maximumNumberOfLines = 2
|
|
||||||
self.textNode = ASTextNode()
|
|
||||||
self.textNode.maximumNumberOfLines = 8
|
|
||||||
|
|
||||||
self.actionNodesSeparator = ASDisplayNode()
|
|
||||||
self.actionNodesSeparator.isLayerBacked = true
|
|
||||||
|
|
||||||
self.actionNodes = actions.map { action -> TextAlertContentActionNode in
|
|
||||||
return TextAlertContentActionNode(theme: theme, action: action)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var actionVerticalSeparators: [ASDisplayNode] = []
|
required init(coder aDecoder: NSCoder) {
|
||||||
if actions.count > 1 {
|
fatalError("init(coder:) has not been implemented")
|
||||||
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 {
|
||||||
self.actionNodesSeparator.backgroundColor = theme.separatorColor
|
strongSelf.shareCamera(strongSelf.cameraNode)
|
||||||
for actionNode in self.actionNodes {
|
strongSelf.dismiss()
|
||||||
actionNode.updateTheme(theme)
|
|
||||||
}
|
}
|
||||||
for separatorNode in self.actionVerticalSeparators {
|
|
||||||
separatorNode.backgroundColor = theme.separatorColor
|
|
||||||
}
|
}
|
||||||
|
self.controllerNode.switchCamera = { [weak self] in
|
||||||
if let size = self.validLayout {
|
self?.switchCamera()
|
||||||
_ = self.updateLayout(size: size, transition: .immediate)
|
}
|
||||||
|
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 func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
|
override public func loadView() {
|
||||||
var size = size
|
super.loadView()
|
||||||
size.width = min(size.width, 300.0)
|
|
||||||
let measureSize = CGSize(width: size.width - 16.0 * 2.0, height: CGFloat.greatestFiniteMagnitude)
|
|
||||||
|
|
||||||
let hadValidLayout = self.validLayout != nil
|
|
||||||
self.validLayout = size
|
|
||||||
|
|
||||||
var origin: CGPoint = CGPoint(x: 0.0, y: 18.0)
|
|
||||||
let spacing: CGFloat = 3.0
|
|
||||||
|
|
||||||
let videoHeight: CGFloat = 400.0
|
|
||||||
origin.y += videoHeight
|
|
||||||
|
|
||||||
let titleSize = self.titleNode.measure(measureSize)
|
|
||||||
transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - titleSize.width) / 2.0), y: origin.y), size: titleSize))
|
|
||||||
origin.y += titleSize.height + 4.0
|
|
||||||
|
|
||||||
let textSize = self.textNode.measure(measureSize)
|
|
||||||
transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: origin.y), size: textSize))
|
|
||||||
origin.y += textSize.height + 6.0
|
|
||||||
|
|
||||||
let actionButtonHeight: CGFloat = 44.0
|
|
||||||
var minActionsWidth: CGFloat = 0.0
|
|
||||||
let maxActionWidth: CGFloat = floor(size.width / CGFloat(self.actionNodes.count))
|
|
||||||
let actionTitleInsets: CGFloat = 8.0
|
|
||||||
|
|
||||||
var effectiveActionLayout = TextAlertContentActionLayout.horizontal
|
|
||||||
for actionNode in self.actionNodes {
|
|
||||||
let actionTitleSize = actionNode.titleNode.updateLayout(CGSize(width: maxActionWidth, height: actionButtonHeight))
|
|
||||||
if case .horizontal = effectiveActionLayout, actionTitleSize.height > actionButtonHeight * 0.6667 {
|
|
||||||
effectiveActionLayout = .vertical
|
|
||||||
}
|
}
|
||||||
switch effectiveActionLayout {
|
|
||||||
case .horizontal:
|
override public func viewDidAppear(_ animated: Bool) {
|
||||||
minActionsWidth += actionTitleSize.width + actionTitleInsets
|
super.viewDidAppear(animated)
|
||||||
case .vertical:
|
|
||||||
minActionsWidth = max(minActionsWidth, actionTitleSize.width + actionTitleInsets)
|
if !self.animatedIn {
|
||||||
|
self.animatedIn = true
|
||||||
|
self.controllerNode.animateIn()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let insets = UIEdgeInsets(top: 18.0, left: 18.0, bottom: 18.0, right: 18.0)
|
override public func dismiss(completion: (() -> Void)? = nil) {
|
||||||
|
self.controllerNode.animateOut(completion: completion)
|
||||||
var contentWidth = max(titleSize.width, minActionsWidth)
|
|
||||||
contentWidth = max(contentWidth, 300.0)
|
|
||||||
|
|
||||||
var actionsHeight: CGFloat = 0.0
|
|
||||||
switch effectiveActionLayout {
|
|
||||||
case .horizontal:
|
|
||||||
actionsHeight = actionButtonHeight
|
|
||||||
case .vertical:
|
|
||||||
actionsHeight = actionButtonHeight * CGFloat(self.actionNodes.count)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let resultWidth = contentWidth
|
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||||
let resultSize = CGSize(width: resultWidth, height: titleSize.height + textSize.height + spacing + videoHeight + spacing + actionsHeight + insets.top + insets.bottom)
|
super.containerLayoutUpdated(layout, transition: transition)
|
||||||
|
|
||||||
transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: resultWidth, height: videoHeight)))
|
self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationHeight, transition: transition)
|
||||||
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
|
private class VoiceChatCameraPreviewControllerNode: ViewControllerTracingNode, UIScrollViewDelegate {
|
||||||
}
|
private let context: AccountContext
|
||||||
}
|
private var presentationData: PresentationData
|
||||||
|
|
||||||
func voiceChatCameraPreviewController(sharedContext: SharedAccountContext, account: Account, forceTheme: PresentationTheme?, title: String, text: String, apply: @escaping () -> Void) -> AlertController {
|
private let cameraNode: GroupVideoNode
|
||||||
var presentationData = sharedContext.currentPresentationData.with { $0 }
|
private let dimNode: ASDisplayNode
|
||||||
if let forceTheme = forceTheme {
|
private let wrappingScrollNode: ASScrollNode
|
||||||
presentationData = presentationData.withUpdated(theme: forceTheme)
|
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
|
||||||
|
|
||||||
var dismissImpl: ((Bool) -> Void)?
|
private let switchCameraButton: HighlightTrackingButtonNode
|
||||||
var applyImpl: (() -> Void)?
|
private let switchCameraEffectView: UIVisualEffectView
|
||||||
|
private let switchCameraIconNode: ASImageNode
|
||||||
|
|
||||||
let actions: [TextAlertAction] = [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {
|
private var containerLayout: (ContainerViewLayout, CGFloat)?
|
||||||
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)
|
var shareCamera: (() -> Void)?
|
||||||
applyImpl = { [weak contentNode] in
|
var switchCamera: (() -> Void)?
|
||||||
guard let contentNode = contentNode else {
|
var shareScreen: (() -> Void)?
|
||||||
return
|
var dismiss: (() -> Void)?
|
||||||
}
|
var cancel: (() -> Void)?
|
||||||
dismissImpl?(true)
|
|
||||||
|
|
||||||
}
|
init(context: AccountContext, cameraNode: GroupVideoNode) {
|
||||||
|
self.context = context
|
||||||
|
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
|
||||||
let controller = AlertController(theme: AlertControllerTheme(presentationData: presentationData), contentNode: contentNode)
|
self.cameraNode = cameraNode
|
||||||
let presentationDataDisposable = sharedContext.presentationData.start(next: { [weak controller, weak contentNode] presentationData in
|
|
||||||
var presentationData = presentationData
|
self.wrappingScrollNode = ASScrollNode()
|
||||||
if let forceTheme = forceTheme {
|
self.wrappingScrollNode.view.alwaysBounceVertical = true
|
||||||
presentationData = presentationData.withUpdated(theme: forceTheme)
|
self.wrappingScrollNode.view.delaysContentTouches = false
|
||||||
}
|
self.wrappingScrollNode.view.canCancelContentTouches = true
|
||||||
controller?.theme = AlertControllerTheme(presentationData: presentationData)
|
|
||||||
|
self.dimNode = ASDisplayNode()
|
||||||
|
self.dimNode.backgroundColor = UIColor(white: 0.0, alpha: 0.5)
|
||||||
|
|
||||||
|
self.contentContainerNode = ASDisplayNode()
|
||||||
|
self.contentContainerNode.isOpaque = false
|
||||||
|
|
||||||
|
self.backgroundNode = ASDisplayNode()
|
||||||
|
self.backgroundNode.clipsToBounds = true
|
||||||
|
self.backgroundNode.cornerRadius = 16.0
|
||||||
|
|
||||||
|
let backgroundColor = UIColor(rgb: 0x1c1c1e)
|
||||||
|
let textColor: UIColor = .white
|
||||||
|
let buttonColor: UIColor = UIColor(rgb: 0x2b2b2f)
|
||||||
|
let buttonTextColor: UIColor = .white
|
||||||
|
let blurStyle: UIBlurEffect.Style = .dark
|
||||||
|
|
||||||
|
self.effectNode = ASDisplayNode(viewBlock: {
|
||||||
|
return UIVisualEffectView(effect: UIBlurEffect(style: blurStyle))
|
||||||
})
|
})
|
||||||
controller.dismissed = {
|
|
||||||
presentationDataDisposable.dispose()
|
self.contentBackgroundNode = ASDisplayNode()
|
||||||
|
self.contentBackgroundNode.backgroundColor = backgroundColor
|
||||||
|
|
||||||
|
let title = self.presentationData.strings.VoiceChat_VideoPreviewTitle
|
||||||
|
|
||||||
|
self.titleNode = ASTextNode()
|
||||||
|
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.bold(17.0), textColor: textColor)
|
||||||
|
|
||||||
|
self.cameraButton = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(theme: self.presentationData.theme), font: .bold, height: 52.0, cornerRadius: 11.0, gloss: false)
|
||||||
|
self.cameraButton.title = self.presentationData.strings.VoiceChat_VideoPreviewShareCamera
|
||||||
|
|
||||||
|
self.screenButton = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(backgroundColor: buttonColor, foregroundColor: buttonTextColor), font: .bold, height: 52.0, cornerRadius: 11.0, gloss: false)
|
||||||
|
self.screenButton.title = self.presentationData.strings.VoiceChat_VideoPreviewShareScreen
|
||||||
|
|
||||||
|
self.cancelButton = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(backgroundColor: buttonColor, foregroundColor: buttonTextColor), font: .regular, height: 52.0, cornerRadius: 11.0, gloss: false)
|
||||||
|
self.cancelButton.title = self.presentationData.strings.Common_Cancel
|
||||||
|
|
||||||
|
self.previewContainerNode = ASDisplayNode()
|
||||||
|
self.previewContainerNode.cornerRadius = 11.0
|
||||||
|
self.previewContainerNode.backgroundColor = .black
|
||||||
|
|
||||||
|
self.switchCameraButton = HighlightTrackingButtonNode()
|
||||||
|
self.switchCameraEffectView = UIVisualEffectView(effect: UIBlurEffect(style: .dark))
|
||||||
|
self.switchCameraEffectView.clipsToBounds = true
|
||||||
|
self.switchCameraEffectView.layer.cornerRadius = 24.0
|
||||||
|
self.switchCameraEffectView.isUserInteractionEnabled = false
|
||||||
|
|
||||||
|
self.switchCameraIconNode = ASImageNode()
|
||||||
|
self.switchCameraIconNode.displaysAsynchronously = false
|
||||||
|
self.switchCameraIconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Call/CallSwitchCameraButton"), color: .white)
|
||||||
|
self.switchCameraIconNode.contentMode = .center
|
||||||
|
|
||||||
|
super.init()
|
||||||
|
|
||||||
|
self.backgroundColor = nil
|
||||||
|
self.isOpaque = false
|
||||||
|
|
||||||
|
self.dimNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimTapGesture(_:))))
|
||||||
|
self.addSubnode(self.dimNode)
|
||||||
|
|
||||||
|
self.wrappingScrollNode.view.delegate = self
|
||||||
|
self.addSubnode(self.wrappingScrollNode)
|
||||||
|
|
||||||
|
self.wrappingScrollNode.addSubnode(self.backgroundNode)
|
||||||
|
self.wrappingScrollNode.addSubnode(self.contentContainerNode)
|
||||||
|
|
||||||
|
self.backgroundNode.addSubnode(self.effectNode)
|
||||||
|
self.backgroundNode.addSubnode(self.contentBackgroundNode)
|
||||||
|
self.contentContainerNode.addSubnode(self.titleNode)
|
||||||
|
self.contentContainerNode.addSubnode(self.cameraButton)
|
||||||
|
self.contentContainerNode.addSubnode(self.screenButton)
|
||||||
|
self.contentContainerNode.addSubnode(self.cancelButton)
|
||||||
|
|
||||||
|
self.contentContainerNode.addSubnode(self.previewContainerNode)
|
||||||
|
|
||||||
|
self.previewContainerNode.addSubnode(self.cameraNode)
|
||||||
|
self.previewContainerNode.addSubnode(self.switchCameraButton)
|
||||||
|
self.switchCameraButton.view.addSubview(self.switchCameraEffectView)
|
||||||
|
self.switchCameraButton.addSubnode(self.switchCameraIconNode)
|
||||||
|
|
||||||
|
self.cameraButton.pressed = { [weak self] in
|
||||||
|
if let strongSelf = self {
|
||||||
|
strongSelf.shareCamera?()
|
||||||
}
|
}
|
||||||
dismissImpl = { [weak controller, weak contentNode] animated in
|
}
|
||||||
if animated {
|
self.screenButton.pressed = { [weak self] in
|
||||||
controller?.dismissAnimated()
|
if let strongSelf = self {
|
||||||
|
strongSelf.shareScreen?()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.cancelButton.pressed = { [weak self] in
|
||||||
|
if let strongSelf = self {
|
||||||
|
strongSelf.cancel?()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.switchCameraButton.addTarget(self, action: #selector(self.switchCameraPressed), forControlEvents: .touchUpInside)
|
||||||
|
self.switchCameraButton.highligthedChanged = { [weak self] highlighted in
|
||||||
|
if let strongSelf = self {
|
||||||
|
if highlighted {
|
||||||
|
let transition: ContainedViewLayoutTransition = .animated(duration: 0.3, curve: .spring)
|
||||||
|
transition.updateSublayerTransformScale(node: strongSelf.switchCameraButton, scale: 0.9)
|
||||||
} else {
|
} else {
|
||||||
controller?.dismiss()
|
let transition: ContainedViewLayoutTransition = .animated(duration: 0.5, curve: .spring)
|
||||||
|
transition.updateSublayerTransformScale(node: strongSelf.switchCameraButton, scale: 1.0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return controller
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@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
|
||||||
|
|
||||||
|
let bottomInset: CGFloat = 10.0 + cleanInsets.bottom
|
||||||
|
let titleHeight: CGFloat = 54.0
|
||||||
|
var contentHeight = titleHeight + bottomInset + 52.0 + 17.0
|
||||||
|
let innerContentHeight: CGFloat = layout.size.height - contentHeight - 160.0
|
||||||
|
contentHeight = titleHeight + bottomInset + 52.0 + 17.0 + innerContentHeight + buttonOffset
|
||||||
|
|
||||||
|
let width = horizontalContainerFillingSizeForLayout(layout: layout, sideInset: layout.safeInsets.left)
|
||||||
|
|
||||||
|
let previewInset: CGFloat = 16.0
|
||||||
|
let sideInset = floor((layout.size.width - width) / 2.0)
|
||||||
|
let contentContainerFrame = CGRect(origin: CGPoint(x: sideInset, y: layout.size.height - contentHeight), size: CGSize(width: width, height: contentHeight))
|
||||||
|
let contentFrame = contentContainerFrame
|
||||||
|
|
||||||
|
var backgroundFrame = CGRect(origin: CGPoint(x: contentFrame.minX, y: contentFrame.minY), size: CGSize(width: contentFrame.width, height: contentFrame.height + 2000.0))
|
||||||
|
if backgroundFrame.minY < contentFrame.minY {
|
||||||
|
backgroundFrame.origin.y = contentFrame.minY
|
||||||
|
}
|
||||||
|
transition.updateFrame(node: self.backgroundNode, frame: backgroundFrame)
|
||||||
|
transition.updateFrame(node: self.effectNode, frame: CGRect(origin: CGPoint(), size: backgroundFrame.size))
|
||||||
|
transition.updateFrame(node: self.contentBackgroundNode, frame: CGRect(origin: CGPoint(), size: backgroundFrame.size))
|
||||||
|
transition.updateFrame(node: self.wrappingScrollNode, frame: CGRect(origin: CGPoint(), size: layout.size))
|
||||||
|
transition.updateFrame(node: self.dimNode, frame: CGRect(origin: CGPoint(), size: layout.size))
|
||||||
|
|
||||||
|
let titleSize = self.titleNode.measure(CGSize(width: width, height: titleHeight))
|
||||||
|
let titleFrame = CGRect(origin: CGPoint(x: floor((contentFrame.width - titleSize.width) / 2.0), y: 18.0), size: titleSize)
|
||||||
|
transition.updateFrame(node: self.titleNode, frame: titleFrame)
|
||||||
|
|
||||||
|
let previewSize = CGSize(width: contentFrame.width - previewInset * 2.0, height: contentHeight - 243.0 - bottomInset)
|
||||||
|
transition.updateFrame(node: self.previewContainerNode, frame: CGRect(origin: CGPoint(x: previewInset, y: 56.0), size: previewSize))
|
||||||
|
|
||||||
|
self.cameraNode.frame = CGRect(origin: CGPoint(), size: previewSize)
|
||||||
|
self.cameraNode.updateLayout(size: previewSize, isLandscape: false, transition: .immediate)
|
||||||
|
|
||||||
|
let switchCameraFrame = CGRect(x: previewSize.width - 48.0 - 16.0, y: previewSize.height - 48.0 - 16.0, width: 48.0, height: 48.0)
|
||||||
|
transition.updateFrame(node: self.switchCameraButton, frame: switchCameraFrame)
|
||||||
|
transition.updateFrame(view: self.switchCameraEffectView, frame: CGRect(origin: CGPoint(), size: switchCameraFrame.size))
|
||||||
|
transition.updateFrame(node: self.switchCameraIconNode, frame: CGRect(origin: CGPoint(), size: switchCameraFrame.size))
|
||||||
|
|
||||||
|
let buttonInset: CGFloat = 16.0
|
||||||
|
let cameraButtonHeight = self.cameraButton.updateLayout(width: contentFrame.width - buttonInset * 2.0, transition: transition)
|
||||||
|
transition.updateFrame(node: self.cameraButton, frame: CGRect(x: buttonInset, y: contentHeight - cameraButtonHeight - insets.bottom - 16.0 - buttonOffset, width: contentFrame.width, height: cameraButtonHeight))
|
||||||
|
|
||||||
|
let screenButtonHeight = self.screenButton.updateLayout(width: contentFrame.width - buttonInset * 2.0, transition: transition)
|
||||||
|
transition.updateFrame(node: self.screenButton, frame: CGRect(x: buttonInset, y: contentHeight - cameraButtonHeight - 8.0 - screenButtonHeight - insets.bottom - 16.0, width: contentFrame.width, height: screenButtonHeight))
|
||||||
|
|
||||||
|
let cancelButtonHeight = self.cancelButton.updateLayout(width: contentFrame.width - buttonInset * 2.0, transition: transition)
|
||||||
|
transition.updateFrame(node: self.cancelButton, frame: CGRect(x: buttonInset, y: contentHeight - cancelButtonHeight - insets.bottom - 16.0, width: contentFrame.width, height: cancelButtonHeight))
|
||||||
|
|
||||||
|
transition.updateFrame(node: self.contentContainerNode, frame: contentContainerFrame)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
@ -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()
|
||||||
|
}
|
||||||
|
}, switchCamera: { [weak self] in
|
||||||
|
self?.call.switchVideoCamera()
|
||||||
|
}, shareScreen: { [weak self] in
|
||||||
|
self?.call.requestScreencast()
|
||||||
})
|
})
|
||||||
self.controller?.present(controller, in: .window(.root))
|
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 {
|
||||||
|
if isLandscape {
|
||||||
|
backgroundColor = isFullscreen ? panelBackgroundColor : secondaryPanelBackgroundColor
|
||||||
|
} else {
|
||||||
backgroundColor = fullscreenBackgroundColor
|
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 {
|
||||||
@ -3753,33 +3781,36 @@ 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,21 +3876,22 @@ 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)
|
||||||
|
|
||||||
@ -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
|
||||||
|
|
||||||
@ -4173,8 +4215,6 @@ public final class VoiceChatController: ViewController {
|
|||||||
}
|
}
|
||||||
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:
|
||||||
|
@ -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
|
||||||
|
|
||||||
override init() {
|
private var currentParams: (CGSize, VoiceChatParticipantItem.ParticipantText)?
|
||||||
self.iconNode = ASImageNode()
|
|
||||||
self.iconNode.displaysAsynchronously = false
|
|
||||||
|
|
||||||
|
override init() {
|
||||||
|
self.iconNodes = []
|
||||||
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 {
|
switch textColor {
|
||||||
case .generic:
|
|
||||||
textColorValue = item.presentationData.theme.list.itemSecondaryTextColor
|
|
||||||
case .accent:
|
case .accent:
|
||||||
textColorValue = item.presentationData.theme.list.itemAccentColor
|
wavesColor = accentColor
|
||||||
wavesColor = textColorValue
|
|
||||||
case .constructive:
|
|
||||||
textColorValue = constructiveColor
|
|
||||||
case .destructive:
|
case .destructive:
|
||||||
textColorValue = destructiveColor
|
wavesColor = destructiveColor
|
||||||
wavesColor = textColorValue
|
default:
|
||||||
}
|
|
||||||
if item.transparent && item.style == .list {
|
|
||||||
textColorValue = UIColor(rgb: 0xffffff, alpha: 0.65)
|
|
||||||
}
|
|
||||||
statusAttributedString = NSAttributedString(string: text, font: statusFont, textColor: textColorValue)
|
|
||||||
case .none:
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
if let expandedText = item.expandedText, case let .text(text, _, textColor) = expandedText {
|
|
||||||
let textColorValue: UIColor
|
|
||||||
switch textColor {
|
|
||||||
case .generic:
|
|
||||||
textColorValue = item.presentationData.theme.list.itemSecondaryTextColor
|
|
||||||
case .accent:
|
|
||||||
textColorValue = item.presentationData.theme.list.itemAccentColor
|
|
||||||
case .constructive:
|
|
||||||
textColorValue = constructiveColor
|
|
||||||
case .destructive:
|
|
||||||
textColorValue = destructiveColor
|
|
||||||
}
|
|
||||||
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
|
||||||
|
@ -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
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -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 |
BIN
submodules/TelegramUI/Images.xcassets/Call/CallAirpodsMaxButton.imageset/ic_call_airpodsmax.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Call/CallAirpodsMaxButton.imageset/ic_call_airpodsmax.pdf
vendored
Normal file
Binary file not shown.
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"images" : [
|
"images" : [
|
||||||
{
|
{
|
||||||
"filename" : "ic_call_flip.pdf",
|
"filename" : "ic_cam_flip.pdf",
|
||||||
"idiom" : "universal"
|
"idiom" : "universal"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
Binary file not shown.
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"images" : [
|
"images" : [
|
||||||
{
|
{
|
||||||
"filename" : "ic_vc_camera.pdf",
|
"filename" : "ic_voicesharing.pdf",
|
||||||
"idiom" : "universal"
|
"idiom" : "universal"
|
||||||
}
|
}
|
||||||
],
|
],
|
Binary file not shown.
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"images" : [
|
"images" : [
|
||||||
{
|
{
|
||||||
"filename" : "volsmall.pdf",
|
"filename" : "ic_voicecamera.pdf",
|
||||||
"idiom" : "universal"
|
"idiom" : "universal"
|
||||||
}
|
}
|
||||||
],
|
],
|
Binary file not shown.
12
submodules/TelegramUI/Images.xcassets/Call/StatusVolume.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Call/StatusVolume.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "ic_voicevolumeon.pdf",
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
submodules/TelegramUI/Images.xcassets/Call/StatusVolume.imageset/ic_voicevolumeon.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Call/StatusVolume.imageset/ic_voicevolumeon.pdf
vendored
Normal file
Binary file not shown.
12
submodules/TelegramUI/Images.xcassets/Call/StatusVolumeOff.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Call/StatusVolumeOff.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "ic_voicevolumeoff.pdf",
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
submodules/TelegramUI/Images.xcassets/Call/StatusVolumeOff.imageset/ic_voicevolumeoff.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Call/StatusVolumeOff.imageset/ic_voicevolumeoff.pdf
vendored
Normal file
Binary file not shown.
Binary file not shown.
@ -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 {
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -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 {
|
||||||
|
@ -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,12 +3335,8 @@ 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
|
|
||||||
if let strongSelf = self {
|
|
||||||
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(strongSelf.peerId), keepStack: strongSelf.nearbyPeerDistance != nil ? .always : .default, peerNearbyData: strongSelf.nearbyPeerDistance.flatMap({ ChatPeerNearbyData(distance: $0) }), greetingData: strongSelf.nearbyPeerDistance != nil ? sticker.flatMap({ ChatGreetingData(sticker: $0) }) : nil, completion: { _ in
|
|
||||||
if strongSelf.nearbyPeerDistance != nil {
|
|
||||||
var viewControllers = navigationController.viewControllers
|
var viewControllers = navigationController.viewControllers
|
||||||
viewControllers = viewControllers.filter { controller in
|
viewControllers = viewControllers.filter { controller in
|
||||||
if controller is PeerInfoScreen {
|
if controller is PeerInfoScreen {
|
||||||
@ -3373,8 +3348,6 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
|||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
})
|
|
||||||
}
|
|
||||||
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 {
|
||||||
if let navigationController = controller.navigationController as? NavigationController {
|
if let navigationController = controller.navigationController as? NavigationController {
|
||||||
@ -3773,12 +3746,8 @@ 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
|
|
||||||
if let strongSelf = self {
|
|
||||||
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(strongSelf.peerId), keepStack: strongSelf.nearbyPeerDistance != nil ? .always : .default, activateMessageSearch: (.everything, ""), peerNearbyData: strongSelf.nearbyPeerDistance.flatMap({ ChatPeerNearbyData(distance: $0) }), greetingData: strongSelf.nearbyPeerDistance != nil ? sticker.flatMap({ ChatGreetingData(sticker: $0) }) : nil, completion: { _ in
|
|
||||||
if strongSelf.nearbyPeerDistance != nil {
|
|
||||||
var viewControllers = navigationController.viewControllers
|
var viewControllers = navigationController.viewControllers
|
||||||
viewControllers = viewControllers.filter { controller in
|
viewControllers = viewControllers.filter { controller in
|
||||||
if controller is PeerInfoScreen {
|
if controller is PeerInfoScreen {
|
||||||
@ -3790,8 +3759,6 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
|||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func openChatForReporting(_ reason: ReportReason) {
|
private func openChatForReporting(_ reason: ReportReason) {
|
||||||
@ -4233,12 +4200,8 @@ 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
|
|
||||||
if let strongSelf = self {
|
|
||||||
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(strongSelf.peerId), keepStack: strongSelf.nearbyPeerDistance != nil ? .always : .default, peerNearbyData: strongSelf.nearbyPeerDistance.flatMap({ ChatPeerNearbyData(distance: $0) }), greetingData: strongSelf.nearbyPeerDistance != nil ? sticker.flatMap({ ChatGreetingData(sticker: $0) }) : nil, completion: { _ in
|
|
||||||
if strongSelf.nearbyPeerDistance != nil {
|
|
||||||
var viewControllers = navigationController.viewControllers
|
var viewControllers = navigationController.viewControllers
|
||||||
viewControllers = viewControllers.filter { controller in
|
viewControllers = viewControllers.filter { controller in
|
||||||
if controller is PeerInfoScreen {
|
if controller is PeerInfoScreen {
|
||||||
@ -4250,8 +4213,6 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
|||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func openAddContact() {
|
private func openAddContact() {
|
||||||
|
@ -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
|
Loading…
x
Reference in New Issue
Block a user