Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios

This commit is contained in:
overtake 2020-06-24 12:06:33 +03:00
commit 863ccdf297
17 changed files with 639 additions and 175 deletions

View File

@ -1,7 +1,24 @@
import Foundation
import UIKit
import Display
public final class OverlayMediaControllerEmbeddingItem {
public let position: CGPoint
public let itemNode: OverlayMediaItemNode
public init(
position: CGPoint,
itemNode: OverlayMediaItemNode
) {
self.position = position
self.itemNode = itemNode
}
}
public protocol OverlayMediaController: class {
var updatePossibleEmbeddingItem: ((OverlayMediaControllerEmbeddingItem?) -> Void)? { get set }
var embedPossibleEmbeddingItem: ((OverlayMediaControllerEmbeddingItem) -> Bool)? { get set }
var hasNodes: Bool { get }
func addNode(_ node: OverlayMediaItemNode, customTransition: Bool)
func removeNode(_ node: OverlayMediaItemNode, customTransition: Bool)
@ -10,10 +27,21 @@ public protocol OverlayMediaController: class {
public final class OverlayMediaManager {
public var controller: (OverlayMediaController & ViewController)?
public var updatePossibleEmbeddingItem: ((OverlayMediaControllerEmbeddingItem?) -> Void)?
public var embedPossibleEmbeddingItem: ((OverlayMediaControllerEmbeddingItem) -> Bool)?
public init() {
}
public func attachOverlayMediaController(_ controller: OverlayMediaController & ViewController) {
self.controller = controller
controller.updatePossibleEmbeddingItem = { [weak self] item in
self?.updatePossibleEmbeddingItem?(item)
}
controller.embedPossibleEmbeddingItem = { [weak self] item in
return self?.embedPossibleEmbeddingItem?(item) ?? false
}
}
}

View File

@ -102,6 +102,19 @@ private final class NavigationControllerNode: ASDisplayNode {
}
}
public protocol NavigationControllerDropContentItem: class {
}
public final class NavigationControllerDropContent {
public let position: CGPoint
public let item: NavigationControllerDropContentItem
public init(position: CGPoint, item: NavigationControllerDropContentItem) {
self.position = position
self.item = item
}
}
open class NavigationController: UINavigationController, ContainableController, UIGestureRecognizerDelegate {
public var isOpaqueWhenInOverlay: Bool = true
public var blocksBackgroundWhenInOverlay: Bool = true
@ -1221,6 +1234,35 @@ open class NavigationController: UINavigationController, ContainableController,
}
}
public func updatePossibleControllerDropContent(content: NavigationControllerDropContent?) {
if let rootContainer = self.rootContainer {
switch rootContainer {
case let .flat(container):
if let controller = container.controllers.last {
controller.updatePossibleControllerDropContent(content: content)
}
case .split:
break
}
}
}
public func acceptPossibleControllerDropContent(content: NavigationControllerDropContent) -> Bool {
if let rootContainer = self.rootContainer {
switch rootContainer {
case let .flat(container):
if let controller = container.controllers.last {
if controller.acceptPossibleControllerDropContent(content: content) {
return true
}
}
case .split:
break
}
}
return false
}
override open func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) {
preconditionFailure()
}

View File

@ -45,6 +45,7 @@ public final class NavigationBarBadgeNode: ASDisplayNode {
self.textColor = textColor
self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 18.0, color: fillColor, strokeColor: strokeColor, strokeWidth: 1.0)
self.textNode.attributedText = NSAttributedString(string: self.text, font: self.font, textColor: self.textColor)
self.textNode.redrawIfPossible()
}
override public func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize {

View File

@ -644,6 +644,13 @@ public enum TabBarItemContextActionType {
open func tabBarItemSwipeAction(direction: TabBarItemSwipeDirection) {
}
open func updatePossibleControllerDropContent(content: NavigationControllerDropContent?) {
}
open func acceptPossibleControllerDropContent(content: NavigationControllerDropContent) -> Bool {
return false
}
}
func traceIsOpaque(layer: CALayer, rect: CGRect) -> Bool {

View File

@ -1287,7 +1287,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
mediaManager?.setOverlayVideoNode(nil)
})
expandImpl = { [weak overlayNode] in
guard let contentInfo = item.contentInfo else {
guard let contentInfo = item.contentInfo, let overlayNode = overlayNode else {
return
}
@ -1302,7 +1302,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
baseNavigationController?.view.endEditing(true)
(baseNavigationController?.topViewController as? ViewController)?.present(gallery, in: .window(.root), with: GalleryControllerPresentationArguments(transitionArguments: { id, media in
(baseNavigationController?.topViewController as? ViewController)?.present(gallery, in: .window(.root), with: GalleryControllerPresentationArguments(transitionArguments: { [weak overlayNode] id, media in
if let overlayNode = overlayNode, let overlaySupernode = overlayNode.supernode {
return GalleryTransitionArguments(transitionNode: (overlayNode, overlayNode.bounds, { [weak overlayNode] in
return (overlayNode?.view.snapshotContentTree(), nil)

View File

@ -42,6 +42,7 @@ private enum DebugControllerSection: Int32 {
case logs
case logging
case experiments
case videoExperiments
case info
}
@ -70,6 +71,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
case knockoutWallpaper(PresentationTheme, Bool)
case alternativeFolderTabs(Bool)
case videoCalls(Bool)
case videoCallsInfo(PresentationTheme, String)
case hostInfo(PresentationTheme, String)
case versionInfo(PresentationTheme)
@ -83,8 +85,10 @@ private enum DebugControllerEntry: ItemListNodeEntry {
return DebugControllerSection.logging.rawValue
case .enableRaiseToSpeak, .keepChatNavigationStack, .skipReadHistory, .crashOnSlowQueries:
return DebugControllerSection.experiments.rawValue
case .clearTips, .reimport, .resetData, .resetDatabase, .resetHoles, .reindexUnread, .resetBiometricsData, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .alternativeFolderTabs, .videoCalls:
case .clearTips, .reimport, .resetData, .resetDatabase, .resetHoles, .reindexUnread, .resetBiometricsData, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .alternativeFolderTabs:
return DebugControllerSection.experiments.rawValue
case .videoCalls, .videoCallsInfo:
return DebugControllerSection.videoExperiments.rawValue
case .hostInfo, .versionInfo:
return DebugControllerSection.info.rawValue
}
@ -140,10 +144,12 @@ private enum DebugControllerEntry: ItemListNodeEntry {
return 23
case .videoCalls:
return 24
case .hostInfo:
case .videoCallsInfo:
return 25
case .versionInfo:
case .hostInfo:
return 26
case .versionInfo:
return 27
}
}
@ -542,7 +548,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
}).start()
})
case let .videoCalls(value):
return ItemListSwitchItem(presentationData: presentationData, title: "Video", value: value, sectionId: self.section, style: .blocks, updated: { value in
return ItemListSwitchItem(presentationData: presentationData, title: "Experimental Feature", value: value, sectionId: self.section, style: .blocks, updated: { value in
let _ = arguments.sharedContext.accountManager.transaction ({ transaction in
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in
var settings = settings as? ExperimentalUISettings ?? ExperimentalUISettings.defaultSettings
@ -551,6 +557,8 @@ private enum DebugControllerEntry: ItemListNodeEntry {
})
}).start()
})
case let .videoCallsInfo(_, text):
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
case let .hostInfo(theme, string):
return ItemListTextItem(presentationData: presentationData, text: .plain(string), sectionId: self.section)
case let .versionInfo(theme):
@ -595,6 +603,7 @@ private func debugControllerEntries(presentationData: PresentationData, loggingS
entries.append(.knockoutWallpaper(presentationData.theme, experimentalSettings.knockoutWallpaper))
entries.append(.alternativeFolderTabs(experimentalSettings.foldersTabAtBottom))
entries.append(.videoCalls(experimentalSettings.videoCalls))
entries.append(.videoCallsInfo(presentationData.theme, "Enables experimental transmission of electromagnetic radiation synchronized with pressure waves. Needs to be enabled on both sides."))
if let backupHostOverride = networkSettings?.backupHostOverride {
entries.append(.hostInfo(presentationData.theme, "Host: \(backupHostOverride)"))

View File

@ -45,9 +45,9 @@ public extension TabBarControllerTheme {
}
public extension NavigationBarTheme {
convenience init(rootControllerTheme: PresentationTheme) {
convenience init(rootControllerTheme: PresentationTheme, hideBackground: Bool = false) {
let theme = rootControllerTheme.rootController.navigationBar
self.init(buttonColor: theme.buttonColor, disabledButtonColor: theme.disabledButtonColor, primaryTextColor: theme.primaryTextColor, backgroundColor: theme.backgroundColor, separatorColor: theme.separatorColor, badgeBackgroundColor: theme.badgeBackgroundColor, badgeStrokeColor: theme.badgeStrokeColor, badgeTextColor: theme.badgeTextColor)
self.init(buttonColor: theme.buttonColor, disabledButtonColor: theme.disabledButtonColor, primaryTextColor: theme.primaryTextColor, backgroundColor: hideBackground ? .clear : theme.backgroundColor, separatorColor: hideBackground ? .clear : theme.separatorColor, badgeBackgroundColor: theme.badgeBackgroundColor, badgeStrokeColor: hideBackground ? .clear : theme.badgeStrokeColor, badgeTextColor: theme.badgeTextColor)
}
}
@ -62,6 +62,10 @@ public extension NavigationBarPresentationData {
self.init(theme: NavigationBarTheme(rootControllerTheme: presentationData.theme), strings: NavigationBarStrings(presentationStrings: presentationData.strings))
}
convenience init(presentationData: PresentationData, hideBackground: Bool) {
self.init(theme: NavigationBarTheme(rootControllerTheme: presentationData.theme, hideBackground: hideBackground), strings: NavigationBarStrings(presentationStrings: presentationData.strings))
}
convenience init(presentationTheme: PresentationTheme, presentationStrings: PresentationStrings) {
self.init(theme: NavigationBarTheme(rootControllerTheme: presentationTheme), strings: NavigationBarStrings(presentationStrings: presentationStrings))
}

View File

@ -320,6 +320,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
private let peekData: ChatPeekTimeout?
private let peekTimerDisposable = MetaDisposable()
private var hasEmbeddedTitleContent = false
public override var customData: Any? {
return self.chatLocation
@ -373,7 +375,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
case .inline:
navigationBarPresentationData = nil
default:
navigationBarPresentationData = NavigationBarPresentationData(presentationData: self.presentationData)
navigationBarPresentationData = NavigationBarPresentationData(presentationData: self.presentationData, hideBackground: true)
}
super.init(context: context, navigationBarPresentationData: navigationBarPresentationData, mediaAccessoryPanelVisibility: mediaAccessoryPanelVisibility, locationBroadcastPanelSource: locationBroadcastPanelSource)
@ -2779,8 +2781,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
case .inline:
self.statusBar.statusBarStyle = .Ignore
}
self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData))
self.chatTitleView?.updateThemeAndStrings(theme: self.presentationData.theme, strings: self.presentationData.strings)
self.updateNavigationBarPresentation()
self.updateChatPresentationInterfaceState(animated: false, interactive: false, { state in
var state = state
state = state.updatedTheme(self.presentationData.theme)
@ -2794,6 +2795,20 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
self.currentContextController?.updateTheme(presentationData: self.presentationData)
}
private func updateNavigationBarPresentation() {
let navigationBarTheme: NavigationBarTheme
if self.hasEmbeddedTitleContent {
navigationBarTheme = NavigationBarTheme(rootControllerTheme: defaultDarkPresentationTheme, hideBackground: true)
} else {
navigationBarTheme = NavigationBarTheme(rootControllerTheme: self.presentationData.theme, hideBackground: true)
}
self.navigationBar?.updatePresentationData(NavigationBarPresentationData(theme: navigationBarTheme, strings: NavigationBarStrings(presentationStrings: self.presentationData.strings)))
self.chatTitleView?.updateThemeAndStrings(theme: self.presentationData.theme, strings: self.presentationData.strings, hasEmbeddedTitleContent: self.hasEmbeddedTitleContent)
}
override public func loadDisplayNode() {
self.displayNode = ChatControllerNode(context: self.context, chatLocation: self.chatLocation, subject: self.subject, controllerInteraction: self.controllerInteraction!, chatPresentationInterfaceState: self.presentationInterfaceState, automaticMediaDownloadSettings: self.automaticMediaDownloadSettings, navigationBar: self.navigationBar, controller: self)
@ -4735,6 +4750,32 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}))
}
self.chatDisplayNode.updateHasEmbeddedTitleContent = { [weak self] hasEmbeddedTitleContent in
guard let strongSelf = self else {
return
}
if strongSelf.hasEmbeddedTitleContent != hasEmbeddedTitleContent {
strongSelf.hasEmbeddedTitleContent = hasEmbeddedTitleContent
if strongSelf.hasEmbeddedTitleContent {
strongSelf.statusBar.statusBarStyle = .White
} else {
strongSelf.statusBar.statusBarStyle = strongSelf.presentationData.theme.rootController.statusBarStyle.style
}
if let navigationBar = strongSelf.navigationBar {
if let navigationBarCopy = navigationBar.view.snapshotContentTree() {
navigationBar.view.superview?.insertSubview(navigationBarCopy, aboveSubview: navigationBar.view)
navigationBarCopy.alpha = 0.0
navigationBarCopy.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring, completion: { [weak navigationBarCopy] _ in
navigationBarCopy?.removeFromSuperview()
})
}
}
strongSelf.updateNavigationBarPresentation()
}
}
self.interfaceInteraction = interfaceInteraction
if let search = self.focusOnSearchAfterAppearance {
@ -5143,7 +5184,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
switch self.presentationInterfaceState.mode {
case .standard, .inline:
break
break
case .overlay:
if case .Ignore = self.statusBar.statusBarStyle {
} else if layout.safeInsets.top.isZero {
@ -5503,7 +5544,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
switch updatedChatPresentationInterfaceState.mode {
case .standard:
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style
if self.hasEmbeddedTitleContent {
self.statusBar.statusBarStyle = .White
} else {
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style
}
self.deferScreenEdgeGestures = []
case .overlay:
self.deferScreenEdgeGestures = [.top]
@ -9256,6 +9301,14 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
self.focusOnSearchAfterAppearance = (domain, query)
self.interfaceInteraction?.beginMessageSearch(domain, query)
}
override public func updatePossibleControllerDropContent(content: NavigationControllerDropContent?) {
self.chatDisplayNode.updateEmbeddedTitlePeekContent(content: content)
}
override public func acceptPossibleControllerDropContent(content: NavigationControllerDropContent) -> Bool {
return self.chatDisplayNode.acceptEmbeddedTitlePeekContent(content: content)
}
}
private final class ContextControllerContentSourceImpl: ContextControllerContentSource {

View File

@ -12,6 +12,15 @@ import TextFormat
import AccountContext
import TelegramNotices
import ReactionSelectionNode
import TelegramUniversalVideoContent
final class VideoNavigationControllerDropContentItem: NavigationControllerDropContentItem {
let itemNode: OverlayMediaItemNode
init(itemNode: OverlayMediaItemNode) {
self.itemNode = itemNode
}
}
private final class ChatControllerNodeView: UITracingLayerView, WindowInputAccessoryHeightProvider, PreviewingHostView {
var inputAccessoryHeight: (() -> CGFloat)?
@ -56,6 +65,175 @@ private struct ChatControllerNodeDerivedLayoutState {
var upperInputPositionBound: CGFloat?
}
private final class ChatEmbeddedTitleContentNode: ASDisplayNode {
private let context: AccountContext
private let backgroundNode: ASDisplayNode
private let videoNode: OverlayUniversalVideoNode
private var validLayout: (CGSize, CGFloat, CGFloat)?
private let dismissed: () -> Void
private let interactiveExtensionUpdated: (ContainedViewLayoutTransition) -> Void
private(set) var interactiveExtension: CGFloat = 0.0
private var freezeInteractiveExtension = false
init(context: AccountContext, videoNode: OverlayUniversalVideoNode, interactiveExtensionUpdated: @escaping (ContainedViewLayoutTransition) -> Void, dismissed: @escaping () -> Void) {
self.dismissed = dismissed
self.interactiveExtensionUpdated = interactiveExtensionUpdated
self.context = context
self.backgroundNode = ASDisplayNode()
self.backgroundNode.backgroundColor = .black
self.videoNode = videoNode
super.init()
self.clipsToBounds = true
self.addSubnode(self.backgroundNode)
self.view.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(self.panGesture(_:))))
}
@objc private func panGesture(_ recognizer: UIPanGestureRecognizer) {
switch recognizer.state {
case .began:
break
case .changed:
let translation = recognizer.translation(in: self.view)
func rubberBandingOffset(offset: CGFloat, bandingStart: CGFloat) -> CGFloat {
let bandedOffset = offset - bandingStart
let range: CGFloat = 600.0
let coefficient: CGFloat = 0.4
return bandingStart + (1.0 - (1.0 / ((bandedOffset * coefficient / range) + 1.0))) * range
}
let offset = rubberBandingOffset(offset: translation.y, bandingStart: 0.0)
if translation.y > 80.0 {
self.freezeInteractiveExtension = true
self.videoNode.customExpand?()
} else {
self.interactiveExtension = max(0.0, offset)
self.interactiveExtensionUpdated(.immediate)
}
case .cancelled, .ended:
if !freezeInteractiveExtension {
self.interactiveExtension = 0.0
self.interactiveExtensionUpdated(.animated(duration: 0.3, curve: .spring))
}
default:
break
}
}
func calculateHeight(width: CGFloat) -> CGFloat {
return self.videoNode.content.dimensions.aspectFilled(CGSize(width: width, height: 16.0)).height
}
func updateLayout(size: CGSize, topInset: CGFloat, interactiveExtension: CGFloat, transition: ContainedViewLayoutTransition, transitionSurface: ASDisplayNode?, navigationBar: NavigationBar?) {
let isFirstTime = self.validLayout == nil
self.validLayout = (size, topInset, interactiveExtension)
let videoFrame = CGRect(origin: CGPoint(x: 0.0, y: topInset + interactiveExtension), size: CGSize(width: size.width, height: size.height - topInset - interactiveExtension))
if isFirstTime, let transitionSurface = transitionSurface {
let sourceFrame = self.videoNode.view.convert(self.videoNode.bounds, to: transitionSurface.view)
let targetFrame = self.view.convert(videoFrame, to: transitionSurface.view)
self.context.sharedContext.mediaManager.setOverlayVideoNode(nil)
transitionSurface.addSubnode(self.videoNode)
let navigationBarCopy = navigationBar?.view.snapshotView(afterScreenUpdates: true)
let navigationBarContainer = UIView()
navigationBarContainer.frame = targetFrame
navigationBarContainer.clipsToBounds = true
transitionSurface.view.addSubview(navigationBarContainer)
navigationBarContainer.layer.animateFrame(from: sourceFrame, to: targetFrame, duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring)
if let navigationBar = navigationBar, let navigationBarCopy = navigationBarCopy {
let navigationFrame = navigationBar.view.convert(navigationBar.bounds, to: transitionSurface.view)
let navigationSourceFrame = navigationFrame.offsetBy(dx: -sourceFrame.minX, dy: -sourceFrame.minY)
let navigationTargetFrame = navigationFrame.offsetBy(dx: -targetFrame.minX, dy: -targetFrame.minY)
navigationBarCopy.frame = navigationTargetFrame
navigationBarContainer.addSubview(navigationBarCopy)
navigationBarCopy.layer.animateFrame(from: navigationSourceFrame, to: navigationTargetFrame, duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring)
navigationBarCopy.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring)
}
self.videoNode.updateRoundCorners(false, transition: .animated(duration: 0.25, curve: .spring))
self.videoNode.showControls()
self.videoNode.updateLayout(targetFrame.size, transition: .animated(duration: 0.25, curve: .spring))
self.videoNode.frame = targetFrame
self.videoNode.layer.animateFrame(from: sourceFrame, to: targetFrame, duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring, completion: { [weak self] _ in
guard let strongSelf = self else {
return
}
navigationBarContainer.removeFromSuperview()
strongSelf.addSubnode(strongSelf.videoNode)
if let (size, topInset, interactiveExtension) = strongSelf.validLayout {
strongSelf.updateLayout(size: size, topInset: topInset, interactiveExtension: interactiveExtension, transition: .immediate, transitionSurface: nil, navigationBar: nil)
}
})
self.backgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
self.videoNode.customExpand = { [weak self] in
guard let strongSelf = self else {
return
}
let transition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .spring)
strongSelf.videoNode.customExpand = nil
strongSelf.videoNode.customClose = nil
let previousFrame = strongSelf.videoNode.frame
strongSelf.context.sharedContext.mediaManager.setOverlayVideoNode(strongSelf.videoNode)
strongSelf.videoNode.updateRoundCorners(true, transition: transition)
if let targetSuperview = strongSelf.videoNode.view.superview {
let sourceFrame = strongSelf.view.convert(previousFrame, to: targetSuperview)
let targetFrame = strongSelf.videoNode.frame
strongSelf.videoNode.frame = sourceFrame
strongSelf.videoNode.updateLayout(sourceFrame.size, transition: .immediate)
transition.updateFrame(node: strongSelf.videoNode, frame: targetFrame)
strongSelf.videoNode.updateLayout(targetFrame.size, transition: transition)
}
strongSelf.dismissed()
}
self.videoNode.customClose = { [weak self] in
guard let strongSelf = self else {
return
}
strongSelf.videoNode.customClose = nil
strongSelf.dismissed()
}
}
transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(), size: size))
if self.videoNode.supernode == self {
self.videoNode.layer.transform = CATransform3DIdentity
transition.updateFrame(node: self.videoNode, frame: videoFrame)
}
}
}
enum ChatEmbeddedTitlePeekContent: Equatable {
case none
case peek
}
class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
let context: AccountContext
let chatLocation: ChatLocation
@ -63,6 +241,8 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
private weak var controller: ChatControllerImpl?
let navigationBar: NavigationBar?
private let navigationBarBackroundNode: ASDisplayNode
private let navigationBarSeparatorNode: ASDisplayNode
private var backgroundEffectNode: ASDisplayNode?
private var containerBackgroundNode: ASImageNode?
@ -204,6 +384,10 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
private var onLayoutCompletions: [(ContainedViewLayoutTransition) -> Void] = []
private var embeddedTitlePeekContent: ChatEmbeddedTitlePeekContent = .none
private var embeddedTitleContentNode: ChatEmbeddedTitleContentNode?
private var dismissedEmbeddedTitleContentNode: ChatEmbeddedTitleContentNode?
init(context: AccountContext, chatLocation: ChatLocation, subject: ChatControllerSubject?, controllerInteraction: ChatControllerInteraction, chatPresentationInterfaceState: ChatPresentationInterfaceState, automaticMediaDownloadSettings: MediaAutoDownloadSettings, navigationBar: NavigationBar?, controller: ChatControllerImpl?) {
self.context = context
self.chatLocation = chatLocation
@ -248,6 +432,12 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
self.navigateButtons = ChatHistoryNavigationButtons(theme: self.chatPresentationInterfaceState.theme)
self.navigateButtons.accessibilityElementsHidden = true
self.navigationBarBackroundNode = ASDisplayNode()
self.navigationBarBackroundNode.backgroundColor = chatPresentationInterfaceState.theme.rootController.navigationBar.backgroundColor
self.navigationBarSeparatorNode = ASDisplayNode()
self.navigationBarSeparatorNode.backgroundColor = chatPresentationInterfaceState.theme.rootController.navigationBar.separatorColor
super.init()
self.controller?.presentationContext.topLevelSubview = { [weak self] in
@ -329,6 +519,9 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
self.addSubnode(self.navigateButtons)
self.addSubnode(self.navigationBarBackroundNode)
self.addSubnode(self.navigationBarSeparatorNode)
self.historyNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))))
self.textInputPanelNode = ChatTextInputPanelNode(presentationInterfaceState: chatPresentationInterfaceState, presentController: { [weak self] controller in
@ -696,12 +889,63 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
} else {
insets = layout.insets(options: [.input])
}
if case .overlay = self.chatPresentationInterfaceState.mode {
insets.top = 44.0
let statusBarHeight = layout.insets(options: [.statusBar]).top
if let embeddedTitleContentNode = self.embeddedTitleContentNode {
let embeddedSize = CGSize(width: layout.size.width, height: min(400.0, embeddedTitleContentNode.calculateHeight(width: layout.size.width)) + statusBarHeight + embeddedTitleContentNode.interactiveExtension)
if embeddedTitleContentNode.supernode == nil {
self.insertSubnode(embeddedTitleContentNode, aboveSubnode: self.navigationBarBackroundNode)
var previousTopInset = insets.top
if case .overlay = self.chatPresentationInterfaceState.mode {
previousTopInset = 44.0
} else {
previousTopInset += navigationBarHeight
}
if case .peek = self.embeddedTitlePeekContent {
previousTopInset += 32.0
}
embeddedTitleContentNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: previousTopInset))
transition.updateFrame(node: embeddedTitleContentNode, frame: CGRect(origin: CGPoint(), size: embeddedSize))
embeddedTitleContentNode.updateLayout(size: embeddedSize, topInset: statusBarHeight, interactiveExtension: embeddedTitleContentNode.interactiveExtension, transition: .immediate, transitionSurface: self, navigationBar: self.navigationBar)
} else {
transition.updateFrame(node: embeddedTitleContentNode, frame: CGRect(origin: CGPoint(), size: embeddedSize))
embeddedTitleContentNode.updateLayout(size: embeddedSize, topInset: statusBarHeight, interactiveExtension: embeddedTitleContentNode.interactiveExtension, transition: transition, transitionSurface: self, navigationBar: self.navigationBar)
}
insets.top += embeddedSize.height
} else {
insets.top += navigationBarHeight
if case .overlay = self.chatPresentationInterfaceState.mode {
insets.top = 44.0
} else {
insets.top += navigationBarHeight
}
if case .peek = self.embeddedTitlePeekContent {
insets.top += 32.0
}
}
if let dismissedEmbeddedTitleContentNode = self.dismissedEmbeddedTitleContentNode {
self.dismissedEmbeddedTitleContentNode = nil
if transition.isAnimated {
dismissedEmbeddedTitleContentNode.alpha = 0.0
dismissedEmbeddedTitleContentNode.layer.allowsGroupOpacity = true
dismissedEmbeddedTitleContentNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, completion: { [weak dismissedEmbeddedTitleContentNode] _ in
dismissedEmbeddedTitleContentNode?.removeFromSupernode()
})
transition.updateFrame(node: dismissedEmbeddedTitleContentNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: insets.top)))
} else {
dismissedEmbeddedTitleContentNode.removeFromSupernode()
}
}
transition.updateFrame(node: self.navigationBarBackroundNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: insets.top)))
transition.updateFrame(node: self.navigationBarSeparatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: insets.top), size: CGSize(width: layout.size.width, height: UIScreenPixel)))
var wrappingInsets = UIEdgeInsets()
if case .overlay = self.chatPresentationInterfaceState.mode {
let containerWidth = horizontalContainerFillingSizeForLayout(layout: layout, sideInset: 8.0 + layout.safeInsets.left)
@ -1518,6 +1762,9 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
}
self.updatePlainInputSeparator(transition: .immediate)
self.inputPanelBackgroundSeparatorNode.backgroundColor = self.chatPresentationInterfaceState.theme.chat.inputPanel.panelSeparatorColor
self.navigationBarBackroundNode.backgroundColor = chatPresentationInterfaceState.theme.rootController.navigationBar.backgroundColor
self.navigationBarSeparatorNode.backgroundColor = chatPresentationInterfaceState.theme.rootController.navigationBar.separatorColor
}
let keepSendButtonEnabled = chatPresentationInterfaceState.interfaceState.forwardMessageIds != nil || chatPresentationInterfaceState.interfaceState.editMessage != nil
@ -2384,4 +2631,58 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
func animateQuizCorrectOptionSelected() {
self.view.insertSubview(ConfettiView(frame: self.view.bounds), aboveSubview: self.historyNode.view)
}
func updateEmbeddedTitlePeekContent(content: NavigationControllerDropContent?) {
guard let (_, navigationHeight) = self.validLayout else {
return
}
var peekContent: ChatEmbeddedTitlePeekContent = .none
if let content = content, let item = content.item as? VideoNavigationControllerDropContentItem, let _ = item.itemNode as? OverlayUniversalVideoNode {
if content.position.y < navigationHeight + 32.0 {
peekContent = .peek
}
}
if self.embeddedTitlePeekContent != peekContent {
self.embeddedTitlePeekContent = peekContent
self.requestLayout(.animated(duration: 0.3, curve: .spring))
}
}
var updateHasEmbeddedTitleContent: ((Bool) -> Void)?
func acceptEmbeddedTitlePeekContent(content: NavigationControllerDropContent) -> Bool {
guard let (_, navigationHeight) = self.validLayout else {
return false
}
if content.position.y >= navigationHeight + 32.0 {
return false
}
if let item = content.item as? VideoNavigationControllerDropContentItem, let itemNode = item.itemNode as? OverlayUniversalVideoNode {
let embeddedTitleContentNode = ChatEmbeddedTitleContentNode(context: self.context, videoNode: itemNode, interactiveExtensionUpdated: { [weak self] transition in
guard let strongSelf = self else {
return
}
strongSelf.requestLayout(transition)
}, dismissed: { [weak self] in
guard let strongSelf = self else {
return
}
if let embeddedTitleContentNode = strongSelf.embeddedTitleContentNode {
strongSelf.embeddedTitleContentNode = nil
strongSelf.dismissedEmbeddedTitleContentNode = embeddedTitleContentNode
strongSelf.requestLayout(.animated(duration: 0.25, curve: .spring))
strongSelf.updateHasEmbeddedTitleContent?(false)
}
})
self.embeddedTitleContentNode = embeddedTitleContentNode
self.embeddedTitlePeekContent = .none
self.updateHasEmbeddedTitleContent?(true)
DispatchQueue.main.async {
self.requestLayout(.animated(duration: 0.25, curve: .spring))
}
return true
}
return false
}
}

View File

@ -23,61 +23,6 @@ enum ChatTitleContent {
case custom(String)
}
private final class ChatTitleNetworkStatusNode: ASDisplayNode {
private var theme: PresentationTheme
private let titleNode: ImmediateTextNode
private let activityIndicator: ActivityIndicator
var title: String = "" {
didSet {
if self.title != oldValue {
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.bold(17.0), textColor: self.theme.rootController.navigationBar.primaryTextColor)
}
}
}
init(theme: PresentationTheme) {
self.theme = theme
self.titleNode = ImmediateTextNode()
self.titleNode.isUserInteractionEnabled = false
self.titleNode.displaysAsynchronously = false
self.titleNode.maximumNumberOfLines = 1
self.titleNode.isOpaque = false
self.titleNode.isUserInteractionEnabled = false
self.activityIndicator = ActivityIndicator(type: .custom(theme.rootController.navigationBar.primaryTextColor, 22.0, 1.5, false), speed: .slow)
let activityIndicatorSize = self.activityIndicator.measure(CGSize(width: 100.0, height: 100.0))
self.activityIndicator.frame = CGRect(origin: CGPoint(), size: activityIndicatorSize)
super.init()
self.addSubnode(self.titleNode)
self.addSubnode(self.activityIndicator)
}
func updateTheme(theme: PresentationTheme) {
self.theme = theme
self.titleNode.attributedText = NSAttributedString(string: self.title, font: Font.medium(24.0), textColor: self.theme.rootController.navigationBar.primaryTextColor)
self.activityIndicator.type = .custom(self.theme.rootController.navigationBar.primaryTextColor, 22.0, 1.5, false)
}
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) {
let indicatorSize = self.activityIndicator.bounds.size
let indicatorPadding = indicatorSize.width + 6.0
let titleSize = self.titleNode.updateLayout(CGSize(width: max(1.0, size.width - indicatorPadding), height: size.height))
let combinedHeight = titleSize.height
let titleFrame = CGRect(origin: CGPoint(x: indicatorPadding + floor((size.width - titleSize.width - indicatorPadding) / 2.0), y: floor((size.height - combinedHeight) / 2.0)), size: titleSize)
transition.updateFrame(node: self.titleNode, frame: titleFrame)
transition.updateFrame(node: self.activityIndicator, frame: CGRect(origin: CGPoint(x: titleFrame.minX - indicatorSize.width - 4.0, y: titleFrame.minY - 1.0), size: indicatorSize))
}
}
private enum ChatTitleIcon {
case none
case lock
@ -88,6 +33,7 @@ final class ChatTitleView: UIView, NavigationBarTitleView {
private let account: Account
private var theme: PresentationTheme
private var hasEmbeddedTitleContent: Bool = false
private var strings: PresentationStrings
private var dateTimeFormat: PresentationDateTimeFormat
private var nameDisplayOrder: PresentationPersonNameOrder
@ -120,43 +66,6 @@ final class ChatTitleView: UIView, NavigationBarTitleView {
}
private func updateNetworkStatusNode(networkState: AccountNetworkState, layout: ContainerViewLayout?) {
var isOnline = false
if case .online = networkState {
isOnline = true
}
/*if isOnline || layout?.metrics.widthClass == .regular {
self.contentContainer.isHidden = false
if let networkStatusNode = self.networkStatusNode {
self.networkStatusNode = nil
networkStatusNode.removeFromSupernode()
}
} else {
self.contentContainer.isHidden = true
let statusNode: ChatTitleNetworkStatusNode
if let current = self.networkStatusNode {
statusNode = current
} else {
statusNode = ChatTitleNetworkStatusNode(theme: self.theme)
self.networkStatusNode = statusNode
self.insertSubview(statusNode.view, aboveSubview: self.contentContainer.view)
}
switch self.networkState {
case .waitingForNetwork:
statusNode.title = self.strings.State_WaitingForNetwork
case let .connecting(proxy):
if let layout = layout, proxy != nil && layout.size.width > 320.0 {
statusNode.title = self.strings.State_ConnectingToProxy
} else {
statusNode.title = self.strings.State_Connecting
}
case .updating:
statusNode.title = self.strings.State_Updating
case .online:
break
}
}*/
self.setNeedsLayout()
}
@ -183,6 +92,8 @@ final class ChatTitleView: UIView, NavigationBarTitleView {
var titleContent: ChatTitleContent? {
didSet {
if let titleContent = self.titleContent {
let titleTheme = self.hasEmbeddedTitleContent ? defaultDarkPresentationTheme : self.theme
var string: NSAttributedString?
var titleLeftIcon: ChatTitleIcon = .none
var titleRightIcon: ChatTitleIcon = .none
@ -192,20 +103,20 @@ final class ChatTitleView: UIView, NavigationBarTitleView {
case let .peer(peerView, _, isScheduledMessages):
if isScheduledMessages {
if peerView.peerId == self.account.peerId {
string = NSAttributedString(string: self.strings.ScheduledMessages_RemindersTitle, font: Font.medium(17.0), textColor: self.theme.rootController.navigationBar.primaryTextColor)
string = NSAttributedString(string: self.strings.ScheduledMessages_RemindersTitle, font: Font.medium(17.0), textColor: titleTheme.rootController.navigationBar.primaryTextColor)
} else {
string = NSAttributedString(string: self.strings.ScheduledMessages_Title, font: Font.medium(17.0), textColor: self.theme.rootController.navigationBar.primaryTextColor)
string = NSAttributedString(string: self.strings.ScheduledMessages_Title, font: Font.medium(17.0), textColor: titleTheme.rootController.navigationBar.primaryTextColor)
}
isEnabled = false
} else {
if let peer = peerViewMainPeer(peerView) {
if peerView.peerId == self.account.peerId {
string = NSAttributedString(string: self.strings.Conversation_SavedMessages, font: Font.medium(17.0), textColor: self.theme.rootController.navigationBar.primaryTextColor)
string = NSAttributedString(string: self.strings.Conversation_SavedMessages, font: Font.medium(17.0), textColor: titleTheme.rootController.navigationBar.primaryTextColor)
} else {
if !peerView.peerIsContact, let user = peer as? TelegramUser, !user.flags.contains(.isSupport), user.botInfo == nil, let phone = user.phone, !phone.isEmpty {
string = NSAttributedString(string: formatPhoneNumber(phone), font: Font.medium(17.0), textColor: self.theme.rootController.navigationBar.primaryTextColor)
string = NSAttributedString(string: formatPhoneNumber(phone), font: Font.medium(17.0), textColor: titleTheme.rootController.navigationBar.primaryTextColor)
} else {
string = NSAttributedString(string: peer.displayTitle(strings: self.strings, displayOrder: self.nameDisplayOrder), font: Font.medium(17.0), textColor: self.theme.rootController.navigationBar.primaryTextColor)
string = NSAttributedString(string: peer.displayTitle(strings: self.strings, displayOrder: self.nameDisplayOrder), font: Font.medium(17.0), textColor: titleTheme.rootController.navigationBar.primaryTextColor)
}
}
titleScamIcon = peer.isScam
@ -220,9 +131,9 @@ final class ChatTitleView: UIView, NavigationBarTitleView {
}
}
case .group:
string = NSAttributedString(string: "Feed", font: Font.medium(17.0), textColor: self.theme.rootController.navigationBar.primaryTextColor)
string = NSAttributedString(string: "Feed", font: Font.medium(17.0), textColor: titleTheme.rootController.navigationBar.primaryTextColor)
case let .custom(text):
string = NSAttributedString(string: text, font: Font.medium(17.0), textColor: self.theme.rootController.navigationBar.primaryTextColor)
string = NSAttributedString(string: text, font: Font.medium(17.0), textColor: titleTheme.rootController.navigationBar.primaryTextColor)
}
if let string = string, self.titleNode.attributedText == nil || !self.titleNode.attributedText!.isEqual(to: string) {
@ -234,7 +145,7 @@ final class ChatTitleView: UIView, NavigationBarTitleView {
self.titleLeftIcon = titleLeftIcon
switch titleLeftIcon {
case .lock:
self.titleLeftIconNode.image = PresentationResourcesChat.chatTitleLockIcon(self.theme)
self.titleLeftIconNode.image = PresentationResourcesChat.chatTitleLockIcon(titleTheme)
default:
self.titleLeftIconNode.image = nil
}
@ -243,7 +154,7 @@ final class ChatTitleView: UIView, NavigationBarTitleView {
if titleScamIcon != self.titleScamIcon {
self.titleScamIcon = titleScamIcon
self.titleCredibilityIconNode.image = titleScamIcon ? PresentationResourcesChatList.scamIcon(self.theme, type: .regular) : nil
self.titleCredibilityIconNode.image = titleScamIcon ? PresentationResourcesChatList.scamIcon(titleTheme, type: .regular) : nil
self.setNeedsLayout()
}
@ -251,7 +162,7 @@ final class ChatTitleView: UIView, NavigationBarTitleView {
self.titleRightIcon = titleRightIcon
switch titleRightIcon {
case .mute:
self.titleRightIconNode.image = PresentationResourcesChat.chatTitleMuteIcon(self.theme)
self.titleRightIconNode.image = PresentationResourcesChat.chatTitleMuteIcon(titleTheme)
default:
self.titleRightIconNode.image = nil
}
@ -278,6 +189,8 @@ final class ChatTitleView: UIView, NavigationBarTitleView {
}
}
let titleTheme = self.hasEmbeddedTitleContent ? defaultDarkPresentationTheme : self.theme
var state = ChatTitleActivityNodeState.none
switch self.networkState {
case .waitingForNetwork, .connecting, .updating:
@ -285,14 +198,14 @@ final class ChatTitleView: UIView, NavigationBarTitleView {
switch self.networkState {
case .waitingForNetwork:
infoText = self.strings.ChatState_WaitingForNetwork
case let .connecting(proxy):
case .connecting:
infoText = self.strings.ChatState_Connecting
case .updating:
infoText = self.strings.ChatState_Updating
case .online:
infoText = ""
}
state = .info(NSAttributedString(string: infoText, font: Font.regular(13.0), textColor: self.theme.rootController.navigationBar.secondaryTextColor), .generic)
state = .info(NSAttributedString(string: infoText, font: Font.regular(13.0), textColor: titleTheme.rootController.navigationBar.secondaryTextColor), .generic)
case .online:
if let (peerId, inputActivities) = self.inputActivities, !inputActivities.isEmpty, inputActivitiesAllowed {
var stringValue = ""
@ -336,7 +249,7 @@ final class ChatTitleView: UIView, NavigationBarTitleView {
}
}
}
let color = self.theme.rootController.navigationBar.accentTextColor
let color = titleTheme.rootController.navigationBar.accentTextColor
let string = NSAttributedString(string: stringValue, font: Font.regular(13.0), textColor: color)
switch mergedActivity {
case .typingText:
@ -357,21 +270,21 @@ final class ChatTitleView: UIView, NavigationBarTitleView {
if let peer = peerViewMainPeer(peerView) {
let servicePeer = isServicePeer(peer)
if peer.id == self.account.peerId || isScheduledMessages {
let string = NSAttributedString(string: "", font: Font.regular(13.0), textColor: self.theme.rootController.navigationBar.secondaryTextColor)
let string = NSAttributedString(string: "", font: Font.regular(13.0), textColor: titleTheme.rootController.navigationBar.secondaryTextColor)
state = .info(string, .generic)
} else if let user = peer as? TelegramUser {
if servicePeer {
let string = NSAttributedString(string: "", font: Font.regular(13.0), textColor: self.theme.rootController.navigationBar.secondaryTextColor)
let string = NSAttributedString(string: "", font: Font.regular(13.0), textColor: titleTheme.rootController.navigationBar.secondaryTextColor)
state = .info(string, .generic)
} else if user.flags.contains(.isSupport) {
let statusText = self.strings.Bot_GenericSupportStatus
let string = NSAttributedString(string: statusText, font: Font.regular(13.0), textColor: self.theme.rootController.navigationBar.secondaryTextColor)
let string = NSAttributedString(string: statusText, font: Font.regular(13.0), textColor: titleTheme.rootController.navigationBar.secondaryTextColor)
state = .info(string, .generic)
} else if let _ = user.botInfo {
let statusText = self.strings.Bot_GenericBotStatus
let string = NSAttributedString(string: statusText, font: Font.regular(13.0), textColor: self.theme.rootController.navigationBar.secondaryTextColor)
let string = NSAttributedString(string: statusText, font: Font.regular(13.0), textColor: titleTheme.rootController.navigationBar.secondaryTextColor)
state = .info(string, .generic)
} else if let peer = peerViewMainPeer(peerView) {
let timestamp = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970
@ -383,10 +296,10 @@ final class ChatTitleView: UIView, NavigationBarTitleView {
userPresence = TelegramUserPresence(status: .none, lastActivity: 0)
}
let (string, activity) = stringAndActivityForUserPresence(strings: self.strings, dateTimeFormat: self.dateTimeFormat, presence: userPresence, relativeTo: Int32(timestamp))
let attributedString = NSAttributedString(string: string, font: Font.regular(13.0), textColor: activity ? self.theme.rootController.navigationBar.accentTextColor : self.theme.rootController.navigationBar.secondaryTextColor)
let attributedString = NSAttributedString(string: string, font: Font.regular(13.0), textColor: activity ? titleTheme.rootController.navigationBar.accentTextColor : titleTheme.rootController.navigationBar.secondaryTextColor)
state = .info(attributedString, activity ? .online : .lastSeenTime)
} else {
let string = NSAttributedString(string: "", font: Font.regular(13.0), textColor: self.theme.rootController.navigationBar.secondaryTextColor)
let string = NSAttributedString(string: "", font: Font.regular(13.0), textColor: titleTheme.rootController.navigationBar.secondaryTextColor)
state = .info(string, .generic)
}
} else if let group = peer as? TelegramGroup {
@ -408,11 +321,11 @@ final class ChatTitleView: UIView, NavigationBarTitleView {
if onlineCount > 1 {
let string = NSMutableAttributedString()
string.append(NSAttributedString(string: "\(strings.Conversation_StatusMembers(Int32(group.participantCount))), ", font: Font.regular(13.0), textColor: self.theme.rootController.navigationBar.secondaryTextColor))
string.append(NSAttributedString(string: strings.Conversation_StatusOnline(Int32(onlineCount)), font: Font.regular(13.0), textColor: self.theme.rootController.navigationBar.secondaryTextColor))
string.append(NSAttributedString(string: "\(strings.Conversation_StatusMembers(Int32(group.participantCount))), ", font: Font.regular(13.0), textColor: titleTheme.rootController.navigationBar.secondaryTextColor))
string.append(NSAttributedString(string: strings.Conversation_StatusOnline(Int32(onlineCount)), font: Font.regular(13.0), textColor: titleTheme.rootController.navigationBar.secondaryTextColor))
state = .info(string, .generic)
} else {
let string = NSAttributedString(string: strings.Conversation_StatusMembers(Int32(group.participantCount)), font: Font.regular(13.0), textColor: self.theme.rootController.navigationBar.secondaryTextColor)
let string = NSAttributedString(string: strings.Conversation_StatusMembers(Int32(group.participantCount)), font: Font.regular(13.0), textColor: titleTheme.rootController.navigationBar.secondaryTextColor)
state = .info(string, .generic)
}
} else if let channel = peer as? TelegramChannel {
@ -420,17 +333,17 @@ final class ChatTitleView: UIView, NavigationBarTitleView {
if memberCount == 0 {
let string: NSAttributedString
if case .group = channel.info {
string = NSAttributedString(string: strings.Group_Status, font: Font.regular(13.0), textColor: self.theme.rootController.navigationBar.secondaryTextColor)
string = NSAttributedString(string: strings.Group_Status, font: Font.regular(13.0), textColor: titleTheme.rootController.navigationBar.secondaryTextColor)
} else {
string = NSAttributedString(string: strings.Channel_Status, font: Font.regular(13.0), textColor: self.theme.rootController.navigationBar.secondaryTextColor)
string = NSAttributedString(string: strings.Channel_Status, font: Font.regular(13.0), textColor: titleTheme.rootController.navigationBar.secondaryTextColor)
}
state = .info(string, .generic)
} else {
if case .group = channel.info, let onlineMemberCount = onlineMemberCount, onlineMemberCount > 1 {
let string = NSMutableAttributedString()
string.append(NSAttributedString(string: "\(strings.Conversation_StatusMembers(Int32(memberCount))), ", font: Font.regular(13.0), textColor: self.theme.rootController.navigationBar.secondaryTextColor))
string.append(NSAttributedString(string: strings.Conversation_StatusOnline(Int32(onlineMemberCount)), font: Font.regular(13.0), textColor: self.theme.rootController.navigationBar.secondaryTextColor))
string.append(NSAttributedString(string: "\(strings.Conversation_StatusMembers(Int32(memberCount))), ", font: Font.regular(13.0), textColor: titleTheme.rootController.navigationBar.secondaryTextColor))
string.append(NSAttributedString(string: strings.Conversation_StatusOnline(Int32(onlineMemberCount)), font: Font.regular(13.0), textColor: titleTheme.rootController.navigationBar.secondaryTextColor))
state = .info(string, .generic)
} else {
let membersString: String
@ -439,17 +352,17 @@ final class ChatTitleView: UIView, NavigationBarTitleView {
} else {
membersString = strings.Conversation_StatusSubscribers(memberCount)
}
let string = NSAttributedString(string: membersString, font: Font.regular(13.0), textColor: self.theme.rootController.navigationBar.secondaryTextColor)
let string = NSAttributedString(string: membersString, font: Font.regular(13.0), textColor: titleTheme.rootController.navigationBar.secondaryTextColor)
state = .info(string, .generic)
}
}
} else {
switch channel.info {
case .group:
let string = NSAttributedString(string: strings.Group_Status, font: Font.regular(13.0), textColor: self.theme.rootController.navigationBar.secondaryTextColor)
let string = NSAttributedString(string: strings.Group_Status, font: Font.regular(13.0), textColor: titleTheme.rootController.navigationBar.secondaryTextColor)
state = .info(string, .generic)
case .broadcast:
let string = NSAttributedString(string: strings.Channel_Status, font: Font.regular(13.0), textColor: self.theme.rootController.navigationBar.secondaryTextColor)
let string = NSAttributedString(string: strings.Channel_Status, font: Font.regular(13.0), textColor: titleTheme.rootController.navigationBar.secondaryTextColor)
state = .info(string, .generic)
}
}
@ -551,11 +464,11 @@ final class ChatTitleView: UIView, NavigationBarTitleView {
}
}
func updateThemeAndStrings(theme: PresentationTheme, strings: PresentationStrings) {
func updateThemeAndStrings(theme: PresentationTheme, strings: PresentationStrings, hasEmbeddedTitleContent: Bool) {
self.theme = theme
self.hasEmbeddedTitleContent = hasEmbeddedTitleContent
self.strings = strings
//self.networkStatusNode?.updateTheme(theme: theme)
let titleContent = self.titleContent
self.titleContent = titleContent
self.updateStatus()
@ -568,8 +481,6 @@ final class ChatTitleView: UIView, NavigationBarTitleView {
func updateLayout(size: CGSize, clearBounds: CGRect, transition: ContainedViewLayoutTransition) {
self.validLayout = (size, clearBounds)
let transition: ContainedViewLayoutTransition = .immediate
self.button.frame = clearBounds
self.contentContainer.frame = clearBounds

View File

@ -11,6 +11,9 @@ public final class OverlayMediaControllerImpl: ViewController, OverlayMediaContr
return self.displayNode as! OverlayMediaControllerNode
}
public var updatePossibleEmbeddingItem: ((OverlayMediaControllerEmbeddingItem?) -> Void)?
public var embedPossibleEmbeddingItem: ((OverlayMediaControllerEmbeddingItem) -> Bool)?
public init() {
super.init(navigationBarPresentationData: nil)
@ -22,7 +25,11 @@ public final class OverlayMediaControllerImpl: ViewController, OverlayMediaContr
}
override public func loadDisplayNode() {
self.displayNode = OverlayMediaControllerNode()
self.displayNode = OverlayMediaControllerNode(updatePossibleEmbeddingItem: { [weak self] item in
self?.updatePossibleEmbeddingItem?(item)
}, embedPossibleEmbeddingItem: { [weak self] item in
return self?.embedPossibleEmbeddingItem?(item) ?? false
})
self.displayNodeDidLoad()
}

View File

@ -28,7 +28,12 @@ private final class OverlayMediaVideoNodeData {
}
}
final class OverlayMediaControllerNode: ASDisplayNode, UIGestureRecognizerDelegate {
private let updatePossibleEmbeddingItem: (OverlayMediaControllerEmbeddingItem?) -> Void
private let embedPossibleEmbeddingItem: (OverlayMediaControllerEmbeddingItem) -> Bool
private var videoNodes: [OverlayMediaVideoNodeData] = []
private var validLayout: ContainerViewLayout?
@ -40,7 +45,10 @@ final class OverlayMediaControllerNode: ASDisplayNode, UIGestureRecognizerDelega
private var pinchingNode: OverlayMediaItemNode?
private var pinchingNodeInitialSize: CGSize?
override init() {
init(updatePossibleEmbeddingItem: @escaping (OverlayMediaControllerEmbeddingItem?) -> Void, embedPossibleEmbeddingItem: @escaping (OverlayMediaControllerEmbeddingItem) -> Bool) {
self.updatePossibleEmbeddingItem = updatePossibleEmbeddingItem
self.embedPossibleEmbeddingItem = embedPossibleEmbeddingItem
super.init()
self.setViewBlock({
@ -329,34 +337,46 @@ final class OverlayMediaControllerNode: ASDisplayNode, UIGestureRecognizerDelega
draggingNode.updateMinimizedEdge(nil, adjusting: true)
}
draggingNode.frame = nodeFrame
self.updatePossibleEmbeddingItem(OverlayMediaControllerEmbeddingItem(
position: nodeFrame.center,
itemNode: draggingNode
))
}
case .ended, .cancelled:
if let draggingNode = self.draggingNode, let validLayout = self.validLayout, let index = self.videoNodes.firstIndex(where: { $0.node === draggingNode }){
let nodeSize = self.videoNodes[index].currentSize
let previousFrame = draggingNode.frame
let (updatedLocation, shouldDismiss) = self.nodeLocationForPosition(layout: validLayout, position: CGPoint(x: previousFrame.midX, y: previousFrame.midY), velocity: recognizer.velocity(in: self.view), size: nodeSize, tempExtendedTopInset: draggingNode.tempExtendedTopInset)
if shouldDismiss && draggingNode.isMinimizeable {
draggingNode.updateMinimizedEdge(updatedLocation.x.isZero ? .left : .right, adjusting: false)
self.videoNodes[index].isMinimized = true
if self.embedPossibleEmbeddingItem(OverlayMediaControllerEmbeddingItem(
position: previousFrame.center,
itemNode: draggingNode
)) {
self.draggingNode = nil
} else {
draggingNode.updateMinimizedEdge(nil, adjusting: true)
self.videoNodes[index].isMinimized = false
}
if let group = draggingNode.group {
self.locationByGroup[group] = updatedLocation
}
self.videoNodes[index].location = updatedLocation
draggingNode.frame = CGRect(origin: self.nodePosition(layout: validLayout, size: nodeSize, location: updatedLocation, hidden: !draggingNode.hasAttachedContext, isMinimized: self.videoNodes[index].isMinimized, tempExtendedTopInset: draggingNode.tempExtendedTopInset), size: nodeSize)
draggingNode.layer.animateFrame(from: previousFrame, to: draggingNode.frame, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring)
self.draggingNode = nil
if shouldDismiss && !draggingNode.isMinimizeable {
draggingNode.dismiss()
let (updatedLocation, shouldDismiss) = self.nodeLocationForPosition(layout: validLayout, position: CGPoint(x: previousFrame.midX, y: previousFrame.midY), velocity: recognizer.velocity(in: self.view), size: nodeSize, tempExtendedTopInset: draggingNode.tempExtendedTopInset)
if shouldDismiss && draggingNode.isMinimizeable {
draggingNode.updateMinimizedEdge(updatedLocation.x.isZero ? .left : .right, adjusting: false)
self.videoNodes[index].isMinimized = true
} else {
draggingNode.updateMinimizedEdge(nil, adjusting: true)
self.videoNodes[index].isMinimized = false
}
if let group = draggingNode.group {
self.locationByGroup[group] = updatedLocation
}
self.videoNodes[index].location = updatedLocation
draggingNode.frame = CGRect(origin: self.nodePosition(layout: validLayout, size: nodeSize, location: updatedLocation, hidden: !draggingNode.hasAttachedContext, isMinimized: self.videoNodes[index].isMinimized, tempExtendedTopInset: draggingNode.tempExtendedTopInset), size: nodeSize)
draggingNode.layer.animateFrame(from: previousFrame, to: draggingNode.frame, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring)
self.draggingNode = nil
if shouldDismiss && !draggingNode.isMinimizeable {
draggingNode.dismiss()
}
}
self.updatePossibleEmbeddingItem(nil)
}
default:
break

View File

@ -2281,14 +2281,14 @@ final class PeerInfoHeaderNode: ASDisplayNode {
if buttonKeys.count > 3 {
if self.isOpenedFromChat {
switch buttonKey {
case .message, .search:
case .message, .search, .videoCall:
hiddenWhileExpanded = true
default:
hiddenWhileExpanded = false
}
} else {
switch buttonKey {
case .mute, .search:
case .mute, .search, .videoCall:
hiddenWhileExpanded = true
default:
hiddenWhileExpanded = false

View File

@ -207,6 +207,43 @@ public final class SharedAccountContextImpl: SharedAccountContext {
self.mediaManager = MediaManagerImpl(accountManager: accountManager, inForeground: applicationBindings.applicationInForeground, presentationData: presentationData)
self.mediaManager.overlayMediaManager.updatePossibleEmbeddingItem = { [weak self] item in
guard let strongSelf = self else {
return
}
guard let navigationController = strongSelf.mainWindow?.viewController as? NavigationController else {
return
}
var content: NavigationControllerDropContent?
if let item = item {
content = NavigationControllerDropContent(
position: item.position,
item: VideoNavigationControllerDropContentItem(
itemNode: item.itemNode
)
)
}
navigationController.updatePossibleControllerDropContent(content: content)
}
self.mediaManager.overlayMediaManager.embedPossibleEmbeddingItem = { [weak self] item in
guard let strongSelf = self else {
return false
}
guard let navigationController = strongSelf.mainWindow?.viewController as? NavigationController else {
return false
}
let content = NavigationControllerDropContent(
position: item.position,
item: VideoNavigationControllerDropContentItem(
itemNode: item.itemNode
)
)
return navigationController.acceptPossibleControllerDropContent(content: content)
}
self._autodownloadSettings.set(.single(initialPresentationDataAndSettings.autodownloadSettings)
|> then(accountManager.sharedData(keys: [SharedDataKeys.autodownloadSettings])
|> map { sharedData in

View File

@ -10,7 +10,7 @@ import TelegramAudio
import AccountContext
public final class OverlayUniversalVideoNode: OverlayMediaItemNode {
private let content: UniversalVideoContent
public let content: UniversalVideoContent
private let videoNode: UniversalVideoNode
private let decoration: OverlayVideoDecoration
@ -30,8 +30,16 @@ public final class OverlayUniversalVideoNode: OverlayMediaItemNode {
}
}
private let defaultExpand: () -> Void
public var customExpand: (() -> Void)?
public var customClose: (() -> Void)?
public init(postbox: Postbox, audioSession: ManagedAudioSession, manager: UniversalVideoManager, content: UniversalVideoContent, expand: @escaping () -> Void, close: @escaping () -> Void) {
self.content = content
self.defaultExpand = expand
var expandImpl: (() -> Void)?
var unminimizeImpl: (() -> Void)?
var togglePlayPauseImpl: (() -> Void)?
var closeImpl: (() -> Void)?
@ -40,7 +48,7 @@ public final class OverlayUniversalVideoNode: OverlayMediaItemNode {
}, togglePlayPause: {
togglePlayPauseImpl?()
}, expand: {
expand()
expandImpl?()
}, close: {
closeImpl?()
})
@ -49,6 +57,17 @@ public final class OverlayUniversalVideoNode: OverlayMediaItemNode {
super.init()
expandImpl = { [weak self] in
guard let strongSelf = self else {
return
}
if let customExpand = strongSelf.customExpand {
customExpand()
} else {
strongSelf.defaultExpand()
}
}
unminimizeImpl = { [weak self] in
self?.unminimize?()
}
@ -57,6 +76,10 @@ public final class OverlayUniversalVideoNode: OverlayMediaItemNode {
}
closeImpl = { [weak self] in
if let strongSelf = self {
if let customClose = strongSelf.customClose {
customClose()
return
}
if strongSelf.videoNode.hasAttachedContext {
strongSelf.videoNode.continuePlayingWithoutSound()
}
@ -104,18 +127,32 @@ public final class OverlayUniversalVideoNode: OverlayMediaItemNode {
override public func updateLayout(_ size: CGSize) {
if size != self.validLayoutSize {
self.updateLayoutImpl(size)
self.updateLayoutImpl(size, transition: .immediate)
}
}
private func updateLayoutImpl(_ size: CGSize) {
public func updateLayout(_ size: CGSize, transition: ContainedViewLayoutTransition) {
if size != self.validLayoutSize {
self.updateLayoutImpl(size, transition: transition)
}
}
private func updateLayoutImpl(_ size: CGSize, transition: ContainedViewLayoutTransition) {
self.validLayoutSize = size
self.videoNode.frame = CGRect(origin: CGPoint(), size: size)
self.videoNode.updateLayout(size: size, transition: .immediate)
transition.updateFrame(node: self.videoNode, frame: CGRect(origin: CGPoint(), size: size))
self.videoNode.updateLayout(size: size, transition: transition)
}
override public func updateMinimizedEdge(_ edge: OverlayMediaItemMinimizationEdge?, adjusting: Bool) {
self.decoration.updateMinimizedEdge(edge, adjusting: adjusting)
}
public func updateRoundCorners(_ value: Bool, transition: ContainedViewLayoutTransition) {
transition.updateCornerRadius(node: self, cornerRadius: value ? 4.0 : 0.0)
}
public func showControls() {
self.decoration.showControls()
}
}

View File

@ -148,6 +148,13 @@ final class OverlayVideoDecoration: UniversalVideoDecoration {
}
}
func showControls() {
if self.controlsNode.alpha.isZero {
self.controlsNode.alpha = 1.0
self.controlsNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
}
}
func setStatus(_ status: Signal<MediaPlayerStatus?, NoError>) {
self.controlsNode.status = status |> map { value -> MediaPlayerStatus in
if let value = value {

View File

@ -109,12 +109,12 @@ final class PictureInPictureVideoControlsNode: ASDisplayNode {
let buttonSize = TGEmbedPIPButtonSize
self.leaveButton.frame = CGRect(origin: CGPoint(x: forth - floor(buttonSize.width / 2.0) - 10.0, y: size.height - buttonSize.height - 15.0), size: buttonSize)
transition.updateFrame(view: self.leaveButton, frame: CGRect(origin: CGPoint(x: forth - floor(buttonSize.width / 2.0) - 10.0, y: size.height - buttonSize.height - 15.0), size: buttonSize))
self.pauseButton.frame = CGRect(origin: CGPoint(x: floor((size.width - buttonSize.width) / 2.0), y: size.height - buttonSize.height - 15.0), size: buttonSize)
self.playButton.frame = CGRect(origin: CGPoint(x: floor((size.width - buttonSize.width) / 2.0), y: size.height - buttonSize.height - 15.0), size: buttonSize)
transition.updateFrame(view: self.pauseButton, frame: CGRect(origin: CGPoint(x: floor((size.width - buttonSize.width) / 2.0), y: size.height - buttonSize.height - 15.0), size: buttonSize))
transition.updateFrame(view: self.playButton, frame: CGRect(origin: CGPoint(x: floor((size.width - buttonSize.width) / 2.0), y: size.height - buttonSize.height - 15.0), size: buttonSize))
self.closeButton.frame = CGRect(origin: CGPoint(x: self.playButton.frame.origin.x + forth + 10.0, y: size.height - buttonSize.height - 15.0), size: buttonSize)
transition.updateFrame(view: self.closeButton, frame: CGRect(origin: CGPoint(x: self.playButton.frame.origin.x + forth + 10.0, y: size.height - buttonSize.height - 15.0), size: buttonSize))
}
@objc func leavePressed() {